Files
ProjectKP/KP-ASOY FIX (2)/KP-ASOY/resources/views/user/dashboard.blade.php
T
2026-06-28 15:14:51 +07:00

897 lines
29 KiB
PHP

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard User</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--primary: #1d4ed8;
--primary-soft: #e0e7ff;
--bg: #f4f6fb;
--card: #ffffff;
--border: #e5e7eb;
--text: #111827;
--muted: #6b7280;
--danger: #dc2626;
--success: #16a34a;
--warning: #f59e0b;
--info: #2563eb;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Inter', sans-serif;
}
body {
background: var(--bg);
display: flex
}
/* ─── SIDEBAR ─── */
.sidebar {
width: 280px;
height: 100vh;
background: #fff;
padding: 24px 18px;
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
position: sticky;
top: 0;
flex-shrink: 0;
}
.logo {
display: flex;
gap: 12px;
align-items: center;
margin-bottom: 18px
}
.logo img {
width: 54px;
height: 54px;
object-fit: contain
}
.logo-text .top,
.logo-text .mid {
font-size: 13px;
font-weight: 700;
line-height: 1.4;
color: #111827
}
.logo-text .bot {
font-size: 11px;
color: var(--muted);
margin-top: 2px
}
.divider {
height: 1px;
background: var(--border);
margin: 14px 0
}
.menu {
display: flex;
flex-direction: column;
gap: 4px
}
.menu a {
padding: 11px 13px;
border-radius: 10px;
text-decoration: none;
color: #374151;
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
transition: .2s;
}
.menu a:hover {
background: #f3f4f6
}
.menu a.active {
background: var(--primary-soft);
color: var(--primary);
font-weight: 600;
}
.menu a svg {
flex-shrink: 0;
opacity: .7
}
.menu a.active svg {
opacity: 1
}
.bottom {
margin-top: auto;
display: flex;
flex-direction: column;
gap: 4px
}
.menu-item {
padding: 11px 13px;
color: #374151;
text-decoration: none;
border-radius: 10px;
font-size: 14px;
display: flex;
align-items: center;
gap: 10px;
transition: .2s;
}
.menu-item:hover {
background: #f3f4f6
}
.menu-item svg {
opacity: .7
}
.logout-btn {
width: 100%;
padding: 11px 13px;
border: none;
background: none;
color: var(--danger);
cursor: pointer;
text-align: left;
border-radius: 10px;
font-size: 14px;
display: flex;
align-items: center;
gap: 10px;
transition: .2s;
}
.logout-btn:hover {
background: #fef2f2
}
/* ─── MAIN ─── */
.main {
flex: 1;
padding: 28px 32px;
overflow-y: auto;
height: 100vh
}
/* ─── HEADER ─── */
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 28px;
}
.search-wrap {
position: relative;
width: 340px
}
.search-wrap svg {
position: absolute;
left: 13px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
color: var(--muted);
}
.search {
width: 100%;
padding: 10px 14px 10px 40px;
border: 1px solid var(--border);
border-radius: 12px;
transition: .2s;
font-size: 14px;
background: #fff;
}
.search:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(29, 78, 216, .08);
}
/* ─── USER ─── */
.user {
display: flex;
align-items: center;
gap: 14px
}
.avatar {
width: 40px;
height: 40px;
background: #1e3a8a;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-weight: 700;
font-size: 15px;
flex-shrink: 0;
}
/* ─── NOTIF ─── */
.notif {
position: relative;
cursor: pointer
}
.notif-btn {
width: 40px;
height: 40px;
border: 1px solid var(--border);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
cursor: pointer;
transition: .2s;
}
.notif-btn:hover {
background: #f3f4f6
}
.badge {
position: absolute;
top: -5px;
right: -5px;
background: #ef4444;
color: #fff;
font-size: 11px;
border-radius: 999px;
padding: 2px 6px;
font-weight: 600;
min-width: 20px;
text-align: center;
}
.dropdown {
position: absolute;
right: 0;
top: 50px;
width: 320px;
background: #fff;
border: 1px solid var(--border);
border-radius: 14px;
box-shadow: 0 10px 30px rgba(0, 0, 0, .1);
display: none;
z-index: 100;
}
.dropdown.show {
display: block
}
.dropdown-header {
padding: 13px 16px;
font-weight: 600;
font-size: 14px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.dropdown-header a {
font-size: 12px;
color: var(--primary);
text-decoration: none;
font-weight: 500
}
.dropdown-item {
padding: 13px 16px;
border-bottom: 1px solid var(--border);
font-size: 13px;
line-height: 1.5;
color: var(--text);
}
.dropdown-item:last-child {
border-bottom: none
}
.dropdown-item.unread {
background: #eef2ff
}
.dropdown-empty {
padding: 24px;
text-align: center;
color: var(--muted);
font-size: 13px
}
/* ─── PAGE TITLE ─── */
.page-title {
font-size: 24px;
font-weight: 700;
margin-bottom: 4px;
color: var(--text)
}
.page-subtitle {
color: var(--muted);
font-size: 14px;
margin-bottom: 22px
}
/* ─── CARDS ─── */
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
margin-bottom: 24px
}
.card {
background: #fff;
padding: 20px 22px;
border-radius: 14px;
border: 1px solid var(--border);
transition: .2s;
}
.card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0, 0, 0, .06)
}
.card h4 {
font-size: 11px;
font-weight: 600;
letter-spacing: .05em;
margin-bottom: 8px;
}
.card h2 {
font-size: 32px;
font-weight: 700
}
.card.total h4,
.card.total h2 {
color: var(--info)
}
.card.proses h4,
.card.proses h2 {
color: var(--warning)
}
.card.selesai h4,
.card.selesai h2 {
color: var(--success)
}
.card-icon {
width: 36px;
height: 36px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
}
.card.total .card-icon {
background: #dbeafe
}
.card.proses .card-icon {
background: #fef3c7
}
.card.selesai .card-icon {
background: #dcfce7
}
/* ─── LIST ─── */
.list {
background: #fff;
border-radius: 14px;
border: 1px solid var(--border);
overflow: hidden;
}
.list-header {
padding: 16px 20px;
font-weight: 600;
font-size: 14px;
border-bottom: 1px solid var(--border);
background: #fafafa;
display: flex;
justify-content: space-between;
align-items: center;
}
.list-header a {
font-size: 13px;
color: var(--primary);
text-decoration: none;
font-weight: 600;
}
.list-header a:hover {
text-decoration: underline
}
.item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--border);
transition: .2s;
}
.item:last-child {
border-bottom: none
}
.item:hover {
background: #f9fafb
}
.item-left strong {
display: block;
margin-bottom: 3px;
font-size: 14px
}
.item-left small {
color: var(--muted);
font-size: 12px
}
.badge-status {
padding: 5px 12px;
border-radius: 999px;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
}
.badge-success {
background: #dcfce7;
color: var(--success)
}
.badge-warning {
background: #fef3c7;
color: #b45309
}
.badge-info {
background: #dbeafe;
color: #1d4ed8
}
.badge-danger {
background: #fee2e2;
color: #dc2626
}
/* ─── EMPTY STATE ─── */
.empty-state {
padding: 48px 20px;
text-align: center;
color: var(--muted);
}
.empty-state svg {
margin: 0 auto 12px;
display: block;
opacity: .35
}
.empty-state p {
font-size: 14px
}
/* ─── NO RESULT ─── */
.no-result {
display: none;
padding: 32px 20px;
text-align: center;
color: var(--muted);
font-size: 14px;
}
/* ─── RESPONSIVE ─── */
@media (max-width: 1024px) {
.cards { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
body { flex-direction: column; }
.sidebar {
width: 100%;
height: auto;
position: relative;
border-right: none;
border-bottom: 1px solid var(--border);
padding: 16px;
flex-direction: column;
}
.logo {
justify-content: center;
margin-bottom: 12px;
}
.divider { display: none; }
.menu {
flex-direction: row;
flex-wrap: wrap;
overflow-x: visible;
gap: 8px;
padding-bottom: 8px;
}
.menu a { white-space: nowrap; justify-content: center; flex: 1 1 calc(50% - 8px); text-align: center; font-size: 13px; padding: 10px; }
.bottom {
flex-direction: row;
margin-top: 8px;
gap: 8px;
border-top: 1px solid var(--border);
padding-top: 12px;
}
.bottom a, .bottom button { white-space: nowrap; justify-content: center; flex: 1 1 calc(50% - 8px); font-size: 13px; padding: 10px; }
.main { padding: 20px 16px; height: auto; }
.header { flex-direction: column-reverse; align-items: flex-start; gap: 20px; margin-bottom: 20px; }
.search-wrap { width: 100%; }
.user { width: 100%; justify-content: space-between; }
.dropdown { width: 300px; left: -10px; right: auto; top: 55px; max-width: calc(100vw - 32px); }
.cards { grid-template-columns: 1fr; gap: 12px; }
.item { flex-direction: column; align-items: flex-start; gap: 12px; }
}
</style>
</head>
<body>
<!-- ═══ SIDEBAR ═══ -->
<div class="sidebar">
<div class="logo">
<img src="{{ asset('Kalimantan-Barat-Logo-Vector.png') }}" alt="Logo">
<div class="logo-text">
<div class="top">DINAS TENAGA KERJA</div>
<div class="mid">DAN TRANSMIGRASI</div>
<div class="bot">PROVINSI KALIMANTAN BARAT</div>
</div>
</div>
<div class="divider"></div>
<div class="menu">
<a href="{{ route('user.dashboard') }}"
class="{{ request()->routeIs('user.dashboard') ? 'active' : '' }}">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7" rx="1" />
<rect x="13" y="3" width="8" height="7" rx="1" />
<rect x="13" y="13" width="8" height="8" rx="1" />
<rect x="3" y="13" width="7" height="8" rx="1" />
</svg>
Dashboard
</a>
<a href="{{ route('input.activity') }}"
class="{{ request()->routeIs('input.activity') ? 'active' : '' }}">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<path d="M11 5H6a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2v-5" />
<path d="M17.5 3.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 8.5-8.5z" />
</svg>
Input Aktivitas
</a>
<a href="{{ route('activity.list') }}"
class="{{ request()->routeIs('activity.list') ? 'active' : '' }}">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2" />
<rect x="9" y="3" width="6" height="4" rx="1" />
<path d="M9 12h6M9 16h4" />
</svg>
Daftar Aktivitas
</a>
</div>
<div class="bottom">
<a href="{{ route('settings') }}" class="menu-item">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
</svg>
Pengaturan
</a>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button class="logout-btn" type="submit">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
<polyline points="16 17 21 12 16 7" />
<line x1="21" y1="12" x2="9" y2="12" />
</svg>
Logout
</button>
</form>
</div>
</div>
<!-- ═══ MAIN ═══ -->
<div class="main">
<!-- HEADER -->
<div class="header">
<div class="search-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
<input type="text" class="search" placeholder="Cari aktivitas..."
oninput="filterAktivitas(this.value)">
</div>
<div class="user">
<!-- NOTIF -->
<div class="notif">
<div class="notif-btn" onclick="toggleNotif()">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
</svg>
</div>
@if($unreadCount > 0)
<div class="badge">{{ $unreadCount }}</div>
@endif
<div class="dropdown" id="notifDropdown">
<div class="dropdown-header">
Notifikasi
@if(count($notifications) > 0)
<a href="#" onclick="markAllAsRead(event)">Tandai semua dibaca</a>
@endif
</div>
<div id="notifListContainer">
@forelse($notifications as $n)
<div class="dropdown-item {{ !$n['read_at'] ? 'unread' : '' }}">
{{ $n['data']['message'] ?? 'Ada notifikasi baru' }}
<br><small style="color:var(--muted); font-size:11px;">{{ \Carbon\Carbon::parse($n['created_at'])->diffForHumans() }}</small>
</div>
@empty
<div class="dropdown-empty">Tidak ada notifikasi</div>
@endforelse
</div>
</div>
</div>
<!-- USER INFO -->
<div>
<strong style="font-size:14px">
{{ session('user')['name'] ?? 'User' }}
</strong><br>
<small style="color:var(--muted);font-size:12px">Pegawai</small>
</div>
<div class="avatar">
{{ strtoupper(substr(session('user')['name'] ?? 'U', 0, 1)) }}
</div>
</div>{{-- end .user --}}
</div>{{-- end .header --}}
<!-- PAGE TITLE -->
<div class="page-title">Dashboard Monitoring</div>
<div class="page-subtitle">Pantau aktivitas kamu</div>
<!-- CARDS -->
<div class="cards">
<div class="card total">
<div class="card-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="#2563eb" stroke-width="2">
<rect x="3" y="3" width="7" height="7" rx="1" />
<rect x="13" y="3" width="8" height="7" rx="1" />
<rect x="13" y="13" width="8" height="8" rx="1" />
<rect x="3" y="13" width="7" height="8" rx="1" />
</svg>
</div>
<h4>TOTAL</h4>
<h2>{{ $activities->count() }}</h2>
</div>
<div class="card proses">
<div class="card-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="#f59e0b" stroke-width="2">
<circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" />
</svg>
</div>
<h4>ON PROGRESS</h4>
<h2>{{ $onProgres }}</h2>
</div>
<div class="card selesai">
<div class="card-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="#16a34a" stroke-width="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
</div>
<h4>SELESAI</h4>
<h2>{{ $selesai }}</h2>
</div>
</div>
<!-- RECENT ACTIVITIES -->
<div class="list">
<div class="list-header">
<span>Recent Activities</span>
<a href="{{ route('activity.list') }}">Lihat Semua </a>
</div>
<div id="activity-list">
@forelse($activities->take(5) as $item)
<div class="item" data-name="{{ strtolower($item->nama_kegiatan) }}">
<div class="item-left">
<strong>{{ $item->nama_kegiatan }}</strong>
<small>
{{ $item->user->name ?? 'Pegawai' }}
&nbsp;·&nbsp;
{{ \Carbon\Carbon::parse($item->tanggal ?? $item->created_at)->translatedFormat('d M Y') }}
@if(!empty($item->tanggal_selesai))
- {{ \Carbon\Carbon::parse($item->tanggal_selesai)->translatedFormat('d M Y') }}
@endif
@if(!empty($item->lokasi))
&nbsp;·&nbsp; {{ $item->lokasi }}
@endif
</small>
</div>
<div>
@php
$isOverdue = !empty($item->tanggal_selesai) && \Carbon\Carbon::parse($item->tanggal_selesai)->isPast() && $item->status !== 'selesai' && $item->status !== 'telah_ditinjau';
@endphp
@if($item->status == 'selesai')
<span class="badge-status badge-success">Selesai</span>
@elseif(!empty($item->reviewed_at) || $item->status == 'ditinjau' || $item->status == 'telah_ditinjau')
<span class="badge-status badge-info">Ditinjau</span>
@elseif($item->status == 'ditolak')
<span class="badge-status badge-danger">Ditolak</span>
@elseif($isOverdue)
<span class="badge-status badge-danger" style="background:#fef2f2; color:#dc2626; border-color:#fecaca;">Terlambat</span>
@else
<span class="badge-status badge-warning">On Progress</span>
@endif
</div>
</div>
@empty
<div class="empty-state">
<svg width="44" height="44" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="1.5">
<path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2" />
<rect x="9" y="3" width="6" height="4" rx="1" />
<path d="M9 12h6M9 16h4" />
</svg>
<p>Belum ada aktivitas yang diinput.</p>
<a href="{{ route('input.activity') }}"
style="display:inline-block;margin-top:12px;padding:8px 18px;
background:var(--primary);color:#fff;border-radius:8px;
text-decoration:none;font-size:13px;font-weight:500">
+ Input Aktivitas
</a>
</div>
@endforelse
</div>
<!-- No result saat search -->
<div class="no-result" id="no-result">
Tidak ada aktivitas yang cocok dengan pencarian.
</div>
</div>
</div>{{-- end .list --}}
</div>{{-- end .main --}}
<script>
// ─── TOGGLE NOTIF ───
function toggleNotif() {
document.getElementById("notifDropdown").classList.toggle("show");
}
document.addEventListener('click', function(e) {
const notif = document.querySelector('.notif');
if (notif && !notif.contains(e.target)) {
document.getElementById('notifDropdown').classList.remove('show');
}
});
async function markAllAsRead(e) {
e.preventDefault();
try {
await fetch('/user/notifications-api/read', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Content-Type': 'application/json'
}
});
// Hapus badge dan ganti class unread
const badge = document.querySelector('.badge');
if (badge) badge.style.display = 'none';
document.querySelectorAll('.dropdown-item.unread').forEach(el => {
el.classList.remove('unread');
});
} catch (e) {
console.error(e);
}
}
// ─── FILTER SEARCH ───
function filterAktivitas(keyword) {
const items = document.querySelectorAll('#activity-list .item');
const noResult = document.getElementById('no-result');
keyword = keyword.toLowerCase().trim();
let visibleCount = 0;
items.forEach(item => {
const name = item.getAttribute('data-name') || '';
if (name.includes(keyword)) {
item.style.display = 'flex';
visibleCount++;
} else {
item.style.display = 'none';
}
});
if (visibleCount === 0 && items.length > 0) {
noResult.style.display = 'block';
} else {
noResult.style.display = 'none';
}
}
</script>
</body>
</html>