nambah menu profil tok
This commit is contained in:
parent
84b66d4465
commit
227aad6557
|
|
@ -29,7 +29,7 @@ public function index(Request $request)
|
|||
$query->where('wisata', 'LIKE', '%' . $request->wisata . '%');
|
||||
}
|
||||
|
||||
$mentah = $query->latest()->paginate(50)->withQueryString();
|
||||
$mentah = $query->latest()->paginate(20)->withQueryString();
|
||||
|
||||
// OPTIONAL
|
||||
$preprocessing = PreprocessingData::latest()->paginate(30, ['*'], 'pre_page');
|
||||
|
|
@ -37,7 +37,7 @@ public function index(Request $request)
|
|||
// 🔥 RINGKASAN DATA
|
||||
$totalUlasan = Ulasan::count();
|
||||
$totalWisata = Ulasan::distinct('wisata')->count('wisata');
|
||||
$lastUpdate = Ulasan::max('updated_at');
|
||||
$lastUpdate = \App\Models\Ulasan::latest()->value('created_at');
|
||||
|
||||
return view('ulasan.index', compact(
|
||||
'mentah',
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
|
|
@ -5,136 +5,311 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login - SENTARA</title>
|
||||
|
||||
@vite(['resources/css/app.css','resources/js/app.js'])
|
||||
<link rel="icon" type="image/png" href="{{ asset('favicon.png') }}">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
*{
|
||||
margin:0;
|
||||
padding:0;
|
||||
box-sizing:border-box;
|
||||
font-family:'Poppins',sans-serif;
|
||||
}
|
||||
|
||||
body{
|
||||
background:#f3f5fb;
|
||||
min-height:100vh;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
padding:40px;
|
||||
}
|
||||
|
||||
/* CONTAINER */
|
||||
.container{
|
||||
width:1350px;
|
||||
max-width:100%;
|
||||
height:720px;
|
||||
background:#fff;
|
||||
border-radius:25px;
|
||||
display:flex;
|
||||
overflow:hidden;
|
||||
box-shadow:0 30px 80px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* LEFT */
|
||||
.left{
|
||||
width:50%;
|
||||
padding:60px 80px;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
justify-content:center;
|
||||
}
|
||||
|
||||
/* LOGO */
|
||||
.logo{
|
||||
text-align:center;
|
||||
margin-bottom:25px;
|
||||
}
|
||||
|
||||
.logo img{
|
||||
width:200px;
|
||||
}
|
||||
|
||||
/* TITLE */
|
||||
.title{
|
||||
text-align:center;
|
||||
font-size:28px;
|
||||
font-weight:600;
|
||||
}
|
||||
|
||||
.title span{
|
||||
color:#ff5c8d;
|
||||
}
|
||||
|
||||
.subtitle{
|
||||
text-align:center;
|
||||
font-size:14px;
|
||||
color:#777;
|
||||
margin:12px 0 35px;
|
||||
}
|
||||
|
||||
/* FORM */
|
||||
.form-group{
|
||||
margin-bottom:20px;
|
||||
}
|
||||
|
||||
label{
|
||||
font-size:13px;
|
||||
font-weight:500;
|
||||
}
|
||||
|
||||
input{
|
||||
width:100%;
|
||||
padding:14px;
|
||||
border-radius:12px;
|
||||
border:1px solid #ddd;
|
||||
margin-top:6px;
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
.input-group{
|
||||
position:relative;
|
||||
}
|
||||
|
||||
.toggle{
|
||||
position:absolute;
|
||||
right:12px;
|
||||
top:14px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.forgot{
|
||||
text-align:right;
|
||||
margin-top:6px;
|
||||
}
|
||||
|
||||
.forgot a{
|
||||
font-size:12px;
|
||||
color:#2d5be3;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
/* BUTTON */
|
||||
.btn{
|
||||
width:100%;
|
||||
padding:14px;
|
||||
border:none;
|
||||
border-radius:12px;
|
||||
background:linear-gradient(90deg,#2d5be3,#4c7dff);
|
||||
color:#fff;
|
||||
font-weight:500;
|
||||
margin-top:20px;
|
||||
cursor:pointer;
|
||||
transition:0.3s;
|
||||
}
|
||||
|
||||
.btn:hover{
|
||||
opacity:0.9;
|
||||
}
|
||||
|
||||
.right{
|
||||
width:50%;
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
/* GAMBAR */
|
||||
.right img{
|
||||
position:absolute;
|
||||
top:0;
|
||||
right:0;
|
||||
width:100%;
|
||||
height:100%;
|
||||
object-fit:cover;
|
||||
|
||||
/* SHAPE DIPERBAIKI */
|
||||
clip-path: ellipse(120% 100% at 100% 50%);
|
||||
}
|
||||
|
||||
/* OVERLAY */
|
||||
.overlay{
|
||||
position:absolute;
|
||||
width:100%;
|
||||
height:100%;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
text-align:center;
|
||||
color:#fff;
|
||||
background:rgba(0,0,0,0.25);
|
||||
padding:20px;
|
||||
z-index:2; /* DI ATAS */
|
||||
}
|
||||
|
||||
/* QUOTE */
|
||||
.quote{
|
||||
font-size:42px;
|
||||
color:#ff5c8d;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
/* TEXT */
|
||||
.overlay p{
|
||||
font-size:30px;
|
||||
font-weight:500;
|
||||
line-height:1.5;
|
||||
max-width:420px;
|
||||
}
|
||||
|
||||
.overlay span{
|
||||
color:#ff5c8d;
|
||||
font-weight:600;
|
||||
}
|
||||
|
||||
/* GARIS */
|
||||
.line{
|
||||
width:70px;
|
||||
height:3px;
|
||||
background:#fff;
|
||||
border-radius:10px;
|
||||
margin-top:18px;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
.line::after{
|
||||
content:'';
|
||||
position:absolute;
|
||||
width:12px;
|
||||
height:12px;
|
||||
border:2px solid #fff;
|
||||
border-left:none;
|
||||
border-top:none;
|
||||
transform:rotate(-45deg);
|
||||
right:-12px;
|
||||
top:-5px;
|
||||
}
|
||||
|
||||
/* MOBILE */
|
||||
@media(max-width:900px){
|
||||
.container{
|
||||
flex-direction:column;
|
||||
height:auto;
|
||||
}
|
||||
|
||||
.left{
|
||||
width:100%;
|
||||
padding:40px;
|
||||
}
|
||||
|
||||
.right{
|
||||
width:100%;
|
||||
height:260px;
|
||||
}
|
||||
|
||||
.overlay p{
|
||||
font-size:20px;
|
||||
}
|
||||
|
||||
.logo img{
|
||||
width:150px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="min-h-screen flex items-center justify-center bg-gray-100 px-6">
|
||||
<body>
|
||||
|
||||
<!-- WRAPPER CARD -->
|
||||
<div class="w-full max-w-6xl bg-white rounded-3xl shadow-2xl overflow-hidden flex">
|
||||
<div class="container">
|
||||
|
||||
<!-- LEFT SIDE LOGIN -->
|
||||
<div class="w-1/2 flex flex-col justify-center px-14 py-16 bg-white">
|
||||
<!-- LEFT -->
|
||||
<div class="left">
|
||||
|
||||
<h1 class="text-4xl font-bold text-blue-900 mb-2">
|
||||
SENTARA
|
||||
</h1>
|
||||
<div class="logo">
|
||||
<img src="{{ asset('images/logo-sentara.png') }}">
|
||||
</div>
|
||||
|
||||
<p class="text-gray-500 mb-10">
|
||||
<div class="title">
|
||||
Selamat Datang <span>Kembali!</span>
|
||||
</div>
|
||||
|
||||
<div class="subtitle">
|
||||
Login untuk mengakses Dashboard Analisis Sentimen Wisata Jember
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form method="POST" action="{{ route('login') }}" class="space-y-6">
|
||||
<form method="POST" action="{{ route('login') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Email -->
|
||||
<div>
|
||||
<label class="block text-gray-700 font-medium mb-2">
|
||||
Email
|
||||
</label>
|
||||
<input type="email" name="email" required
|
||||
class="w-full border border-gray-300 rounded-xl px-4 py-3
|
||||
focus:ring-2 focus:ring-blue-400 focus:outline-none">
|
||||
<div class="form-group">
|
||||
<label>Email</label>
|
||||
<input type="email" name="email" placeholder="Masukkan email Anda" required>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div>
|
||||
<label class="block text-gray-700 font-medium mb-2">
|
||||
Password
|
||||
</label>
|
||||
<input type="password" name="password" required
|
||||
class="w-full border border-gray-300 rounded-xl px-4 py-3
|
||||
focus:ring-2 focus:ring-blue-400 focus:outline-none">
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="password" name="password" placeholder="Masukkan password Anda" required>
|
||||
<span class="toggle" onclick="togglePass()">👁</span>
|
||||
</div>
|
||||
|
||||
<!-- Button -->
|
||||
<button type="submit"
|
||||
class="w-full bg-blue-700 hover:bg-blue-800 text-white py-3
|
||||
rounded-xl font-semibold shadow-md transition">
|
||||
Login
|
||||
</button>
|
||||
<div class="forgot">
|
||||
<a href="{{ route('password.request') }}">Lupa password?</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn">Login</button>
|
||||
|
||||
<!-- Register Link -->
|
||||
<p class="text-sm text-gray-500 text-center">
|
||||
Belum punya akun?
|
||||
<a href="{{ route('register') }}"
|
||||
class="text-blue-700 font-semibold hover:underline">
|
||||
Register
|
||||
</a>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- RIGHT -->
|
||||
<div class="right">
|
||||
|
||||
<!-- RIGHT SIDE SLIDER -->
|
||||
<div class="w-1/2 relative overflow-hidden">
|
||||
<img src="{{ asset('images/papuma.jpg') }}" alt="bg">
|
||||
|
||||
<!-- Slides -->
|
||||
<img src="{{ asset('images/papuma.jpg') }}"
|
||||
class="slide absolute inset-0 w-full h-full object-cover">
|
||||
<div class="overlay">
|
||||
|
||||
<img src="{{ asset('images/watuulo.jpeg') }}"
|
||||
class="slide hidden absolute inset-0 w-full h-full object-cover">
|
||||
<p>
|
||||
Memahami opini,<br>
|
||||
meningkatkan <span>pariwisata</span><br>
|
||||
Jember.
|
||||
</p>
|
||||
|
||||
<img src="{{ asset('images/botani.jpg') }}"
|
||||
class="slide hidden absolute inset-0 w-full h-full object-cover">
|
||||
|
||||
<img src="{{ asset('images/gununggambir.jpeg') }}"
|
||||
class="slide hidden absolute inset-0 w-full h-full object-cover">
|
||||
|
||||
|
||||
<!-- Overlay -->
|
||||
<div class="absolute inset-0 bg-black/40 flex items-center justify-center">
|
||||
<h2 class="text-white text-3xl font-bold text-center px-10">
|
||||
Eksplorasi Wisata Kabupaten Jember
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- LEFT ARROW -->
|
||||
<button onclick="prevSlide()"
|
||||
class="absolute left-5 top-1/2 -translate-y-1/2
|
||||
bg-white/40 hover:bg-white/70 text-white
|
||||
px-4 py-2 rounded-full text-2xl font-bold transition">
|
||||
❮
|
||||
</button>
|
||||
|
||||
<!-- RIGHT ARROW -->
|
||||
<button onclick="nextSlide()"
|
||||
class="absolute right-5 top-1/2 -translate-y-1/2
|
||||
bg-white/40 hover:bg-white/70 text-white
|
||||
px-4 py-2 rounded-full text-2xl font-bold transition">
|
||||
❯
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- SLIDER SCRIPT -->
|
||||
<script>
|
||||
let index = 0;
|
||||
const slides = document.querySelectorAll(".slide");
|
||||
|
||||
function showSlide(i) {
|
||||
slides.forEach(slide => slide.classList.add("hidden"));
|
||||
slides[i].classList.remove("hidden");
|
||||
function togglePass(){
|
||||
let x = document.getElementById("password");
|
||||
x.type = x.type === "password" ? "text" : "password";
|
||||
}
|
||||
|
||||
function nextSlide() {
|
||||
index = (index + 1) % slides.length;
|
||||
showSlide(index);
|
||||
}
|
||||
|
||||
function prevSlide() {
|
||||
index = (index - 1 + slides.length) % slides.length;
|
||||
showSlide(index);
|
||||
}
|
||||
|
||||
// // Auto Slide setiap 3 detik
|
||||
// setInterval(nextSlide, 3000);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class="block hover:text-gray-300">
|
|||
|
||||
<!-- Page Content -->
|
||||
<main class="p-6">
|
||||
{{ $slot }}
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
</x-slot>
|
||||
|
||||
<x-slot name="content">
|
||||
<x-dropdown-link :href="route('profile.edit')">
|
||||
<x-dropdown-link :href="route('profile')">
|
||||
{{ __('Profile') }}
|
||||
</x-dropdown-link>
|
||||
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-3 space-y-1">
|
||||
<x-responsive-nav-link :href="route('profile.edit')">
|
||||
<x-responsive-nav-link :href="route('profile')">
|
||||
{{ __('Profile') }}
|
||||
</x-responsive-nav-link>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,61 +6,177 @@
|
|||
<title>SENTARA Dashboard</title>
|
||||
|
||||
@vite(['resources/css/app.css','resources/js/app.js'])
|
||||
|
||||
<script src="https://unpkg.com/feather-icons"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-100">
|
||||
|
||||
<!-- WRAPPER -->
|
||||
<div class="min-h-screen">
|
||||
|
||||
<!-- SIDEBAR FIX -->
|
||||
<aside class="w-64 fixed top-0 left-0 h-screen
|
||||
<!-- SIDEBAR -->
|
||||
<aside id="sidebar"
|
||||
class="fixed top-0 left-0 h-full w-64
|
||||
bg-gradient-to-b from-blue-900 to-blue-700
|
||||
text-white flex flex-col">
|
||||
text-white flex flex-col
|
||||
transform -translate-x-full md:translate-x-0
|
||||
transition duration-300 z-50">
|
||||
|
||||
<!-- Logo -->
|
||||
<div class="px-6 py-6 text-2xl font-bold tracking-wide">
|
||||
SENTARA
|
||||
<!-- LOGO -->
|
||||
<div class="flex justify-center mt-6 mb-8">
|
||||
<img src="{{ asset('images/tulisan-sentara.png') }}"
|
||||
class="w-44">
|
||||
</div>
|
||||
|
||||
<!-- Menu -->
|
||||
<nav class="flex-1 px-4 space-y-2 text-sm font-medium">
|
||||
<a href="{{ route('dashboard') }}" class="block px-4 py-2 rounded-lg hover:bg-blue-800">
|
||||
Dashboard Sentimen
|
||||
<!-- MENU -->
|
||||
<nav class="flex-1 px-6 space-y-3 text-sm font-medium">
|
||||
|
||||
<!-- DASHBOARD -->
|
||||
<a href="{{ route('dashboard') }}"
|
||||
class="flex items-center gap-4 px-4 py-3 rounded-xl hover:bg-blue-800 transition">
|
||||
|
||||
<div class="w-10 h-10 flex items-center justify-center bg-blue-800 rounded-lg">
|
||||
<i data-feather="grid" class="w-5 h-5"></i>
|
||||
</div>
|
||||
|
||||
<span class="text-[15px]">Dashboard Sentimen</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('ulasan.index') }}" class="block px-4 py-2 rounded-lg hover:bg-blue-800">
|
||||
Data Ulasan
|
||||
<!-- DATA ULASAN -->
|
||||
<a href="{{ route('ulasan.index') }}"
|
||||
class="flex items-center gap-4 px-4 py-3 rounded-xl hover:bg-blue-800 transition">
|
||||
|
||||
<div class="w-10 h-10 flex items-center justify-center bg-blue-800 rounded-lg">
|
||||
<i data-feather="file-text" class="w-5 h-5"></i>
|
||||
</div>
|
||||
|
||||
<span class="text-[15px]">Data Ulasan</span>
|
||||
</a>
|
||||
|
||||
<!-- <a href="{{ route('analisis.index') }}"
|
||||
class="block px-4 py-2 rounded-lg hover:bg-blue-800">
|
||||
Hasil Analisis Sentimen
|
||||
</a> -->
|
||||
|
||||
|
||||
<!-- <a href="{{ route('rekomendasi.index') }}"
|
||||
class="block px-4 py-2 rounded-lg hover:bg-blue-800">
|
||||
Rekomendasi Layanan
|
||||
</a> -->
|
||||
|
||||
</nav>
|
||||
|
||||
<!-- Logout -->
|
||||
<!-- USER SECTION -->
|
||||
<div class="px-6 pb-6">
|
||||
<form method="POST" action="{{ route('logout') }}"
|
||||
onsubmit="return confirm('Apakah Anda yakin ingin keluar?')">
|
||||
|
||||
<div class="relative">
|
||||
|
||||
<!-- USER BUTTON -->
|
||||
<button onclick="toggleUserMenu()"
|
||||
class="w-full flex items-center gap-3 bg-blue-800 hover:bg-blue-900 px-4 py-3 rounded-xl transition">
|
||||
|
||||
<div class="w-10 h-10 flex items-center justify-center bg-blue-700 rounded-full">
|
||||
<i data-feather="user" class="w-5 h-5"></i>
|
||||
</div>
|
||||
|
||||
<div class="text-left">
|
||||
<p class="text-sm font-semibold">
|
||||
{{ auth()->user()->name }}
|
||||
</p>
|
||||
<p class="text-xs text-blue-200">Staff</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<!-- DROPDOWN -->
|
||||
<div id="userMenu"
|
||||
class="hidden absolute bottom-16 left-0 w-full
|
||||
bg-white text-gray-700 rounded-xl shadow-lg overflow-hidden">
|
||||
|
||||
<!-- PROFILE -->
|
||||
<a href="{{ route('profile') }}"
|
||||
class="flex items-center gap-3 px-4 py-3 text-sm hover:bg-gray-100">
|
||||
<i data-feather="user" class="w-4 h-4"></i>
|
||||
Profile
|
||||
</a>
|
||||
|
||||
<!-- LOGOUT -->
|
||||
<form id="logoutForm" method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button class="w-full bg-blue-800 hover:bg-blue-900 px-4 py-2 rounded-lg">
|
||||
<button type="button"
|
||||
onclick="confirmLogout()"
|
||||
class="w-full flex items-center gap-3 px-4 py-3 text-sm hover:bg-gray-100">
|
||||
<i data-feather="log-out" class="w-4 h-4"></i>
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
<!-- CONTENT FIX -->
|
||||
<div class="ml-64 min-h-screen p-10">
|
||||
<!-- CONTENT -->
|
||||
<div class="flex-1 p-4 md:p-6 md:ml-64 ml-0">
|
||||
|
||||
<button onclick="toggleSidebar()"
|
||||
class="md:hidden mb-4 bg-blue-600 text-white px-4 py-2 rounded-lg shadow">
|
||||
☰
|
||||
</button>
|
||||
|
||||
@yield('content')
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- MODAL LOGOUT -->
|
||||
<div id="logoutModal"
|
||||
class="hidden fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center z-50">
|
||||
|
||||
<div class="bg-white rounded-xl p-6 w-80 text-center shadow-lg">
|
||||
|
||||
<h3 class="text-lg font-semibold mb-2">
|
||||
Konfirmasi Logout
|
||||
</h3>
|
||||
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
Apakah Anda ingin keluar?
|
||||
</p>
|
||||
|
||||
<div class="flex justify-center gap-3">
|
||||
|
||||
<button onclick="closeModal()"
|
||||
class="px-4 py-2 bg-gray-200 rounded-lg">
|
||||
Tidak
|
||||
</button>
|
||||
|
||||
<button onclick="submitLogout()"
|
||||
class="px-4 py-2 bg-red-500 text-white rounded-lg">
|
||||
Ya
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- SCRIPT -->
|
||||
<script>
|
||||
function toggleSidebar() {
|
||||
document.getElementById('sidebar').classList.toggle('-translate-x-full');
|
||||
}
|
||||
|
||||
function toggleUserMenu() {
|
||||
document.getElementById('userMenu').classList.toggle('hidden');
|
||||
}
|
||||
|
||||
function confirmLogout(){
|
||||
document.getElementById('logoutModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeModal(){
|
||||
document.getElementById('logoutModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function submitLogout(){
|
||||
document.getElementById('logoutForm').submit();
|
||||
}
|
||||
|
||||
feather.replace();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Profile') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.update-profile-information-form')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.update-password-form')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.delete-user-form')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
@extends('layouts.sentara')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="min-h-screen bg-[#f5f7fb] px-6 py-8">
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
|
||||
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-800">Profile Saya</h1>
|
||||
<p class="text-gray-500 text-sm">
|
||||
Informasi akun pengguna <span class="text-blue-600 font-medium">SENTARA</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PROFILE CARD -->
|
||||
<div class="bg-white rounded-2xl shadow-md p-6 flex items-center gap-6 mb-8 relative overflow-hidden">
|
||||
|
||||
<div class="absolute right-0 bottom-0 w-64 h-64 bg-blue-100 opacity-30 rounded-full"></div>
|
||||
|
||||
<div class="w-24 h-24 rounded-full bg-gradient-to-r from-blue-500 to-blue-700
|
||||
flex items-center justify-center text-white text-3xl font-bold shadow-md">
|
||||
{{ strtoupper(substr(Auth::user()->name, 0, 1)) }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-800">
|
||||
{{ Auth::user()->name }}
|
||||
</h2>
|
||||
|
||||
<p class="text-gray-500 mt-1">
|
||||
Staff - Dinas Pariwisata Jember
|
||||
</p>
|
||||
|
||||
<span class="inline-block mt-3 text-xs bg-blue-100 text-blue-600 px-3 py-1 rounded-full font-medium">
|
||||
STAFF
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- INFORMASI AKUN -->
|
||||
<div class="mb-4 flex items-center gap-2 text-blue-600 font-semibold">
|
||||
<span class="text-xl">📋</span> Informasi Akun
|
||||
</div>
|
||||
|
||||
<!-- GRID -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
|
||||
<!-- NAMA -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-5 text-center hover:shadow-md transition">
|
||||
<div class="w-12 h-12 mx-auto mb-3 bg-blue-100 rounded-full flex items-center justify-center text-blue-600">
|
||||
👤
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm">Nama Lengkap</p>
|
||||
<p class="font-semibold text-gray-800 mt-1">
|
||||
{{ Auth::user()->name }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- EMAIL -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-5 text-center hover:shadow-md transition">
|
||||
<div class="w-12 h-12 mx-auto mb-3 bg-blue-100 rounded-full flex items-center justify-center text-blue-600">
|
||||
✉️
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm">Email</p>
|
||||
<p class="font-semibold text-gray-800 mt-1">
|
||||
{{ Auth::user()->email }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ROLE (FIX ICON) -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-5 text-center hover:shadow-md transition">
|
||||
<div class="w-12 h-12 mx-auto mb-3 bg-blue-100 rounded-full flex items-center justify-center text-blue-600">
|
||||
<!-- ICON GROUP -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M17 20h5v-1a4 4 0 00-3-3.87M9 20H4v-1a4 4 0 013-3.87m10-2.13a4 4 0 10-8 0m8 0a4 4 0 01-8 0"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm">Role</p>
|
||||
<p class="font-semibold text-gray-800 mt-1">Staff</p>
|
||||
</div>
|
||||
|
||||
<!-- STATUS -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-5 text-center hover:shadow-md transition">
|
||||
<div class="w-12 h-12 mx-auto mb-3 bg-green-100 rounded-full flex items-center justify-center text-green-600">
|
||||
✔
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm">Status Akun</p>
|
||||
<span class="inline-block mt-1 text-sm bg-green-100 text-green-600 px-3 py-1 rounded-full">
|
||||
Aktif
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- INFO SISTEM -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 flex items-center justify-between">
|
||||
|
||||
<div>
|
||||
<div class="flex items-center gap-2 text-blue-600 font-semibold mb-2">
|
||||
<span class="text-xl">ℹ</span> Informasi Sistem
|
||||
</div>
|
||||
|
||||
<p class="text-gray-500 text-sm max-w-xl">
|
||||
Akun ini digunakan untuk mengakses sistem SENTARA
|
||||
(Sistem Analisis Sentimen Ulasan Dinas Pariwisata Jember).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="flex justify-between text-sm text-gray-400 mt-6">
|
||||
<span>© 2025 SENTARA. All rights reserved.</span>
|
||||
<span>Dinas Pariwisata Jember</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
<section class="space-y-6">
|
||||
<header>
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
{{ __('Delete Account') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.') }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<x-danger-button
|
||||
x-data=""
|
||||
x-on:click.prevent="$dispatch('open-modal', 'confirm-user-deletion')"
|
||||
>{{ __('Delete Account') }}</x-danger-button>
|
||||
|
||||
<x-modal name="confirm-user-deletion" :show="$errors->userDeletion->isNotEmpty()" focusable>
|
||||
<form method="post" action="{{ route('profile.destroy') }}" class="p-6">
|
||||
@csrf
|
||||
@method('delete')
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
{{ __('Are you sure you want to delete your account?') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }}
|
||||
</p>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="password" value="{{ __('Password') }}" class="sr-only" />
|
||||
|
||||
<x-text-input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
class="mt-1 block w-3/4"
|
||||
placeholder="{{ __('Password') }}"
|
||||
/>
|
||||
|
||||
<x-input-error :messages="$errors->userDeletion->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-secondary-button x-on:click="$dispatch('close')">
|
||||
{{ __('Cancel') }}
|
||||
</x-secondary-button>
|
||||
|
||||
<x-danger-button class="ms-3">
|
||||
{{ __('Delete Account') }}
|
||||
</x-danger-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
||||
</section>
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
<section>
|
||||
<header>
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
{{ __('Update Password') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{{ __('Ensure your account is using a long, random password to stay secure.') }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<form method="post" action="{{ route('password.update') }}" class="mt-6 space-y-6">
|
||||
@csrf
|
||||
@method('put')
|
||||
|
||||
<div>
|
||||
<x-input-label for="update_password_current_password" :value="__('Current Password')" />
|
||||
<x-text-input id="update_password_current_password" name="current_password" type="password" class="mt-1 block w-full" autocomplete="current-password" />
|
||||
<x-input-error :messages="$errors->updatePassword->get('current_password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="update_password_password" :value="__('New Password')" />
|
||||
<x-text-input id="update_password_password" name="password" type="password" class="mt-1 block w-full" autocomplete="new-password" />
|
||||
<x-input-error :messages="$errors->updatePassword->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="update_password_password_confirmation" :value="__('Confirm Password')" />
|
||||
<x-text-input id="update_password_password_confirmation" name="password_confirmation" type="password" class="mt-1 block w-full" autocomplete="new-password" />
|
||||
<x-input-error :messages="$errors->updatePassword->get('password_confirmation')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<x-primary-button>{{ __('Save') }}</x-primary-button>
|
||||
|
||||
@if (session('status') === 'password-updated')
|
||||
<p
|
||||
x-data="{ show: true }"
|
||||
x-show="show"
|
||||
x-transition
|
||||
x-init="setTimeout(() => show = false, 2000)"
|
||||
class="text-sm text-gray-600"
|
||||
>{{ __('Saved.') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
<section>
|
||||
<header>
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
{{ __('Profile Information') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{{ __("Update your account's profile information and email address.") }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<form id="send-verification" method="post" action="{{ route('verification.send') }}">
|
||||
@csrf
|
||||
</form>
|
||||
|
||||
<form method="post" action="{{ route('profile.update') }}" class="mt-6 space-y-6">
|
||||
@csrf
|
||||
@method('patch')
|
||||
|
||||
<div>
|
||||
<x-input-label for="name" :value="__('Name')" />
|
||||
<x-text-input id="name" name="name" type="text" class="mt-1 block w-full" :value="old('name', $user->name)" required autofocus autocomplete="name" />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('name')" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" name="email" type="email" class="mt-1 block w-full" :value="old('email', $user->email)" required autocomplete="username" />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('email')" />
|
||||
|
||||
@if ($user instanceof \Illuminate\Contracts\Auth\MustVerifyEmail && ! $user->hasVerifiedEmail())
|
||||
<div>
|
||||
<p class="text-sm mt-2 text-gray-800">
|
||||
{{ __('Your email address is unverified.') }}
|
||||
|
||||
<button form="send-verification" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
{{ __('Click here to re-send the verification email.') }}
|
||||
</button>
|
||||
</p>
|
||||
|
||||
@if (session('status') === 'verification-link-sent')
|
||||
<p class="mt-2 font-medium text-sm text-green-600">
|
||||
{{ __('A new verification link has been sent to your email address.') }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<x-primary-button>{{ __('Save') }}</x-primary-button>
|
||||
|
||||
@if (session('status') === 'profile-updated')
|
||||
<p
|
||||
x-data="{ show: true }"
|
||||
x-show="show"
|
||||
x-transition
|
||||
x-init="setTimeout(() => show = false, 2000)"
|
||||
class="text-sm text-gray-600"
|
||||
>{{ __('Saved.') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="max-w-7xl mx-auto space-y-6">
|
||||
<div class="max-w-7xl mx-auto px-4 md:px-0 space-y-6">
|
||||
|
||||
<!-- HEADER -->
|
||||
<h2 class="text-2xl font-bold text-gray-800">Data Ulasan</h2>
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
<!-- ============================= -->
|
||||
<div class="bg-white rounded-xl shadow p-6">
|
||||
|
||||
<div class="grid grid-cols-3 gap-6 items-center">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 items-center">
|
||||
|
||||
<!-- UPLOAD -->
|
||||
<div class="border-2 border-dashed border-blue-300 rounded-xl p-6 text-center">
|
||||
|
|
@ -109,7 +109,7 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm sha
|
|||
<!-- ============================= -->
|
||||
<!-- 🔍 SEARCH + FILTER -->
|
||||
<!-- ============================= -->
|
||||
<form method="GET" action="{{ route('ulasan.index') }}" class="flex gap-3">
|
||||
<form method="GET" action="{{ route('ulasan.index') }}" class="flex flex-col md:flex-row gap-3">
|
||||
|
||||
<input type="text" name="search" value="{{ request('search') }}"
|
||||
placeholder="Cari ulasan atau wisata..."
|
||||
|
|
@ -140,6 +140,7 @@ class="bg-gray-200 px-4 py-2 rounded-lg text-sm">
|
|||
<!-- TABLE -->
|
||||
<!-- ============================= -->
|
||||
<div class="bg-white rounded-xl shadow overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-100">
|
||||
|
|
@ -190,7 +191,9 @@ class="bg-gray-200 px-4 py-2 rounded-lg text-sm">
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="p-4 flex justify-between items-center text-sm text-gray-500">
|
||||
</div>
|
||||
|
||||
<div class="p-4 flex flex-col md:flex-row gap-3 justify-between items-center text-sm text-gray-500">
|
||||
|
||||
<!-- KIRI (FINAL FIX) -->
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@
|
|||
// })->middleware(['auth', 'verified'])->name('dashboard');
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
||||
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
|
||||
Route::get('/profile', function () {
|
||||
return view('profile.index');
|
||||
})->name('profile');
|
||||
// Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
||||
// Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
|
||||
Route::get('/ulasan', [UlasanController::class, 'index'])->name('ulasan.index');
|
||||
Route::post('/ulasan/ambil', [UlasanController::class, 'ambilData'])->name('ulasan.ambil');
|
||||
Route::post('/ulasan/analisis', [UlasanController::class, 'analisisData'])->name('ulasan.analisis');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
import re
|
||||
import pandas as pd
|
||||
import pymysql
|
||||
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.feature_extraction.text import TfidfVectorizer
|
||||
from sklearn.naive_bayes import ComplementNB
|
||||
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
|
||||
|
||||
from Sastrawi.Stemmer.StemmerFactory import StemmerFactory
|
||||
from Sastrawi.StopWordRemover.StopWordRemoverFactory import StopWordRemoverFactory
|
||||
|
||||
# ===============================
|
||||
# 🔌 KONEKSI DB
|
||||
# ===============================
|
||||
conn = pymysql.connect(
|
||||
host='localhost',
|
||||
user='root',
|
||||
password='',
|
||||
database='sentara',
|
||||
cursorclass=pymysql.cursors.DictCursor
|
||||
)
|
||||
|
||||
cursor = conn.cursor()
|
||||
|
||||
# ===============================
|
||||
# 1. AMBIL DATA
|
||||
# ===============================
|
||||
df = pd.read_sql("SELECT id, wisata, ulasan FROM ulasan", conn)
|
||||
|
||||
# ===============================
|
||||
# 2. CLEANING
|
||||
# ===============================
|
||||
df = df.dropna(subset=["ulasan"])
|
||||
df["ulasan"] = df["ulasan"].astype(str)
|
||||
df = df[df["ulasan"].str.strip() != ""]
|
||||
|
||||
# ===============================
|
||||
# 3. PREPROCESSING
|
||||
# ===============================
|
||||
stemmer = StemmerFactory().create_stemmer()
|
||||
stop_factory = StopWordRemoverFactory()
|
||||
stopwords = set(stop_factory.get_stop_words())
|
||||
|
||||
def clean_text(text):
|
||||
text = text.lower()
|
||||
text = re.sub(r"http\S+", " ", text)
|
||||
text = re.sub(r"[^a-zA-Z\s]", " ", text)
|
||||
text = re.sub(r"\s+", " ", text)
|
||||
|
||||
words = [w for w in text.split() if w not in stopwords]
|
||||
return stemmer.stem(" ".join(words))
|
||||
|
||||
df["clean"] = df["ulasan"].apply(clean_text)
|
||||
|
||||
# ===============================
|
||||
# 4. LABEL SEMENTARA (dummy)
|
||||
# ===============================
|
||||
def label_rule(text):
|
||||
if "tidak" in text or "buruk" in text:
|
||||
return "negatif"
|
||||
elif "bagus" in text or "indah" in text:
|
||||
return "positif"
|
||||
else:
|
||||
return "netral"
|
||||
|
||||
df["label"] = df["clean"].apply(label_rule)
|
||||
|
||||
# ===============================
|
||||
# 5. TF-IDF + MODEL
|
||||
# ===============================
|
||||
X = df["clean"]
|
||||
y = df["label"]
|
||||
|
||||
X_train, X_test, y_train, y_test = train_test_split(
|
||||
X, y, test_size=0.2, random_state=42, stratify=y
|
||||
)
|
||||
|
||||
vectorizer = TfidfVectorizer()
|
||||
X_train_vec = vectorizer.fit_transform(X_train)
|
||||
X_test_vec = vectorizer.transform(X_test)
|
||||
|
||||
model = ComplementNB()
|
||||
model.fit(X_train_vec, y_train)
|
||||
|
||||
# ===============================
|
||||
# 6. EVALUASI
|
||||
# ===============================
|
||||
y_pred = model.predict(X_test_vec)
|
||||
|
||||
acc = accuracy_score(y_test, y_pred)
|
||||
report = classification_report(y_test, y_pred, output_dict=True)
|
||||
cm = confusion_matrix(y_test, y_pred, labels=["negatif","netral","positif"])
|
||||
|
||||
# ===============================
|
||||
# 7. PREDIKSI SEMUA DATA
|
||||
# ===============================
|
||||
X_all = vectorizer.transform(df["clean"])
|
||||
df["prediksi"] = model.predict(X_all)
|
||||
|
||||
# ===============================
|
||||
# 8. SIMPAN KE DB
|
||||
# ===============================
|
||||
|
||||
# kosongkan tabel
|
||||
cursor.execute("DELETE FROM hasil_analisis")
|
||||
cursor.execute("DELETE FROM evaluasi_model")
|
||||
|
||||
# simpan hasil per ulasan
|
||||
for _, row in df.iterrows():
|
||||
cursor.execute("""
|
||||
INSERT INTO hasil_analisis
|
||||
(wisata, ulasan_terolah, hasil_preprocessing, sentimen, probabilitas, created_at)
|
||||
VALUES (%s,%s,%s,%s,%s,NOW())
|
||||
""", (
|
||||
row["wisata"],
|
||||
row["ulasan"],
|
||||
row["clean"],
|
||||
row["prediksi"],
|
||||
0.9
|
||||
))
|
||||
|
||||
# simpan evaluasi
|
||||
cursor.execute("""
|
||||
INSERT INTO evaluasi_model
|
||||
(precision, recall, f1_score, accuracy, tp, tn, fp, fn, created_at)
|
||||
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,NOW())
|
||||
""", (
|
||||
report["weighted avg"]["precision"],
|
||||
report["weighted avg"]["recall"],
|
||||
report["weighted avg"]["f1-score"],
|
||||
acc,
|
||||
int(cm[2][2]),
|
||||
int(cm[0][0]),
|
||||
int(cm[0][2]),
|
||||
int(cm[2][0])
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print("Analisis selesai ✔")
|
||||
Loading…
Reference in New Issue