new
This commit is contained in:
parent
3084846b2a
commit
5f324fc9c0
|
@ -11,4 +11,5 @@ use Illuminate\Support\Facades\Auth;
|
||||||
class Controller extends BaseController
|
class Controller extends BaseController
|
||||||
{
|
{
|
||||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,10 @@ class HomeController extends Controller
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
// Paths to CSV files in public/storage
|
// Paths to CSV files in public/storage
|
||||||
$pathDana = public_path('storage/data_labeled.csv');
|
$pathDana = base_path('resources/views/data/data_labeled.csv');
|
||||||
$pathGopay = public_path('storage/data_labeledgopay.csv');
|
$pathGopay = base_path('resources/views/data/data_labeledgopay.csv');
|
||||||
$pathShopee = public_path('storage/data_labeledshopee.csv');
|
$pathShopee = base_path('resources/views/data/data_labeledshopee.csv');
|
||||||
|
|
||||||
|
|
||||||
// Read CSVs
|
// Read CSVs
|
||||||
$danaRows = $this->readCsv($pathDana);
|
$danaRows = $this->readCsv($pathDana);
|
||||||
|
@ -69,19 +70,19 @@ class HomeController extends Controller
|
||||||
// Ambil semua baris sesuai source (sama seperti sebelumnya)
|
// Ambil semua baris sesuai source (sama seperti sebelumnya)
|
||||||
switch ($source) {
|
switch ($source) {
|
||||||
case 'dana':
|
case 'dana':
|
||||||
$rows = $this->readCsv(public_path('storage/data_labeled.csv'));
|
$rows = $this->readCsv(base_path('resources/views/data/data_labeled.csv'));
|
||||||
break;
|
break;
|
||||||
case 'gopay':
|
case 'gopay':
|
||||||
$rows = $this->readCsv(public_path('storage/data_labeledgopay.csv'));
|
$rows = $this->readCsv(base_path('resources/views/data/data_labeledgopay.csv'));
|
||||||
break;
|
break;
|
||||||
case 'shopeepay':
|
case 'shopeepay':
|
||||||
$rows = $this->readCsv(public_path('storage/data_labeledshopee.csv'));
|
$rows = $this->readCsv(base_path('resources/views/data/data_labeledshopee.csv'));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$rows = array_merge(
|
$rows = array_merge(
|
||||||
$this->readCsv(public_path('storage/data_labeled.csv')),
|
$this->readCsv(base_path('resources/views/data/data_labeled.csv')),
|
||||||
$this->readCsv(public_path('storage/data_labeledgopay.csv')),
|
$this->readCsv(base_path('resources/views/data/data_labeledgopay.csv')),
|
||||||
$this->readCsv(public_path('storage/data_labeledshopee.csv'))
|
$this->readCsv(base_path('resources/views/data/data_labeledshopee.csv'))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Response;
|
||||||
|
|
||||||
|
class WelcomeController extends Controller{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
// Paths to CSV files in public/storage
|
||||||
|
$pathDana = base_path('resources/views/data/data_labeled.csv');
|
||||||
|
$pathGopay = base_path('resources/views/data/data_labeledgopay.csv');
|
||||||
|
$pathShopee = base_path('resources/views/data/data_labeledshopee.csv');
|
||||||
|
|
||||||
|
|
||||||
|
// Read CSVs
|
||||||
|
$danaRows = $this->readCsv($pathDana);
|
||||||
|
$gopayRows = $this->readCsv($pathGopay);
|
||||||
|
$shopeeRows = $this->readCsv($pathShopee);
|
||||||
|
|
||||||
|
// Merge for "all"
|
||||||
|
$allRows = array_merge($danaRows, $gopayRows, $shopeeRows);
|
||||||
|
|
||||||
|
// Counting helper
|
||||||
|
$calcCounts = function(array $rows) {
|
||||||
|
$counts = ['positif' => 0, 'netral' => 0, 'negatif' => 0];
|
||||||
|
$map = [
|
||||||
|
'positive' => 'positif',
|
||||||
|
'neutral' => 'netral',
|
||||||
|
'negative' => 'negatif',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$label = strtolower(trim($r['label_auto'] ?? ''));
|
||||||
|
if (isset($map[$label])) {
|
||||||
|
$counts[$map[$label]]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $counts;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate counts
|
||||||
|
$counts = [
|
||||||
|
'all' => $calcCounts($allRows),
|
||||||
|
'dana' => $calcCounts($danaRows),
|
||||||
|
'gopay' => $calcCounts($gopayRows),
|
||||||
|
'shopeepay' => $calcCounts($shopeeRows),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('welcome', compact('counts'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return JSON word-cloud data for a given source.
|
||||||
|
* GET /wordcloud-data?source=all|dana|gopay|shopeepay
|
||||||
|
*/
|
||||||
|
public function wordcloudData(Request $request)
|
||||||
|
{
|
||||||
|
$source = strtolower($request->query('source', 'all'));
|
||||||
|
$sentiment = strtolower($request->query('sentiment', 'all'));
|
||||||
|
$map = ['positif'=>'positive','netral'=>'neutral','negatif'=>'negative'];
|
||||||
|
|
||||||
|
// Ambil semua baris sesuai source (sama seperti sebelumnya)
|
||||||
|
switch ($source) {
|
||||||
|
case 'dana':
|
||||||
|
$rows = $this->readCsv(base_path('resources/views/data/data_labeled.csv'));
|
||||||
|
break;
|
||||||
|
case 'gopay':
|
||||||
|
$rows = $this->readCsv(base_path('resources/views/data/data_labeledgopay.csv'));
|
||||||
|
break;
|
||||||
|
case 'shopeepay':
|
||||||
|
$rows = $this->readCsv(base_path('resources/views/data/data_labeledshopee.csv'));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$rows = array_merge(
|
||||||
|
$this->readCsv(base_path('resources/views/data/data_labeled.csv')),
|
||||||
|
$this->readCsv(base_path('resources/views/data/data_labeledgopay.csv')),
|
||||||
|
$this->readCsv(base_path('resources/views/data/data_labeledshopee.csv'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika permintaan untuk sentimen tertentu, filter dulu
|
||||||
|
if (isset($map[$sentiment])) {
|
||||||
|
$engLabel = $map[$sentiment];
|
||||||
|
$rows = array_filter($rows, fn($r) => strtolower($r['label_auto'] ?? '') === $engLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tokenize & hitung frekuensi kata
|
||||||
|
$freq = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$words = preg_split('/\W+/', strtolower($row['clean_text'] ?? ''), -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
foreach ($words as $w) {
|
||||||
|
if (strlen($w) < 3) continue;
|
||||||
|
$freq[$w] = ($freq[$w] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arsort($freq);
|
||||||
|
$top = array_slice($freq, 0, 800, true); // batasi ke 70 kata
|
||||||
|
|
||||||
|
$list = [];
|
||||||
|
foreach ($top as $word => $count) {
|
||||||
|
$list[] = [$word, $count];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response::json($list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper: load a CSV into an associative array.
|
||||||
|
*/
|
||||||
|
private function readCsv(string $path): array
|
||||||
|
{
|
||||||
|
if (! File::exists($path)) {
|
||||||
|
logger()->warning("CSV file not found at {$path}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$handle = fopen($path, 'r');
|
||||||
|
$header = fgetcsv($handle);
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
while ($row = fgetcsv($handle)) {
|
||||||
|
$data[] = array_combine($header, $row);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($handle);
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -294,9 +294,9 @@
|
||||||
$r = $mapRowByKelas[$kelas];
|
$r = $mapRowByKelas[$kelas];
|
||||||
// Ambil nilai ke‐2 (precision), ke‐3 (recall), ke‐4 (f1‐score)
|
// Ambil nilai ke‐2 (precision), ke‐3 (recall), ke‐4 (f1‐score)
|
||||||
// Hati‐hati: format CSV kadang ada kolom kosong di indeks 1 → kita gunakan r[2], r[3], r[4].
|
// Hati‐hati: format CSV kadang ada kolom kosong di indeks 1 → kita gunakan r[2], r[3], r[4].
|
||||||
$precision = isset($r[2]) ? (float) $r[2] : 0;
|
$precision = isset($r[1]) ? (float) $r[1] : 0;
|
||||||
$recall = isset($r[3]) ? (float) $r[3] : 0;
|
$recall = isset($r[2]) ? (float) $r[2] : 0;
|
||||||
$f1score = isset($r[4]) ? (float) $r[4] : 0;
|
$f1score = isset($r[3]) ? (float) $r[3] : 0;
|
||||||
|
|
||||||
// Push ke dalam array
|
// Push ke dalam array
|
||||||
$metricsDetail[$key]['labels'][] = ucfirst($kelas);
|
$metricsDetail[$key]['labels'][] = ucfirst($kelas);
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
,Predicted_neutral,Predicted_positive,Predicted_negative
|
||||||
|
Actual_neutral,75,134,5
|
||||||
|
Actual_positive,1,876,1
|
||||||
|
Actual_negative,2,157,120
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
,Predicted_neutral,Predicted_positive,Predicted_negative
|
||||||
|
Actual_neutral,644,0,37
|
||||||
|
Actual_positive,54,8,39
|
||||||
|
Actual_negative,69,0,507
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
,Predicted_neutral,Predicted_positive,Predicted_negative
|
||||||
|
Actual_neutral,427,5,51
|
||||||
|
Actual_positive,75,172,51
|
||||||
|
Actual_negative,26,1,487
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,4 @@
|
||||||
|
,precision,recall,f1-score,support
|
||||||
|
Netral,0.9615384615384616,0.35046728971962615,0.5136986301369864,214.0
|
||||||
|
Positif,0.7506426735218509,0.9977220956719818,0.8567237163814181,878.0
|
||||||
|
Negatif,0.9523809523809523,0.43010752688172044,0.5925925925925926,279.0
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
,precision,recall,f1-score,support
|
||||||
|
Netral,0.8396349413298566,0.9456681350954479,0.8895027624309392,681.0
|
||||||
|
Positif,1.0,0.07920792079207921,0.14678899082568808,101.0
|
||||||
|
Negatif,0.869639794168096,0.8802083333333334,0.8748921484037964,576.0
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
,precision,recall,f1-score,support
|
||||||
|
Netral,0.8087121212121212,0.8840579710144928,0.8447082096933729,483.0
|
||||||
|
Positif,0.9662921348314607,0.5771812080536913,0.7226890756302521,298.0
|
||||||
|
Negatif,0.8268251273344652,0.9474708171206225,0.8830462375339981,514.0
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
rank,neutral,positive,negative
|
||||||
|
1,dana,dana,dana
|
||||||
|
2,baik,banget,tipu
|
||||||
|
3,admin,transaksi,admin
|
||||||
|
4,cek,aman,sekarang
|
||||||
|
5,kak,jadi,pakai
|
||||||
|
6,promo,bikin,pernah
|
||||||
|
7,pakai,baik,palsu
|
||||||
|
8,kasih,promo,sama
|
||||||
|
9,biar,selalu,ribet
|
||||||
|
10,buat,pakai,tagih
|
||||||
|
11,mantul,voucer,kendala
|
||||||
|
12,thanks,semangat,kasih
|
||||||
|
13,ulang,mantap,transaksi
|
||||||
|
14,big,kasih,was
|
||||||
|
15,ewallet,seru,aman
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
rank,neutral,positive,negative
|
||||||
|
1,cek,kakak,gopay
|
||||||
|
2,admin,gopay,blokir
|
||||||
|
3,kakak,sama,aku
|
||||||
|
4,gopay,sekali,kakak
|
||||||
|
5,halo,makin,saldo
|
||||||
|
6,saldo,pakai,sama
|
||||||
|
7,masuk,jadi,pakai
|
||||||
|
8,mohon,promo,admin
|
||||||
|
9,bantu,aku,bayar
|
||||||
|
10,aku,hemat,uang
|
||||||
|
11,terima,plissss,padahal
|
||||||
|
12,bayar,keren,sekarang
|
||||||
|
13,pakai,belanja,gimana
|
||||||
|
14,kendala,murah,hari
|
||||||
|
15,gopaylater,mantap,kok
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
rank,neutral,positive,negative
|
||||||
|
1,kak,aku,aku
|
||||||
|
2,cek,kak,admin
|
||||||
|
3,kendala,qris,masuk
|
||||||
|
4,aku,mau,mau
|
||||||
|
5,admin,mudah,tidak
|
||||||
|
6,kamu,fitur,error
|
||||||
|
7,shopeepay,buat,saldo
|
||||||
|
8,kakak,sih,jam
|
||||||
|
9,masuk,cepat,lama
|
||||||
|
10,maaf,bayar,sekali
|
||||||
|
11,kait,shopeepay,shopeepay
|
||||||
|
12,bantu,favorit,gimana
|
||||||
|
13,buat,aman,apa
|
||||||
|
14,mohon,selalu,banget
|
||||||
|
15,saldo,kendala,shopee
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -8,17 +8,17 @@
|
||||||
// Define CSV files and their identifiers (from public/storage folder)
|
// Define CSV files and their identifiers (from public/storage folder)
|
||||||
$datasets = [
|
$datasets = [
|
||||||
'dana' => [
|
'dana' => [
|
||||||
'path' => public_path('storage/data_labeled.csv'),
|
'path' => base_path('resources/views/data/data_labeled.csv'),
|
||||||
'title' => 'Dana',
|
'title' => 'Dana',
|
||||||
'anchor' => 'dana',
|
'anchor' => 'dana',
|
||||||
],
|
],
|
||||||
'gopay' => [
|
'gopay' => [
|
||||||
'path' => public_path('storage/data_labeledgopay.csv'),
|
'path' => base_path('resources/views/data/data_labeledgopay.csv'),
|
||||||
'title' => 'GoPay',
|
'title' => 'GoPay',
|
||||||
'anchor' => 'gopay',
|
'anchor' => 'gopay',
|
||||||
],
|
],
|
||||||
'shopee' => [
|
'shopee' => [
|
||||||
'path' => public_path('storage/data_labeledshopee.csv'),
|
'path' => base_path('resources/views/data/data_labeledshopee.csv'),
|
||||||
'title' => 'ShopeePay',
|
'title' => 'ShopeePay',
|
||||||
'anchor' => 'shopeepay',
|
'anchor' => 'shopeepay',
|
||||||
],
|
],
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,132 +1,686 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
|
|
||||||
<title>Laravel</title>
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Dashboard Sentimen E-Wallet</title>
|
||||||
|
|
||||||
<!-- Fonts -->
|
<!-- Fonts & Icons -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link href="{{ asset('assets/css/nucleo-icons.css') }}" rel="stylesheet" />
|
||||||
|
<link href="{{ asset('assets/css/nucleo-svg.css') }}" rel="stylesheet" />
|
||||||
|
<script src="https://kit.fontawesome.com/42d5adcbca.js" crossorigin="anonymous"></script>
|
||||||
|
<link href="{{ asset('assets/css/soft-ui-dashboard.css?v=1.0.3') }}" rel="stylesheet" />
|
||||||
|
|
||||||
<!-- Styles -->
|
<!-- WordCloud2.js -->
|
||||||
<style>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/wordcloud2.js/1.0.2/wordcloud2.min.js"></script>
|
||||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}[hidden]{display:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}a{color:inherit;text-decoration:inherit}svg,video{display:block;vertical-align:middle}video{max-width:100%;height:auto}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-100{--bg-opacity:1;background-color:#f7fafc;background-color:rgba(247,250,252,var(--bg-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-t{border-top-width:1px}.flex{display:flex}.grid{display:grid}.hidden{display:none}.items-center{align-items:center}.justify-center{justify-content:center}.font-semibold{font-weight:600}.h-5{height:1.25rem}.h-8{height:2rem}.h-16{height:4rem}.text-sm{font-size:.875rem}.text-lg{font-size:1.125rem}.leading-7{line-height:1.75rem}.mx-auto{margin-left:auto;margin-right:auto}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.mt-4{margin-top:1rem}.ml-4{margin-left:1rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-6xl{max-width:72rem}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.p-6{padding:1.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-8{padding-top:2rem}.fixed{position:fixed}.relative{position:relative}.top-0{top:0}.right-0{right:0}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.text-center{text-align:center}.text-gray-200{--text-opacity:1;color:#edf2f7;color:rgba(237,242,247,var(--text-opacity))}.text-gray-300{--text-opacity:1;color:#e2e8f0;color:rgba(226,232,240,var(--text-opacity))}.text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#a0aec0;color:rgba(160,174,192,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#4a5568;color:rgba(74,85,104,var(--text-opacity))}.text-gray-900{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.w-5{width:1.25rem}.w-8{width:2rem}.w-auto{width:auto}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}@media (min-width:640px){.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:h-20{height:5rem}.sm\:ml-0{margin-left:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-left{text-align:left}.sm\:text-right{text-align:right}}@media (min-width:768px){.md\:border-t-0{border-top-width:0}.md\:border-l{border-left-width:1px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{--bg-opacity:1;background-color:#2d3748;background-color:rgba(45,55,72,var(--bg-opacity))}.dark\:bg-gray-900{--bg-opacity:1;background-color:#1a202c;background-color:rgba(26,32,44,var(--bg-opacity))}.dark\:border-gray-700{--border-opacity:1;border-color:#4a5568;border-color:rgba(74,85,104,var(--border-opacity))}.dark\:text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.dark\:text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}.dark\:text-gray-500{--tw-text-opacity:1;color:#6b7280;color:rgba(107,114,128,var(--tw-text-opacity))}}
|
|
||||||
</style>
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Nunito', sans-serif;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header h6 {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counts .card {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pastikan canvas wordcloud tidak overflow */
|
||||||
|
.wc-canvas {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="g-sidenav-show bg-gray-100">
|
||||||
|
{{-- ====================== --}}
|
||||||
|
{{-- Navbar --}}
|
||||||
|
{{-- ====================== --}}
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow rounded py-3">
|
||||||
|
<div class="container">
|
||||||
|
{{-- Brand --}}
|
||||||
|
<a class="navbar-brand fs-4 fw-bold text-primary" href="{{ url('/') }}">
|
||||||
|
Sentimen E-Wallet
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{{-- Toggle (mobile) --}}
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent"
|
||||||
|
aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{{-- Links --}}
|
||||||
|
<div class="collapse navbar-collapse justify-content-end" id="navbarContent">
|
||||||
|
<ul class="navbar-nav align-items-center">
|
||||||
|
@guest
|
||||||
|
<li class="nav-item me-2">
|
||||||
|
<a class="nav-link btn btn-sm btn-primary px-3 text-white d-flex align-items-center"
|
||||||
|
href="{{ route('login') }}">
|
||||||
|
<i class="fa fa-sign-in-alt me-1"></i>
|
||||||
|
<span class="fw-medium">Login</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@endguest
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: 'Nunito', sans-serif;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="antialiased">
|
|
||||||
<div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center py-4 sm:pt-0">
|
|
||||||
@if (Route::has('login'))
|
|
||||||
<div class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
|
|
||||||
@auth
|
@auth
|
||||||
<a href="{{ url('/home') }}" class="text-sm text-gray-700 dark:text-gray-500 underline">Home</a>
|
<li class="nav-item me-2">
|
||||||
@else
|
<a class="nav-link btn btn-sm btn-outline-secondary px-3 d-flex align-items-center"
|
||||||
<a href="{{ route('login') }}" class="text-sm text-gray-700 dark:text-gray-500 underline">Log in</a>
|
href="{{ route('logout') }}"
|
||||||
|
onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
|
||||||
@if (Route::has('register'))
|
<i class="fa fa-sign-out-alt me-1"></i>
|
||||||
<a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 dark:text-gray-500 underline">Register</a>
|
<span class="fw-medium">Logout</span>
|
||||||
@endif
|
</a>
|
||||||
|
<form id="logout-form" action="{{ route('logout') }}" method="GET" class="d-none"></form>
|
||||||
|
</li>
|
||||||
@endauth
|
@endauth
|
||||||
</div>
|
</ul>
|
||||||
@endif
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
|
<div class="container py-4">
|
||||||
<div class="flex justify-center pt-8 sm:justify-start sm:pt-0">
|
|
||||||
<svg viewBox="0 0 651 192" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-16 w-auto text-gray-700 sm:h-20">
|
|
||||||
<g clip-path="url(#clip0)" fill="#EF3B2D">
|
|
||||||
<path d="M248.032 44.676h-16.466v100.23h47.394v-14.748h-30.928V44.676zM337.091 87.202c-2.101-3.341-5.083-5.965-8.949-7.875-3.865-1.909-7.756-2.864-11.669-2.864-5.062 0-9.69.931-13.89 2.792-4.201 1.861-7.804 4.417-10.811 7.661-3.007 3.246-5.347 6.993-7.016 11.239-1.672 4.249-2.506 8.713-2.506 13.389 0 4.774.834 9.26 2.506 13.459 1.669 4.202 4.009 7.925 7.016 11.169 3.007 3.246 6.609 5.799 10.811 7.66 4.199 1.861 8.828 2.792 13.89 2.792 3.913 0 7.804-.955 11.669-2.863 3.866-1.908 6.849-4.533 8.949-7.875v9.021h15.607V78.182h-15.607v9.02zm-1.431 32.503c-.955 2.578-2.291 4.821-4.009 6.73-1.719 1.91-3.795 3.437-6.229 4.582-2.435 1.146-5.133 1.718-8.091 1.718-2.96 0-5.633-.572-8.019-1.718-2.387-1.146-4.438-2.672-6.156-4.582-1.719-1.909-3.032-4.152-3.938-6.73-.909-2.577-1.36-5.298-1.36-8.161 0-2.864.451-5.585 1.36-8.162.905-2.577 2.219-4.819 3.938-6.729 1.718-1.908 3.77-3.437 6.156-4.582 2.386-1.146 5.059-1.718 8.019-1.718 2.958 0 5.656.572 8.091 1.718 2.434 1.146 4.51 2.674 6.229 4.582 1.718 1.91 3.054 4.152 4.009 6.729.953 2.577 1.432 5.298 1.432 8.162-.001 2.863-.479 5.584-1.432 8.161zM463.954 87.202c-2.101-3.341-5.083-5.965-8.949-7.875-3.865-1.909-7.756-2.864-11.669-2.864-5.062 0-9.69.931-13.89 2.792-4.201 1.861-7.804 4.417-10.811 7.661-3.007 3.246-5.347 6.993-7.016 11.239-1.672 4.249-2.506 8.713-2.506 13.389 0 4.774.834 9.26 2.506 13.459 1.669 4.202 4.009 7.925 7.016 11.169 3.007 3.246 6.609 5.799 10.811 7.66 4.199 1.861 8.828 2.792 13.89 2.792 3.913 0 7.804-.955 11.669-2.863 3.866-1.908 6.849-4.533 8.949-7.875v9.021h15.607V78.182h-15.607v9.02zm-1.432 32.503c-.955 2.578-2.291 4.821-4.009 6.73-1.719 1.91-3.795 3.437-6.229 4.582-2.435 1.146-5.133 1.718-8.091 1.718-2.96 0-5.633-.572-8.019-1.718-2.387-1.146-4.438-2.672-6.156-4.582-1.719-1.909-3.032-4.152-3.938-6.73-.909-2.577-1.36-5.298-1.36-8.161 0-2.864.451-5.585 1.36-8.162.905-2.577 2.219-4.819 3.938-6.729 1.718-1.908 3.77-3.437 6.156-4.582 2.386-1.146 5.059-1.718 8.019-1.718 2.958 0 5.656.572 8.091 1.718 2.434 1.146 4.51 2.674 6.229 4.582 1.718 1.91 3.054 4.152 4.009 6.729.953 2.577 1.432 5.298 1.432 8.162 0 2.863-.479 5.584-1.432 8.161zM650.772 44.676h-15.606v100.23h15.606V44.676zM365.013 144.906h15.607V93.538h26.776V78.182h-42.383v66.724zM542.133 78.182l-19.616 51.096-19.616-51.096h-15.808l25.617 66.724h19.614l25.617-66.724h-15.808zM591.98 76.466c-19.112 0-34.239 15.706-34.239 35.079 0 21.416 14.641 35.079 36.239 35.079 12.088 0 19.806-4.622 29.234-14.688l-10.544-8.158c-.006.008-7.958 10.449-19.832 10.449-13.802 0-19.612-11.127-19.612-16.884h51.777c2.72-22.043-11.772-40.877-33.023-40.877zm-18.713 29.28c.12-1.284 1.917-16.884 18.589-16.884 16.671 0 18.697 15.598 18.813 16.884h-37.402zM184.068 43.892c-.024-.088-.073-.165-.104-.25-.058-.157-.108-.316-.191-.46-.056-.097-.137-.176-.203-.265-.087-.117-.161-.242-.265-.345-.085-.086-.194-.148-.29-.223-.109-.085-.206-.182-.327-.252l-.002-.001-.002-.002-35.648-20.524a2.971 2.971 0 00-2.964 0l-35.647 20.522-.002.002-.002.001c-.121.07-.219.167-.327.252-.096.075-.205.138-.29.223-.103.103-.178.228-.265.345-.066.089-.147.169-.203.265-.083.144-.133.304-.191.46-.031.085-.08.162-.104.25-.067.249-.103.51-.103.776v38.979l-29.706 17.103V24.493a3 3 0 00-.103-.776c-.024-.088-.073-.165-.104-.25-.058-.157-.108-.316-.191-.46-.056-.097-.137-.176-.203-.265-.087-.117-.161-.242-.265-.345-.085-.086-.194-.148-.29-.223-.109-.085-.206-.182-.327-.252l-.002-.001-.002-.002L40.098 1.396a2.971 2.971 0 00-2.964 0L1.487 21.919l-.002.002-.002.001c-.121.07-.219.167-.327.252-.096.075-.205.138-.29.223-.103.103-.178.228-.265.345-.066.089-.147.169-.203.265-.083.144-.133.304-.191.46-.031.085-.08.162-.104.25-.067.249-.103.51-.103.776v122.09c0 1.063.568 2.044 1.489 2.575l71.293 41.045c.156.089.324.143.49.202.078.028.15.074.23.095a2.98 2.98 0 001.524 0c.069-.018.132-.059.2-.083.176-.061.354-.119.519-.214l71.293-41.045a2.971 2.971 0 001.489-2.575v-38.979l34.158-19.666a2.971 2.971 0 001.489-2.575V44.666a3.075 3.075 0 00-.106-.774zM74.255 143.167l-29.648-16.779 31.136-17.926.001-.001 34.164-19.669 29.674 17.084-21.772 12.428-43.555 24.863zm68.329-76.259v33.841l-12.475-7.182-17.231-9.92V49.806l12.475 7.182 17.231 9.92zm2.97-39.335l29.693 17.095-29.693 17.095-29.693-17.095 29.693-17.095zM54.06 114.089l-12.475 7.182V46.733l17.231-9.92 12.475-7.182v74.537l-17.231 9.921zM38.614 7.398l29.693 17.095-29.693 17.095L8.921 24.493 38.614 7.398zM5.938 29.632l12.475 7.182 17.231 9.92v79.676l.001.005-.001.006c0 .114.032.221.045.333.017.146.021.294.059.434l.002.007c.032.117.094.222.14.334.051.124.088.255.156.371a.036.036 0 00.004.009c.061.105.149.191.222.288.081.105.149.22.244.314l.008.01c.084.083.19.142.284.215.106.083.202.178.32.247l.013.005.011.008 34.139 19.321v34.175L5.939 144.867V29.632h-.001zm136.646 115.235l-65.352 37.625V148.31l48.399-27.628 16.953-9.677v33.862zm35.646-61.22l-29.706 17.102V66.908l17.231-9.92 12.475-7.182v33.841z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-8 bg-white dark:bg-gray-800 overflow-hidden shadow sm:rounded-lg">
|
<div class="row mb-4">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2">
|
<div class="col-12 d-flex justify-content-end mb-3">
|
||||||
<div class="p-6">
|
<select id="walletFilter" class="form-select form-select-sm w-auto" style="min-width: 140px;">
|
||||||
<div class="flex items-center">
|
<option value="all">Semua</option>
|
||||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg>
|
<option value="dana">Dana</option>
|
||||||
<div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laravel.com/docs" class="underline text-gray-900 dark:text-white">Documentation</a></div>
|
<option value="gopay">GoPay</option>
|
||||||
</div>
|
<option value="shopeepay">ShopeePay</option>
|
||||||
|
</select>
|
||||||
<div class="ml-12">
|
</div>
|
||||||
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
|
<div class="row gx-3 counts">
|
||||||
Laravel has wonderful, thorough documentation covering every aspect of the framework. Whether you are new to the framework or have previous experience with Laravel, we recommend reading all of the documentation from beginning to end.
|
@foreach (['positif' => 'bg-gradient-success', 'netral' => 'bg-gradient-warning', 'negatif' => 'bg-gradient-danger'] as $sent => $badge)
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card {{ $badge }} shadow">
|
||||||
|
<div class="card-body d-flex justify-content-between align-items-center p-4">
|
||||||
|
<div>
|
||||||
|
<p class="mb-1 text-sm text-uppercase opacity-7">{{ 'Total ' . ucfirst($sent) }}</p>
|
||||||
|
<h4 id="{{ $sent }}Count" class="mb-0 fw-bold text-white">
|
||||||
|
{{ $counts['all'][$sent] }}
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="icon icon-lg d-flex justify-content-center align-items-center">
|
||||||
</div>
|
<i
|
||||||
|
class="fa-solid fa-face-{{ $sent === 'positif' ? 'smile' : 'frown' }} fa-2x text-black"></i>
|
||||||
<div class="p-6 border-t border-gray-200 dark:border-gray-700 md:border-t-0 md:border-l">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path><path d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
|
||||||
<div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laracasts.com" class="underline text-gray-900 dark:text-white">Laracasts</a></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ml-12">
|
|
||||||
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
|
|
||||||
Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript development. Check them out, see for yourself, and massively level up your development skills in the process.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-6 border-t border-gray-200 dark:border-gray-700">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"></path></svg>
|
|
||||||
<div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laravel-news.com/" class="underline text-gray-900 dark:text-white">Laravel News</a></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ml-12">
|
|
||||||
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
|
|
||||||
Laravel News is a community driven portal and newsletter aggregating all of the latest and most important news in the Laravel ecosystem, including new package releases and tutorials.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-6 border-t border-gray-200 dark:border-gray-700 md:border-l">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
||||||
<div class="ml-4 text-lg leading-7 font-semibold text-gray-900 dark:text-white">Vibrant Ecosystem</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ml-12">
|
|
||||||
<div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
|
|
||||||
Laravel's robust library of first-party tools and libraries, such as <a href="https://forge.laravel.com" class="underline">Forge</a>, <a href="https://vapor.laravel.com" class="underline">Vapor</a>, <a href="https://nova.laravel.com" class="underline">Nova</a>, and <a href="https://envoyer.io" class="underline">Envoyer</a> help you take your projects to the next level. Pair them with powerful open source libraries like <a href="https://laravel.com/docs/billing" class="underline">Cashier</a>, <a href="https://laravel.com/docs/dusk" class="underline">Dusk</a>, <a href="https://laravel.com/docs/broadcasting" class="underline">Echo</a>, <a href="https://laravel.com/docs/horizon" class="underline">Horizon</a>, <a href="https://laravel.com/docs/sanctum" class="underline">Sanctum</a>, <a href="https://laravel.com/docs/telescope" class="underline">Telescope</a>, and more.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center mt-4 sm:items-center sm:justify-between">
|
{{-- ====================== --}}
|
||||||
<div class="text-center text-sm text-gray-500 sm:text-left">
|
{{-- Section: Charts Row --}}
|
||||||
<div class="flex items-center">
|
{{-- ====================== --}}
|
||||||
<svg fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor" class="-mt-px w-5 h-5 text-gray-400">
|
<div class="row gx-3 mb-4">
|
||||||
<path d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
|
<div class="col-lg-5 mb-3">
|
||||||
</svg>
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-header">
|
||||||
<a href="https://laravel.bigcartel.com" class="ml-1 underline">
|
<h6 class="mb-0">Perbandingan Sentimen E-Wallet</h6>
|
||||||
Shop
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="ml-4 -mt-px w-5 h-5 text-gray-400">
|
|
||||||
<path d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<a href="https://github.com/sponsors/taylorotwell" class="ml-1 underline">
|
|
||||||
Sponsor
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-body p-3" style="height: 300px;">
|
||||||
<div class="ml-4 text-center text-sm text-gray-500 sm:text-right sm:ml-0">
|
<canvas id="chart-bars" class="w-100 h-100"></canvas>
|
||||||
Laravel v{{ Illuminate\Foundation\Application::VERSION }} (PHP v{{ PHP_VERSION }})
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-7 mb-3">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6 class="mb-0">Tren Sentimen E-Wallet</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-3" style="height: 300px;">
|
||||||
|
<canvas id="chart-line" class="w-100 h-100"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
|
||||||
|
{{-- ====================== --}}
|
||||||
|
{{-- Section: Pie + Table --}}
|
||||||
|
{{-- ====================== --}}
|
||||||
|
<div class="row gx-3 mb-4">
|
||||||
|
<div class="col-lg-5 mb-3">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h6 class="mb-0">Distribusi Persentase Sentimen</h6>
|
||||||
|
<select id="walletFilterPie" class="form-select form-select-sm w-auto"
|
||||||
|
style="min-width: 140px;">
|
||||||
|
<option value="all">Semua</option>
|
||||||
|
<option value="dana">Dana</option>
|
||||||
|
<option value="gopay">GoPay</option>
|
||||||
|
<option value="shopeepay">ShopeePay</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-3" style="height: 300px;">
|
||||||
|
<canvas id="chart-pie" class="w-100 h-100"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-7 mb-3">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6 class="mb-0">Perbandingan Sentimen Antar Brand</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-3 overflow-auto" style="max-height: 300px;">
|
||||||
|
<table class="table table-striped align-middle mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>E-Wallet</th>
|
||||||
|
<th>Positif</th>
|
||||||
|
<th>Netral</th>
|
||||||
|
<th>Negatif</th>
|
||||||
|
<th>Net Score</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (['Dana' => 'dana', 'GoPay' => 'gopay', 'ShopeePay' => 'shopeepay'] as $label => $key)
|
||||||
|
@php
|
||||||
|
$d = $counts[$key];
|
||||||
|
$net = (($d['positif'] - $d['negatif']) / max(1, array_sum($d))) * 100;
|
||||||
|
@endphp
|
||||||
|
<tr>
|
||||||
|
<td><strong>{{ $label }}</strong></td>
|
||||||
|
<td>{{ $d['positif'] }}</td>
|
||||||
|
<td>{{ $d['netral'] }}</td>
|
||||||
|
<td>{{ $d['negatif'] }}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge {{ $net > 0 ? 'bg-success' : 'bg-danger' }}">
|
||||||
|
{{ number_format($net, 1) }}%
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ====================== --}}
|
||||||
|
{{-- Section: WordClouds --}}
|
||||||
|
{{-- ====================== --}}
|
||||||
|
<div class="row gx-3 mb-4">
|
||||||
|
<div class="col-12 mb-2 d-flex justify-content-center">
|
||||||
|
<select id="sourceSelect" class="form-select w-auto" style="min-width: 140px;">
|
||||||
|
<option value="all">Gabungan</option>
|
||||||
|
<option value="dana">Dana</option>
|
||||||
|
<option value="gopay">GoPay</option>
|
||||||
|
<option value="shopeepay">ShopeePay</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
@foreach (['positif', 'netral', 'negatif'] as $s)
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h6 class="mb-0">{{ ucfirst($s) }}</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0 d-flex justify-content-center align-items-center"
|
||||||
|
style="height: 250px;">
|
||||||
|
<canvas id="wc-{{ $s }}" class="wc-canvas"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- =========================================================== --}}
|
||||||
|
{{-- Section: Confusion Matrix (Tailwind-like styling via utility) --}}
|
||||||
|
{{-- =========================================================== --}}
|
||||||
|
<div class="row gx-3 mb-4">
|
||||||
|
@foreach (['dana', 'gopay', 'shopeepay'] as $key)
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<h6 class="mb-0 text-capitalize">{{ $key }} Confusion Matrix</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-3">
|
||||||
|
@php
|
||||||
|
$confusionPath = storage_path("app/public/confusion_matrix_{$key}.csv");
|
||||||
|
$confusionData = [];
|
||||||
|
if (file_exists($confusionPath)) {
|
||||||
|
$confusionData = array_map('str_getcsv', file($confusionPath));
|
||||||
|
}
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@if (count($confusionData) > 1)
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-sm text-center mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th class="text-start">Actual\Predicted</th>
|
||||||
|
@foreach (array_slice($confusionData[0], 1) as $header)
|
||||||
|
<th>{{ $header }}</th>
|
||||||
|
@endforeach
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (array_slice($confusionData, 1) as $rowIndex => $row)
|
||||||
|
<tr class="{{ $rowIndex % 2 === 0 ? '' : 'table-light' }}">
|
||||||
|
<th class="text-start">{{ $row[0] }}</th>
|
||||||
|
@foreach (array_slice($row, 1) as $colIndex => $cell)
|
||||||
|
@php
|
||||||
|
$isDiagonal = $rowIndex === $colIndex;
|
||||||
|
@endphp
|
||||||
|
<td
|
||||||
|
class="{{ $isDiagonal ? 'bg-dark text-white fw-bold' : '' }}">
|
||||||
|
{{ $cell }}
|
||||||
|
</td>
|
||||||
|
@endforeach
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<p class="text-center text-muted mb-0">
|
||||||
|
File <code>confusion_matrix_{{ $key }}.csv</code> tidak ditemukan atau
|
||||||
|
kosong.
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ====================================== --}}
|
||||||
|
{{-- Section: Metrics Chart (Precision, F1) --}}
|
||||||
|
{{-- ====================================== --}}
|
||||||
|
@php
|
||||||
|
$wallets = ['dana', 'gopay', 'shopeepay'];
|
||||||
|
$metricsDetail = [];
|
||||||
|
$kelasUrut = ['netral', 'positif', 'negatif'];
|
||||||
|
|
||||||
|
foreach ($wallets as $key) {
|
||||||
|
$fileKey = $key;
|
||||||
|
$pathCsv = storage_path("app/public/evaluation_metrics_full{$fileKey}.csv");
|
||||||
|
$rows = [];
|
||||||
|
if (file_exists($pathCsv)) {
|
||||||
|
$rows = array_map('str_getcsv', file($pathCsv));
|
||||||
|
}
|
||||||
|
|
||||||
|
$metricsDetail[$key] = [
|
||||||
|
'labels' => [],
|
||||||
|
'precision' => [],
|
||||||
|
'recall' => [],
|
||||||
|
'f1' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (count($rows) > 1) {
|
||||||
|
$mapRowByKelas = [];
|
||||||
|
foreach (array_slice($rows, 1) as $r) {
|
||||||
|
$mapRowByKelas[strtolower(trim($r[0]))] = $r;
|
||||||
|
}
|
||||||
|
foreach ($kelasUrut as $kelas) {
|
||||||
|
if (isset($mapRowByKelas[$kelas])) {
|
||||||
|
$r = $mapRowByKelas[$kelas];
|
||||||
|
$precision = isset($r[1]) ? (float) $r[1] : 0;
|
||||||
|
$recall = isset($r[2]) ? (float) $r[2] : 0;
|
||||||
|
$f1score = isset($r[3]) ? (float) $r[3] : 0;
|
||||||
|
$metricsDetail[$key]['labels'][] = ucfirst($kelas);
|
||||||
|
$metricsDetail[$key]['precision'][] = round($precision, 3);
|
||||||
|
$metricsDetail[$key]['recall'][] = round($recall, 3);
|
||||||
|
$metricsDetail[$key]['f1'][] = round($f1score, 3);
|
||||||
|
} else {
|
||||||
|
$metricsDetail[$key]['labels'][] = ucfirst($kelas);
|
||||||
|
$metricsDetail[$key]['precision'][] = 0;
|
||||||
|
$metricsDetail[$key]['recall'][] = 0;
|
||||||
|
$metricsDetail[$key]['f1'][] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="row gx-3 mb-4">
|
||||||
|
@foreach ($wallets as $key)
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<h6 class="mb-0 text-capitalize">Metrik Klasifikasi: {{ ucfirst($key) }}</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-3" style="height: 300px;">
|
||||||
|
<canvas id="chart-{{ $key }}" class="w-100 h-100"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ====================================== --}}
|
||||||
|
{{-- Section: Top Features per e-Wallet --}}
|
||||||
|
{{-- ====================================== --}}
|
||||||
|
<div class="row gx-3 mb-4">
|
||||||
|
@foreach (['dana', 'gopay', 'shopeepay'] as $key)
|
||||||
|
@php
|
||||||
|
$featuresPath = storage_path("app/public/top_features_{$key}.csv");
|
||||||
|
$featuresRaw = [];
|
||||||
|
if (file_exists($featuresPath)) {
|
||||||
|
$featuresRaw = array_map('str_getcsv', file($featuresPath));
|
||||||
|
}
|
||||||
|
$classFeatures = ['neutral' => [], 'positive' => [], 'negative' => []];
|
||||||
|
if (count($featuresRaw) > 1) {
|
||||||
|
foreach (array_slice($featuresRaw, 1) as $row) {
|
||||||
|
$classFeatures['neutral'][] = $row[1] ?? '';
|
||||||
|
$classFeatures['positive'][] = $row[2] ?? '';
|
||||||
|
$classFeatures['negative'][] = $row[3] ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="col-lg-4 col-md-6 mb-3">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-header bg-secondary text-white">
|
||||||
|
<h6 class="mb-0 text-uppercase">{{ ucfirst($key) }} Top Features</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-3">
|
||||||
|
@if (count($featuresRaw) > 1)
|
||||||
|
<div class="row">
|
||||||
|
@foreach (['neutral' => 'Netral', 'positive' => 'Positif', 'negative' => 'Negatif'] as $clsKey => $clsLabel)
|
||||||
|
<div class="col-4">
|
||||||
|
<h6 class="text-secondary text-uppercase text-center small">
|
||||||
|
{{ $clsLabel }}</h6>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
@foreach ($classFeatures[$clsKey] as $feature)
|
||||||
|
<li class="list-group-item py-1 small">{{ $feature }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<p class="text-center text-muted mb-0 small">
|
||||||
|
File <code>top_features_{{ $key }}.csv</code> tidak ditemukan atau kosong.
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ====================== --}}
|
||||||
|
{{-- SCRIPTS --}}
|
||||||
|
{{-- ====================== --}}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const counts = @json($counts);
|
||||||
|
// Update summary cards
|
||||||
|
const upd = key => ['positif', 'netral', 'negatif']
|
||||||
|
.forEach(s => document.getElementById(s + 'Count').textContent = counts[key][s]);
|
||||||
|
document.getElementById('walletFilter').addEventListener('change', e => upd(e.target.value));
|
||||||
|
upd('all');
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// Chart.js v3+ Configs
|
||||||
|
// ======================
|
||||||
|
const COLORS = ['rgba(75, 192, 192, 0.6)', 'rgba(54, 162, 235, 0.6)', 'rgba(255, 206, 86, 0.6)'];
|
||||||
|
const BORDERS = ['rgba(75, 192, 192, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)'];
|
||||||
|
|
||||||
|
// Bar Chart: Perbandingan Sentimen
|
||||||
|
new Chart(document.getElementById('chart-bars').getContext('2d'), {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: ['Positif', 'Netral', 'Negatif'],
|
||||||
|
datasets: ['dana', 'gopay', 'shopeepay'].map((k, i) => ({
|
||||||
|
label: k.charAt(0).toUpperCase() + k.slice(1),
|
||||||
|
data: ['positif', 'netral', 'negatif'].map(s => counts[k][s]),
|
||||||
|
backgroundColor: COLORS[i],
|
||||||
|
borderColor: BORDERS[i],
|
||||||
|
borderWidth: 1
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: {
|
||||||
|
display: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'top'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Line Chart: Tren Sentimen
|
||||||
|
new Chart(document.getElementById('chart-line').getContext('2d'), {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: ['Dana', 'GoPay', 'ShopeePay'],
|
||||||
|
datasets: ['positif', 'netral', 'negatif'].map((s, i) => ({
|
||||||
|
label: s.charAt(0).toUpperCase() + s.slice(1),
|
||||||
|
data: ['dana', 'gopay', 'shopeepay'].map(k => counts[k][s]),
|
||||||
|
borderColor: BORDERS[i],
|
||||||
|
fill: false,
|
||||||
|
tension: 0.4,
|
||||||
|
pointBackgroundColor: BORDERS[i],
|
||||||
|
pointRadius: 4
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: {
|
||||||
|
display: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'top'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pie Chart: Distribusi Persentase
|
||||||
|
const pieCtx = document.getElementById('chart-pie').getContext('2d');
|
||||||
|
const pie = new Chart(pieCtx, {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: ['Positif', 'Netral', 'Negatif'],
|
||||||
|
datasets: [{
|
||||||
|
data: (() => {
|
||||||
|
const d = counts['all'];
|
||||||
|
const total = (d.positif + d.netral + d.negatif) || 1;
|
||||||
|
return [
|
||||||
|
((d.positif / total) * 100).toFixed(2),
|
||||||
|
((d.netral / total) * 100).toFixed(2),
|
||||||
|
((d.negatif / total) * 100).toFixed(2)
|
||||||
|
];
|
||||||
|
})(),
|
||||||
|
backgroundColor: COLORS,
|
||||||
|
borderColor: BORDERS,
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
formatter: (value) => value + '%',
|
||||||
|
color: '#fff',
|
||||||
|
font: {
|
||||||
|
weight: 'bold',
|
||||||
|
size: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'bottom'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [ChartDataLabels]
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('walletFilterPie').addEventListener('change', e => {
|
||||||
|
const key = e.target.value;
|
||||||
|
const d = counts[key];
|
||||||
|
const total = (d.positif + d.netral + d.negatif) || 1;
|
||||||
|
pie.data.datasets[0].data = [
|
||||||
|
((d.positif / total) * 100).toFixed(2),
|
||||||
|
((d.netral / total) * 100).toFixed(2),
|
||||||
|
((d.negatif / total) * 100).toFixed(2)
|
||||||
|
];
|
||||||
|
pie.update();
|
||||||
|
['positif', 'netral', 'negatif'].forEach(s => {
|
||||||
|
document.getElementById(s + 'Count').textContent = counts[key][s];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// WordClouds
|
||||||
|
// ======================
|
||||||
|
const sourceSel = document.getElementById('sourceSelect');
|
||||||
|
const sentiments = ['positif', 'netral', 'negatif'];
|
||||||
|
|
||||||
|
function drawWC(sentiment) {
|
||||||
|
const cvs = document.getElementById(`wc-${sentiment}`);
|
||||||
|
const origin = [cvs.width / 2, cvs.height / 2];
|
||||||
|
fetch(`{{ url('/wordcloud-data') }}?source=${sourceSel.value}&sentiment=${sentiment}`)
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(list => {
|
||||||
|
WordCloud(cvs, {
|
||||||
|
list,
|
||||||
|
clearCanvas: true,
|
||||||
|
weightFactor: 1,
|
||||||
|
gridSize: 2,
|
||||||
|
rotateRatio: 0.3,
|
||||||
|
backgroundColor: window.getComputedStyle(cvs).backgroundColor,
|
||||||
|
origin
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
cvs.getContext('2d').fillText('Gagal memuat wordcloud', cvs.width / 2 - 50, cvs.height /
|
||||||
|
2);
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sourceSel.addEventListener('change', () => sentiments.forEach(drawWC));
|
||||||
|
sentiments.forEach(drawWC);
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// Metrics Charts per Wallet
|
||||||
|
// ======================
|
||||||
|
const wallets = @json($wallets);
|
||||||
|
const dataPHP = @json($metricsDetail);
|
||||||
|
|
||||||
|
wallets.forEach(key => {
|
||||||
|
const ctx = document.getElementById('chart-' + key).getContext('2d');
|
||||||
|
const labels = dataPHP[key]['labels'];
|
||||||
|
const dataPrecision = dataPHP[key]['precision'];
|
||||||
|
const dataRecall = dataPHP[key]['recall'];
|
||||||
|
const dataF1 = dataPHP[key]['f1'];
|
||||||
|
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Precision',
|
||||||
|
data: dataPrecision,
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.5)',
|
||||||
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Recall',
|
||||||
|
data: dataRecall,
|
||||||
|
backgroundColor: 'rgba(255, 206, 86, 0.5)',
|
||||||
|
borderColor: 'rgba(255, 206, 86, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'F1-Score',
|
||||||
|
data: dataF1,
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.5)',
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 1,
|
||||||
|
ticks: {
|
||||||
|
callback: value => value.toFixed(2)
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Nilai'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: context => {
|
||||||
|
let label = context.dataset.label || '';
|
||||||
|
if (label) label += ': ';
|
||||||
|
if (context.parsed.y !== null) {
|
||||||
|
label += context.parsed.y.toFixed(3);
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'top'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -6,18 +6,53 @@ use App\Http\Controllers\InfoUserController;
|
||||||
use App\Http\Controllers\RegisterController;
|
use App\Http\Controllers\RegisterController;
|
||||||
use App\Http\Controllers\ResetController;
|
use App\Http\Controllers\ResetController;
|
||||||
use App\Http\Controllers\SessionsController;
|
use App\Http\Controllers\SessionsController;
|
||||||
|
use App\Http\Controllers\WelcomeController; // ← Pastikan di‐import
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Public Routes (Guest)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Untuk guest, root (/) akan men‐trigger WelcomeController@index,
|
||||||
|
| sehingga view 'welcome' menerima data $counts.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
Route::get('/wordcloud-data', [WelcomeController::class, 'wordcloudData'])
|
||||||
|
->name('wordcloud.data');
|
||||||
|
Route::group(['middleware' => 'guest'], function () {
|
||||||
|
// root → WelcomeController@index (mengirimkan $counts ke welcome.blade.php)
|
||||||
|
Route::get('/', [WelcomeController::class, 'index'])->name('home');
|
||||||
|
|
||||||
|
// Rute login / register / reset password
|
||||||
|
Route::get('/login', [SessionsController::class, 'create'])->name('login');
|
||||||
|
Route::post('/session', [SessionsController::class, 'store'])->name('session.store');
|
||||||
|
Route::get('/register', [RegisterController::class, 'create'])->name('register');
|
||||||
|
Route::post('/register', [RegisterController::class, 'store']);
|
||||||
|
Route::get('/login/forgot-password', [ResetController::class, 'create'])->name('password.request');
|
||||||
|
Route::post('/forgot-password', [ResetController::class, 'sendEmail'])->name('password.email');
|
||||||
|
Route::get('/reset-password/{token}', [ResetController::class, 'resetPass'])->name('password.reset');
|
||||||
|
Route::post('/reset-password', [ChangePasswordController::class, 'changePassword'])->name('password.update');
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Authenticated Routes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Setelah login, user diarahkan ke /dashboard dan route lain milik middleware 'auth'.
|
||||||
|
| Jika welcome.blade.php juga memerlukan wordcloud-data, pastikan URL‐nya sesuai.
|
||||||
|
|
|
||||||
|
*/
|
||||||
Route::group(['middleware' => 'auth'], function () {
|
Route::group(['middleware' => 'auth'], function () {
|
||||||
|
// dashboard → HomeController@index
|
||||||
|
Route::get('/dashboard', [HomeController::class, 'index'])->name('dashboard');
|
||||||
|
|
||||||
// dashboard → HomeController@index (formerly HomeController@home)
|
// // Jika WordCloud diakses dari welcome juga, pastikan route ke WelcomeController
|
||||||
Route::get('/', [HomeController::class, 'index'])->name('dashboard');
|
// Route::get('/wordcloud-data', [HomeController::class, 'wordcloudData'])
|
||||||
Route::get('/dashboard', [HomeController::class, 'index']);
|
// ->name('wordcloud.data');
|
||||||
|
|
||||||
// word-cloud JSON endpoint
|
// Halaman‐halaman lain
|
||||||
Route::get('/wordcloud-data', [HomeController::class, 'wordcloudData']);
|
|
||||||
|
|
||||||
// other authenticated routes…
|
|
||||||
Route::get('billing', fn() => view('billing'))->name('billing');
|
Route::get('billing', fn() => view('billing'))->name('billing');
|
||||||
Route::get('profile', fn() => view('profile'))->name('profile');
|
Route::get('profile', fn() => view('profile'))->name('profile');
|
||||||
Route::get('rtl', fn() => view('rtl'))->name('rtl');
|
Route::get('rtl', fn() => view('rtl'))->name('rtl');
|
||||||
|
@ -27,33 +62,10 @@ Route::group(['middleware' => 'auth'], function () {
|
||||||
Route::get('static-sign-in', fn() => view('static-sign-in'))->name('sign-in');
|
Route::get('static-sign-in', fn() => view('static-sign-in'))->name('sign-in');
|
||||||
Route::get('static-sign-up', fn() => view('static-sign-up'))->name('sign-up');
|
Route::get('static-sign-up', fn() => view('static-sign-up'))->name('sign-up');
|
||||||
|
|
||||||
Route::get('/logout', [SessionsController::class, 'destroy']);
|
// logout
|
||||||
Route::get('/data-sentimen', [InfoUserController::class, 'create']);
|
Route::get('/logout', [SessionsController::class, 'destroy'])->name('logout');
|
||||||
Route::post('/data-sentimen', [InfoUserController::class, 'store']);
|
|
||||||
|
// data-sentimen
|
||||||
|
Route::get('/data-sentimen', [InfoUserController::class, 'create'])->name('data.sentimen.create');
|
||||||
|
Route::post('/data-sentimen', [InfoUserController::class, 'store'])->name('data.sentimen.store');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['middleware' => 'guest'], function () {
|
|
||||||
// show login form
|
|
||||||
Route::get('/login', [SessionsController::class, 'create'])
|
|
||||||
->name('login');
|
|
||||||
|
|
||||||
// process login
|
|
||||||
Route::post('/session', [SessionsController::class, 'store'])
|
|
||||||
->name('session.store');
|
|
||||||
|
|
||||||
// registration
|
|
||||||
Route::get('/register', [RegisterController::class, 'create'])
|
|
||||||
->name('register');
|
|
||||||
Route::post('/register', [RegisterController::class, 'store']);
|
|
||||||
|
|
||||||
// password reset
|
|
||||||
Route::get('/login/forgot-password', [ResetController::class, 'create'])
|
|
||||||
->name('password.request');
|
|
||||||
Route::post('/forgot-password', [ResetController::class, 'sendEmail'])
|
|
||||||
->name('password.email');
|
|
||||||
Route::get('/reset-password/{token}', [ResetController::class, 'resetPass'])
|
|
||||||
->name('password.reset');
|
|
||||||
Route::post('/reset-password', [ChangePasswordController::class, 'changePassword'])
|
|
||||||
->name('password.update');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue