Initial commit for combined ProjectKP

This commit is contained in:
raditarahman5-cloud
2026-06-28 15:14:51 +07:00
commit ac6c3473d4
203 changed files with 41848 additions and 0 deletions
@@ -0,0 +1,263 @@
@extends('user.layouts.app')
@section('content')
@php
// Logika status agar sinkron dengan database
$status = $activity->status ?? 'on_progres';
$statusLabel = match($status) {
'selesai' => 'DISETUJUI',
'on_progres' => 'ON PROGRES',
'review' => 'REVIEW',
default => strtoupper($status),
};
$statusColor = match($status) {
'selesai' => 'bg-green-100 text-green-700',
'on_progres' => 'bg-blue-100 text-blue-700',
'review' => 'bg-yellow-100 text-yellow-700',
default => 'bg-gray-100 text-gray-700',
};
$fileName = $activity->lampiran ? basename($activity->lampiran) : null;
$shortName = $fileName && strlen($fileName) > 30
? substr($fileName, 0, 30) . '...'
: $fileName;
@endphp
<div class="max-w-6xl mx-auto mt-10 px-4 pb-16">
{{-- HEADER --}}
<div class="mb-8">
<div class="flex gap-3 items-center mb-3">
<span class="{{ $statusColor }} text-xs px-4 py-1.5 rounded-full font-semibold">
{{ $statusLabel }}
</span>
<span class="text-sm text-slate-400">
Terakhir diperbarui:
{{ $activity->updated_at ? \Carbon\Carbon::parse($activity->updated_at)->translatedFormat('d M Y') : '-' }}
</span>
</div>
<h1 class="text-3xl font-bold text-[#0D215C]">{{ $activity->nama_kegiatan }}</h1>
<p class="text-sm text-slate-400">Informasi lengkap aktivitas pegawai</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
{{-- KIRI: KONTEN UTAMA --}}
<div class="lg:col-span-2 bg-white p-7 rounded-2xl shadow-sm border border-slate-100">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-8">
<div>
<p class="text-xs text-slate-400 uppercase tracking-wide mb-1">Nama Pegawai</p>
<p class="font-semibold text-lg text-slate-800">{{ $activity->user->name ?? 'User Tidak Ditemukan' }}</p>
<p class="text-sm text-slate-400">Username: {{ $activity->user->username ?? '-' }}</p>
</div>
<div>
<p class="text-xs text-slate-400 uppercase tracking-wide mb-1">Lokasi</p>
<p class="font-semibold text-slate-800 flex items-center gap-2">
<i data-lucide="map-pin" class="w-4 h-4 text-slate-400"></i>
{{ $activity->lokasi ?? '-' }}
</p>
</div>
<div>
<p class="text-xs text-slate-400 uppercase tracking-wide mb-1">Tanggal Mulai</p>
<p class="font-semibold text-slate-800 flex items-center gap-2">
<i data-lucide="calendar" class="w-4 h-4 text-slate-400"></i>
{{ $activity->tanggal ? \Carbon\Carbon::parse($activity->tanggal)->translatedFormat('d F Y') : '-' }}
</p>
</div>
<div>
<p class="text-xs text-slate-400 uppercase tracking-wide mb-1">Tenggat Waktu</p>
<p class="font-semibold text-slate-800 flex items-center gap-2">
<i data-lucide="clock" class="w-4 h-4 text-slate-400"></i>
@if($activity->tanggal_selesai)
@php
$isOverdue = \Carbon\Carbon::parse($activity->tanggal_selesai)->isPast() && $activity->status !== 'selesai' && $activity->status !== 'telah_ditinjau';
@endphp
<span class="{{ $isOverdue ? 'text-red-600 font-bold' : '' }}">
{{ \Carbon\Carbon::parse($activity->tanggal_selesai)->translatedFormat('d F Y') }}
@if($isOverdue) (Terlambat) @endif
</span>
@else
-
@endif
</p>
</div>
</div>
<hr class="mb-6 border-slate-100">
<div class="mb-8">
<p class="text-xs text-slate-400 uppercase tracking-wide mb-2">Deskripsi Detail</p>
<div class="text-sm text-slate-600 leading-relaxed text-justify">
{{ $activity->deskripsi ?? 'Tidak ada deskripsi tambahan.' }}
</div>
</div>
@if(!empty($activity->status_note))
<div class="mb-8 p-4 bg-blue-50 border border-blue-100 rounded-xl">
<p class="text-xs text-blue-500 uppercase tracking-wide mb-2 font-semibold">Catatan Status Terakhir</p>
<div class="text-sm text-blue-900 leading-relaxed text-justify">
{{ $activity->status_note }}
</div>
</div>
@endif
{{-- 📝 TIMELINE PROGRESS MONITORING --}}
<div class="mb-8">
<p class="text-xs text-slate-400 uppercase tracking-wide mb-4">Log Monitoring Progress</p>
@if(isset($activity->progress) && count($activity->progress) > 0)
<div class="relative border-l-2 border-indigo-100 ml-3 pl-5 space-y-6">
@foreach($activity->progress as $prog)
<div class="relative">
<div class="absolute -left-[27px] bg-indigo-500 h-3 w-3 rounded-full border-2 border-white top-1"></div>
<div class="text-xs text-slate-400 mb-1">
{{ \Carbon\Carbon::parse($prog['created_at'])->format('d M Y, H:i') }}
<span class="font-semibold text-indigo-500">{{ $prog['user']['name'] ?? 'Pegawai' }}</span>
</div>
<div class="text-sm text-slate-700 bg-slate-50 border border-slate-100 p-3 rounded-lg shadow-sm">
{{ $prog['note'] }}
@if(!empty($prog['attachment']))
<div class="mt-3 pt-3 border-t border-slate-200">
<a href="http://127.0.0.1:8000/{{ $prog['attachment'] }}" target="_blank"
class="inline-flex items-center gap-2 px-3 py-1.5 bg-indigo-50 text-indigo-600 text-xs font-semibold rounded-lg hover:bg-indigo-100 transition-colors border border-indigo-100">
<i data-lucide="paperclip" class="w-3.5 h-3.5"></i> Lihat Lampiran Bukti
</a>
</div>
@endif
</div>
</div>
@endforeach
</div>
@else
<div class="text-sm text-slate-500 bg-slate-50 p-4 rounded-xl border border-dashed border-slate-200 text-center">
Belum ada log progress yang dicatat untuk kegiatan ini.
</div>
@endif
@php
$isPetugas = false;
if(isset($activity->petugas)) {
foreach($activity->petugas as $p) {
if($p['id'] == session('user_id')) {
$isPetugas = true;
break;
}
}
}
@endphp
@if($status !== 'selesai' && $isPetugas)
<div class="mt-6 border-t border-slate-100 pt-5">
<form action="{{ route('activity.progress', $activity->id) }}" method="POST" enctype="multipart/form-data">
@csrf
<label class="block text-sm font-semibold text-slate-700 mb-2">Tambahkan Log Progress</label>
<div class="flex flex-col gap-3">
<textarea name="note" required rows="3" placeholder="Contoh: Tim sudah sampai di lokasi survei..." class="w-full bg-slate-50 border border-slate-200 text-slate-800 rounded-xl px-4 py-3 text-sm focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition-all shadow-sm resize-y"></textarea>
<div class="flex justify-end gap-3 flex-wrap">
<label class="cursor-pointer bg-slate-50 border border-slate-200 text-slate-500 hover:text-indigo-600 hover:border-indigo-300 px-5 py-2.5 rounded-xl text-sm font-medium transition-all shadow-sm flex items-center gap-2 whitespace-nowrap">
<i data-lucide="image" class="w-4 h-4"></i> Bukti Foto
<input type="file" name="attachment" accept="image/*,.pdf" class="hidden" onchange="this.parentElement.querySelector('span').innerText = this.files[0].name.substring(0,15) + '...'">
<span class="text-xs text-slate-400"></span>
</label>
<button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2.5 rounded-xl text-sm font-medium shadow-sm shadow-indigo-200 transition-all flex items-center justify-center gap-2 whitespace-nowrap">
<i data-lucide="send" class="w-4 h-4"></i> Kirim Log
</button>
</div>
</div>
</form>
</div>
@endif
</div>
{{-- ACTION BUTTONS --}}
<div class="flex gap-3 flex-wrap border-t pt-6">
@if($status !== 'selesai' && $isPetugas)
<a href="{{ route('activity.edit', $activity->id) }}"
class="inline-flex items-center gap-2 bg-yellow-500 hover:bg-yellow-600 text-white px-5 py-2.5 rounded-lg text-sm font-medium transition">
<i data-lucide="pencil" class="w-4 h-4"></i> Edit Laporan
</a>
<form action="{{ route('activity.selesai', $activity->id) }}" method="POST">
@csrf
<button type="submit" onclick="return confirm('Tandai kegiatan ini sebagai selesai?')"
class="inline-flex items-center gap-2 bg-green-500 hover:bg-green-600 text-white px-5 py-2.5 rounded-lg text-sm font-medium transition">
<i data-lucide="check" class="w-4 h-4"></i> Selesaikan Kegiatan
</button>
</form>
@endif
</div>
</div>
{{-- KANAN: SIDEBAR DETAIL --}}
<div class="space-y-4">
{{-- Lampiran --}}
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-100">
<h4 class="font-semibold mb-4 flex items-center gap-2 text-[#0D215C]">
<i data-lucide="paperclip" class="w-4 h-4"></i> Lampiran
</h4>
@if($activity->lampiran)
<div class="flex items-center gap-3 bg-red-50 border border-red-100 rounded-xl px-4 py-3 mb-4">
<div class="w-10 h-10 rounded-lg bg-red-100 flex items-center justify-center">
<i data-lucide="image" class="w-5 h-5 text-red-500"></i>
</div>
<div class="min-w-0">
<p class="text-sm font-medium text-slate-700 truncate">{{ $shortName }}</p>
<p class="text-xs text-slate-400">File Lampiran</p>
</div>
</div>
<div class="grid grid-cols-2 gap-2">
<a href="{{ rtrim(env('BACKEND_API_URL', 'http://127.0.0.1:8000'), '/api') . '/storage/' . $activity->lampiran }}" target="_blank"
class="flex items-center justify-center gap-2 border border-slate-200 hover:bg-slate-50 text-slate-700 py-2 rounded-lg text-sm transition">
<i data-lucide="eye" class="w-4 h-4"></i> Lihat
</a>
<a href="{{ rtrim(env('BACKEND_API_URL', 'http://127.0.0.1:8000'), '/api') . '/storage/' . $activity->lampiran }}" download
class="flex items-center justify-center gap-2 border border-slate-200 hover:bg-slate-50 text-slate-700 py-2 rounded-lg text-sm transition">
<i data-lucide="download" class="w-4 h-4"></i> Unduh
</a>
</div>
@else
<div class="text-center py-4 border-2 border-dashed border-slate-100 rounded-xl">
<p class="text-sm text-slate-400 italic">Tidak ada lampiran</p>
</div>
@endif
</div>
{{-- Metadata --}}
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-100">
<h4 class="font-semibold mb-4 text-[#0D215C]">Meta Data</h4>
<div class="space-y-3 text-sm">
<div class="flex justify-between text-slate-600">
<span>ID Laporan</span>
<span class="font-mono text-xs">#ACT-{{ str_pad($activity->id, 5, '0', STR_PAD_LEFT) }}</span>
</div>
<div class="flex justify-between text-slate-600">
<span>Metode</span>
<span>{{ $activity->metode ?? 'Luring' }}</span>
</div>
</div>
</div>
<a href="{{ route('activity.list') }}" class="block text-center text-sm text-blue-600 hover:text-blue-800 transition py-2">
Kembali ke Daftar Laporan
</a>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
});
</script>
@endsection
@@ -0,0 +1,94 @@
@extends('user.layouts.app')
@section('content')
<div class="max-w-3xl mx-auto mt-10 px-4 pb-20">
{{-- HEADER --}}
<div class="mb-8">
<a href="{{ route('activity.detail', $activity->id) }}" class="text-sm text-blue-600 flex items-center gap-2 mb-4 no-underline hover:underline">
<i data-lucide="arrow-left" class="w-4 h-4"></i> Kembali ke Detail
</a>
<h2 class="text-2xl font-bold text-[#0D215C]">
Edit Laporan Kegiatan
</h2>
<p class="text-sm text-slate-400">Perbarui informasi laporan kegiatan Anda di bawah ini.</p>
</div>
{{-- FORM CARD --}}
<div class="bg-white p-8 rounded-2xl shadow-sm border border-slate-100">
<form action="{{ route('activity.update', $activity->id) }}" method="POST" class="space-y-6">
@csrf
<div>
<label class="text-sm font-semibold text-slate-700 block mb-2">Nama Kegiatan</label>
<div class="relative">
<input type="text" name="nama_kegiatan"
value="{{ old('nama_kegiatan', $activity->nama_kegiatan) }}"
placeholder="Masukkan nama kegiatan..."
class="w-full p-3 rounded-xl bg-slate-50 border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition" required>
</div>
@error('nama_kegiatan') <span class="text-red-500 text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="text-sm font-semibold text-slate-700 block mb-2">Tanggal Pelaksanaan</label>
<input type="date" name="tanggal"
value="{{ old('tanggal', $activity->tanggal) }}"
class="w-full p-3 rounded-xl bg-slate-50 border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition" required>
@error('tanggal') <span class="text-red-500 text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div>
<label class="text-sm font-semibold text-slate-700 block mb-2">Lokasi</label>
<input type="text" name="lokasi"
value="{{ old('lokasi', $activity->lokasi) }}"
placeholder="Contoh: Kantor Dinas..."
class="w-full p-3 rounded-xl bg-slate-50 border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition" required>
@error('lokasi') <span class="text-red-500 text-xs mt-1">{{ $message }}</span> @enderror
</div>
</div>
<div>
<label class="text-sm font-semibold text-slate-700 block mb-2">Deskripsi Kegiatan</label>
<textarea name="deskripsi" rows="5"
placeholder="Jelaskan detail kegiatan yang dilakukan..."
class="w-full p-3 rounded-xl bg-slate-50 border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition">{{ old('deskripsi', $activity->deskripsi) }}</textarea>
@error('deskripsi') <span class="text-red-500 text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div>
<label class="text-sm font-semibold text-slate-700 block mb-2">Catatan Status Laporan (Node Petugas)</label>
<textarea name="status_note" rows="3"
placeholder="Contoh: Tugas ini sedang berjalan di desa darit..."
class="w-full p-3 rounded-xl bg-slate-50 border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition">{{ old('status_note', $activity->status_note ?? '') }}</textarea>
@error('status_note') <span class="text-red-500 text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div class="flex flex-wrap items-center gap-4 pt-4 border-t border-slate-100">
<button type="submit"
class="bg-[#0D215C] hover:bg-[#0a1a47] text-white px-8 py-3 rounded-xl font-bold transition shadow-lg shadow-blue-900/10 flex items-center gap-2">
<i data-lucide="save" class="w-4 h-4"></i> Simpan Perubahan
</button>
<a href="{{ route('activity.detail', $activity->id) }}"
class="px-8 py-3 border border-slate-200 text-slate-600 rounded-xl font-semibold hover:bg-slate-50 transition">
Batal
</a>
</div>
</form>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
});
</script>
@endsection
@@ -0,0 +1,113 @@
@extends('user.layouts.app')
@section('content')
{{-- PAGE TITLE --}}
<div style="margin-bottom:28px; display: flex; justify-content: space-between; align-items: flex-end; padding: 0 4px; flex-wrap: wrap; gap: 16px;">
<div>
<div style="font-size:26px; font-weight:700; color:#0D215C; font-family:'Outfit', sans-serif;">
Riwayat Aktivitas
</div>
<div style="font-size:14px; color:#6b7280; margin-top:6px; font-family:'Outfit', sans-serif;">
Daftar seluruh laporan kegiatan yang telah Anda kirimkan.
</div>
</div>
<a href="{{ route('input.activity') }}"
style="text-decoration:none; background:#1d4ed8; color:white; padding:10px 24px; border-radius:10px; font-size:13px; font-weight:600; transition:.2s; display: flex; align-items: center; gap: 8px;">
<i data-lucide="plus" style="width: 16px; height: 16px;"></i>
Input Baru
</a>
</div>
{{-- LIST CONTAINER --}}
<div style="display: flex; flex-direction: column; gap: 16px;">
@forelse($activities as $item)
<div class="card" style="padding: 20px; background: white; border-radius: 16px; border: 1px solid #f1f5f9; box-shadow: 0 1px 3px rgba(0,0,0,0.05); transition: .2s;"
onmouseover="this.style.borderColor='#1d4ed8'; this.style.transform='translateY(-2px)'"
onmouseout="this.style.borderColor='#f1f5f9'; this.style.transform='translateY(0)'">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px;">
<div style="display: flex; gap: 16px; align-items: center;">
{{-- Icon Box --}}
<div style="width: 52px; height: 52px; background: #eff6ff; border-radius: 14px; display: flex; align-items: center; justify-content: center; color: #1d4ed8; flex-shrink: 0;">
<i data-lucide="clipboard-list" style="width: 26px; height: 26px;"></i>
</div>
<div>
<div style="font-size: 17px; font-weight: 700; color: #1e293b; margin-bottom: 6px;">
{{ $item->nama_kegiatan }}
</div>
<div style="display: flex; gap: 15px; align-items: center; flex-wrap: wrap;">
<span style="font-size: 13px; color: #64748b; display: flex; align-items: center; gap: 6px;">
<i data-lucide="user" style="width: 14px; height: 14px;"></i>
<strong style="color: #475569;">{{ $item->user->name ?? 'Pegawai' }}</strong>
</span>
<span style="font-size: 13px; color: #64748b; display: flex; align-items: center; gap: 6px;">
<i data-lucide="calendar" style="width: 14px; height: 14px;"></i>
{{ $item->tanggal ? \Carbon\Carbon::parse($item->tanggal)->translatedFormat('d M Y') : '-' }}
@if(!empty($item->tanggal_selesai))
- {{ \Carbon\Carbon::parse($item->tanggal_selesai)->translatedFormat('d M Y') }}
@endif
</span>
<span style="font-size: 13px; color: #64748b; display: flex; align-items: center; gap: 6px;">
<i data-lucide="map-pin" style="width: 14px; height: 14px;"></i>
{{ $item->lokasi ?? '-' }}
</span>
</div>
</div>
</div>
<div style="display: flex; align-items: center; gap: 20px;">
{{-- Badge Status --}}
@php
$status = $item->status ?? 'on_progres';
$isSelesai = $status === 'selesai';
$isOverdue = !empty($item->tanggal_selesai) && \Carbon\Carbon::parse($item->tanggal_selesai)->isPast() && !$isSelesai && $status !== 'telah_ditinjau';
@endphp
<span style="padding: 6px 14px; border-radius: 99px; font-size: 11px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase;
{{ $isSelesai ? 'background:#dcfce7; color:#15803d;' : ($isOverdue ? 'background:#fef2f2; color:#dc2626; border:1px solid #fecaca;' : 'background:#eff6ff; color:#1d4ed8;') }}">
{{ $isSelesai ? 'Selesai' : ($isOverdue ? 'Terlambat' : 'On Progres') }}
</span>
{{-- Action Button --}}
<a href="{{ route('activity.detail', $item->id) }}"
style="text-decoration: none; color: #1d4ed8; font-weight: 600; font-size: 14px; display: flex; align-items: center; gap: 4px; padding: 8px 12px; border-radius: 8px; transition: .2s;"
onmouseover="this.style.background='#eff6ff'"
onmouseout="this.style.background='transparent'">
Detail
<i data-lucide="chevron-right" style="width: 18px; height: 18px;"></i>
</a>
</div>
</div>
</div>
@empty
{{-- EMPTY STATE --}}
<div style="text-align: center; padding: 80px 20px; background: white; border-radius: 20px; border: 2px dashed #e2e8f0;">
<div style="background: #f8fafc; width: 72px; height: 72px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 20px;">
<i data-lucide="inbox" style="width: 36px; height: 36px; color: #cbd5e1;"></i>
</div>
<div style="font-size: 18px; font-weight: 700; color: #475569;">Belum Ada Aktivitas</div>
<div style="font-size: 14px; color: #94a3b8; margin-top: 6px; max-width: 300px; margin-left: auto; margin-right: auto;">
Laporan kegiatan yang Anda kirimkan nantinya akan muncul di daftar ini.
</div>
<a href="{{ route('input.activity') }}" style="display: inline-block; margin-top: 24px; color: #1d4ed8; font-weight: 600; font-size: 14px; text-decoration: none;">
Mulai buat laporan pertama
</a>
</div>
@endforelse
</div>
{{-- Script untuk memastikan icon Lucide muncul --}}
<script>
document.addEventListener("DOMContentLoaded", function() {
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
});
</script>
@endsection
@@ -0,0 +1,897 @@
<!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>
@@ -0,0 +1,123 @@
@extends('user.layouts.app')
@section('content')
{{-- PAGE TITLE --}}
<div style="margin-bottom:28px;">
<div style="font-size:26px; font-weight:700; color:#111827; font-family:'Outfit', sans-serif;">
Input Laporan Kegiatan
</div>
<div style="font-size:14px; color:#6b7280; margin-top:6px; font-family:'Outfit', sans-serif;">
Pastikan data yang dimasukkan sudah sesuai dengan dokumentasi lapangan.
</div>
</div>
@if ($errors->any())
<div style="background-color: #fee2e2; border-left: 4px solid #ef4444; padding: 16px; margin-bottom: 24px; border-radius: 4px;">
<div style="color: #b91c1c; font-weight: 600; margin-bottom: 8px;">Terdapat kesalahan pada input Anda:</div>
<ul style="color: #b91c1c; font-size: 14px; margin: 0; padding-left: 20px;">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('activities.store') }}" method="POST" enctype="multipart/form-data">
@csrf
{{-- DATA PEGAWAI --}}
<div class="card">
<div style="font-size:13px; font-weight:600; color:#1d4ed8; margin-bottom:16px;
padding-bottom:12px; border-bottom:1px solid #e5e7eb; letter-spacing:0.04em;">
DATA PEGAWAI
</div>
<div class="form-grid">
<div>
<label class="label">Nama Pegawai</label>
<input type="text" value="{{ session('user')['name'] ?? 'Guest' }}" class="input readonly" readonly>
</div>
<div>
<label class="label">Username</label>
<input type="text" value="{{ session('user')['username'] ?? '-' }}" class="input readonly" readonly>
</div>
</div>
</div>
{{-- DETAIL KEGIATAN --}}
<div class="card">
<div style="font-size:13px; font-weight:600; color:#1d4ed8; margin-bottom:16px;
padding-bottom:12px; border-bottom:1px solid #e5e7eb; letter-spacing:0.04em;">
DETAIL KEGIATAN
</div>
<div>
<label class="label">Judul Kegiatan</label>
<input type="text" name="nama_kegiatan" class="input" placeholder="Monitoring perusahaan..." required>
</div>
<div class="form-grid" style="margin-top:16px;">
<div>
<label class="label">Tanggal</label>
<input type="date" name="tanggal" class="input" required>
</div>
<div>
<label class="label">Lokasi</label>
<input type="text" name="lokasi" class="input" placeholder="Kota / Alamat" required>
</div>
</div>
<div style="margin-top:16px;">
<label class="label">Detail Kegiatan</label>
<textarea name="deskripsi" class="textarea" placeholder="Tulis laporan lengkap di sini..." required></textarea>
</div>
</div>
{{-- UPLOAD --}}
<div class="card">
<div style="font-size:13px; font-weight:600; color:#1d4ed8; margin-bottom:16px;
padding-bottom:12px; border-bottom:1px solid #e5e7eb; letter-spacing:0.04em;">
LAMPIRAN DOKUMENTASI
</div>
<label class="upload-box" id="upload-label">
<input type="file" name="image" id="file-input" accept=".pdf,.jpg,.jpeg,.png" hidden>
{{-- Icon Upload --}}
<svg width="36" height="36" viewBox="0 0 24 24" fill="none"
stroke="#9ca3af" stroke-width="1.5" style="margin:0 auto 10px;">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="17 8 12 3 7 8"/>
<line x1="12" y1="3" x2="12" y2="15"/>
</svg>
<p style="font-weight:600; font-size:14px; color:#374151; margin-bottom:4px;" id="file-name">
Klik untuk upload file
</p>
<small style="color:#9ca3af; font-size:12px;">PDF / JPG / PNG · Maksimal 2MB</small>
</label>
</div>
{{-- SUBMIT --}}
<button class="btn" type="submit">
Kirim Laporan
</button>
</form>
{{-- Script: tampilkan nama file setelah dipilih --}}
<script>
document.getElementById('file-input').addEventListener('change', function() {
const fileName = this.files[0] ? this.files[0].name : 'Klik untuk upload file';
document.getElementById('file-name').textContent = fileName;
});
</script>
@endsection
@@ -0,0 +1,215 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Panel Petugas Disnakertrans Prov. Kalbar</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<style>
/* ─── RESET + FONT ─── */
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Outfit', sans-serif;
}
body {
background: #F2F5FB;
color: #334155;
}
/* ─── SCROLLBAR ─── */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: #f1f5f9; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
/* ─── VARIABLES ─── */
:root {
--primary: #1d4ed8;
--primary-soft: #e0e7ff;
--border: #e5e7eb;
--text: #111827;
--muted: #6b7280;
}
/* ─── CARD ─── */
.card {
background: #fff;
border: 1px solid var(--border);
border-radius: 16px;
padding: 28px;
margin-bottom: 20px;
}
/* ─── FORM GRID ─── */
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
/* ─── LABEL ─── */
.label {
font-size: 12px;
font-weight: 600;
color: var(--muted);
margin-bottom: 6px;
display: block;
letter-spacing: 0.03em;
}
/* ─── INPUT ─── */
.input {
width: 100%;
padding: 12px 14px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
font-family: 'Outfit', sans-serif;
background: #fff;
color: var(--text);
transition: .2s;
}
.input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(29,78,216,.1);
}
.readonly {
background: #f3f4f6;
color: #6b7280;
cursor: not-allowed;
}
/* ─── TEXTAREA ─── */
.textarea {
width: 100%;
padding: 14px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
font-family: 'Outfit', sans-serif;
color: var(--text);
min-height: 140px;
resize: vertical;
transition: .2s;
}
.textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(29,78,216,.1);
}
/* ─── UPLOAD BOX ─── */
.upload-box {
border: 2px dashed var(--border);
padding: 30px;
text-align: center;
border-radius: 14px;
cursor: pointer;
display: block;
transition: .2s;
}
.upload-box:hover {
background: #f9fafb;
border-color: var(--primary);
}
/* ─── BUTTON ─── */
.btn {
width: 100%;
padding: 14px;
border: none;
border-radius: 12px;
background: var(--primary);
color: #fff;
font-weight: 600;
font-size: 15px;
font-family: 'Outfit', sans-serif;
cursor: pointer;
transition: .2s;
letter-spacing: 0.02em;
}
.btn:hover {
background: #1e40af;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(29,78,216,.3);
}
.btn:active {
transform: translateY(0);
}
/* ─── RESPONSIVE ─── */
@media (max-width: 768px) {
.form-grid {
grid-template-columns: 1fr;
}
main {
padding: 20px 0 !important;
}
.card {
padding: 20px;
}
}
</style>
</head>
<body>
@include('user.layouts.navbar')
<main style="padding: 40px 0;">
<div style="max-width: 860px; margin: auto; padding: 0 24px;">
@yield('content')
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
lucide.createIcons();
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('[onclick^="return confirm"]').forEach(function(btn) {
const match = btn.getAttribute('onclick').match(/confirm\('([^']+)'\)/);
if (match) {
const message = match[1];
btn.removeAttribute('onclick');
btn.addEventListener('click', function(e) {
e.preventDefault();
const form = btn.closest('form');
Swal.fire({
title: 'Konfirmasi',
text: message,
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#3b82f6',
cancelButtonColor: '#64748b',
confirmButtonText: 'Ya, Lanjutkan',
cancelButtonText: 'Batal',
reverseButtons: true,
customClass: {
confirmButton: 'rounded-xl',
cancelButton: 'rounded-xl mr-2'
}
}).then((result) => {
if (result.isConfirmed) {
if (form) {
form.submit();
} else if(btn.getAttribute('href')) {
window.location.href = btn.getAttribute('href');
}
}
});
});
}
});
});
</script>
</body>
</html>
@@ -0,0 +1,136 @@
<nav class="bg-[#f8fafc] border-b border-slate-200 font-['Outfit'] sticky top-0 z-50 shadow-sm">
<div class="max-w-[1200px] mx-auto px-4 md:px-6 lg:px-10">
<div class="flex items-center justify-between h-[70px]">
<!-- LEFT: LOGO -->
<div class="flex items-center gap-2 md:gap-3">
<img src="{{ asset('Kalimantan-Barat-Logo-Vector.png') }}" class="h-9 md:h-[42px] w-auto object-contain" alt="Logo Kalbar">
<div class="leading-[1.2] border-l-2 border-slate-200 pl-2 md:pl-3">
<div class="font-bold text-[11px] md:text-[14px] text-blue-900 tracking-[0.02em]">
DINAS TENAGA KERJA DAN TRANSMIGRASI
</div>
<div class="text-[8px] md:text-[11px] text-slate-500 font-medium">
PROVINSI KALIMANTAN BARAT
</div>
</div>
</div>
<!-- Hamburger Button (Mobile) -->
<div class="flex md:hidden">
<button type="button" id="user-menu-btn" class="text-slate-500 hover:text-blue-900 focus:outline-none p-2 rounded-md hover:bg-slate-200 transition-colors">
<i data-lucide="menu" class="w-6 h-6"></i>
</button>
</div>
<!-- CENTER & RIGHT: NAVIGATION (Desktop) -->
<div class="hidden md:flex items-center gap-6">
<div class="flex items-center gap-2 text-[14px]">
<a href="{{ route('user.dashboard') }}"
class="flex items-center gap-1.5 px-4 py-2 rounded-lg transition-all duration-200 {{ request()->routeIs('user.dashboard') ? 'text-blue-800 bg-indigo-50 font-semibold' : 'text-slate-500 font-medium hover:bg-slate-100 hover:text-blue-800' }}">
<i data-lucide="layout-dashboard" class="w-4 h-4"></i> Dashboard
</a>
<a href="{{ route('input.activity') }}"
class="flex items-center gap-1.5 px-4 py-2 rounded-lg transition-all duration-200 {{ request()->routeIs('input.activity') ? 'text-blue-800 bg-indigo-50 font-semibold' : 'text-slate-500 font-medium hover:bg-slate-100 hover:text-blue-800' }}">
<i data-lucide="edit" class="w-4 h-4"></i> Input Kegiatan
</a>
<a href="{{ route('activity.list') }}"
class="flex items-center gap-1.5 px-4 py-2 rounded-lg transition-all duration-200 {{ request()->routeIs('activity.list') ? 'text-blue-800 bg-indigo-50 font-semibold' : 'text-slate-500 font-medium hover:bg-slate-100 hover:text-blue-800' }}">
<i data-lucide="folder" class="w-4 h-4"></i> Kegiatan Saya
</a>
<a href="{{ route('settings') ?? '#' }}"
class="flex items-center gap-1.5 px-4 py-2 rounded-lg transition-all duration-200 {{ request()->routeIs('settings') ? 'text-blue-800 bg-indigo-50 font-semibold' : 'text-slate-500 font-medium hover:bg-slate-100 hover:text-blue-800' }}">
<i data-lucide="settings" class="w-4 h-4"></i> Pengaturan
</a>
</div>
<div class="w-px h-8 bg-slate-300"></div>
<div class="flex items-center gap-2.5">
<div class="w-[38px] h-[38px] rounded-full bg-blue-900 text-white flex items-center justify-center font-bold text-[16px]">
{{ strtoupper(substr(session('user')['name'] ?? 'U', 0, 1)) }}
</div>
<div class="leading-[1.2]">
<div class="font-semibold text-[14px] text-slate-900">
{{ session('user')['name'] ?? 'User' }}
</div>
<div class="text-[11px] text-slate-500 font-medium">
USER
</div>
</div>
<form method="POST" action="{{ route('logout') }}" class="ml-2 m-0">
@csrf
<button type="submit" class="bg-transparent border-none cursor-pointer text-red-500 p-1.5 rounded-md hover:bg-red-50 transition-colors" title="Logout">
<i data-lucide="log-out" class="w-[18px] h-[18px]"></i>
</button>
</form>
</div>
</div>
</div>
</div>
<!-- Mobile Menu Panel -->
<div id="user-mobile-menu" class="hidden md:hidden bg-[#f8fafc] border-t border-slate-200 shadow-xl absolute w-full left-0 pb-4">
<div class="px-4 py-3 flex flex-col gap-2">
<a href="{{ route('user.dashboard') }}"
class="flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 {{ request()->routeIs('user.dashboard') ? 'text-blue-800 bg-indigo-50 font-semibold' : 'text-slate-600 font-medium hover:bg-slate-200' }}">
<i data-lucide="layout-dashboard" class="w-4 h-4"></i> Dashboard
</a>
<a href="{{ route('input.activity') }}"
class="flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 {{ request()->routeIs('input.activity') ? 'text-blue-800 bg-indigo-50 font-semibold' : 'text-slate-600 font-medium hover:bg-slate-200' }}">
<i data-lucide="edit" class="w-4 h-4"></i> Input Kegiatan
</a>
<a href="{{ route('activity.list') }}"
class="flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 {{ request()->routeIs('activity.list') ? 'text-blue-800 bg-indigo-50 font-semibold' : 'text-slate-600 font-medium hover:bg-slate-200' }}">
<i data-lucide="folder" class="w-4 h-4"></i> Kegiatan Saya
</a>
<a href="{{ route('settings') ?? '#' }}"
class="flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 {{ request()->routeIs('settings') ? 'text-blue-800 bg-indigo-50 font-semibold' : 'text-slate-600 font-medium hover:bg-slate-200' }}">
<i data-lucide="settings" class="w-4 h-4"></i> Pengaturan
</a>
<div class="h-px w-full bg-slate-200 my-2"></div>
<div class="flex items-center justify-between px-2">
<div class="flex items-center gap-3">
<div class="w-[38px] h-[38px] rounded-full bg-blue-900 text-white flex items-center justify-center font-bold text-[16px]">
{{ strtoupper(substr(session('user')['name'] ?? 'U', 0, 1)) }}
</div>
<div class="leading-[1.2]">
<div class="font-semibold text-[14px] text-slate-900">
{{ session('user')['name'] ?? 'User' }}
</div>
<div class="text-[11px] text-slate-500 font-medium">
USER
</div>
</div>
</div>
<form method="POST" action="{{ route('logout') }}" class="m-0">
@csrf
<button type="submit" class="bg-transparent border-none cursor-pointer text-red-500 p-2 rounded-md hover:bg-red-50 transition-colors flex items-center gap-2" title="Logout">
<i data-lucide="log-out" class="w-5 h-5"></i>
</button>
</form>
</div>
</div>
</div>
</nav>
<script>
document.addEventListener('DOMContentLoaded', function() {
const btn = document.getElementById('user-menu-btn');
const menu = document.getElementById('user-mobile-menu');
if(btn && menu) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
menu.classList.toggle('hidden');
lucide.createIcons();
});
document.addEventListener('click', function(e) {
if(!menu.contains(e.target) && !btn.contains(e.target)) {
menu.classList.add('hidden');
}
});
}
});
</script>
@@ -0,0 +1,173 @@
@php
$role = session('user')['role'] ?? 'user';
$layout = in_array($role, ['admin', 'superadmin']) ? 'admin.layouts.app' : 'user.layouts.app';
if ($role === 'superadmin') {
$dashboardRoute = route('admin.superadmin.dashboard');
} elseif ($role === 'admin') {
$dashboardRoute = route('admin.dashboard');
} else {
$dashboardRoute = route('user.dashboard');
}
@endphp
@extends($layout)
@section('content')
<div class="max-w-4xl mx-auto mt-10 px-4 pb-20">
{{-- HEADER --}}
<div class="mb-8">
<a href="{{ $dashboardRoute }}" class="inline-flex items-center gap-2 text-sm text-slate-500 hover:text-[#0D215C] font-semibold mb-4 transition">
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path d="M19 12H5M12 5l-7 7 7 7"/></svg>
Kembali ke Dashboard
</a>
<h2 class="text-2xl font-bold text-[#0D215C]">
Pengaturan Profil
</h2>
<p class="text-sm text-slate-400">Kelola informasi akun dan kata sandi Anda.</p>
</div>
@if(session('success'))
<div class="mb-6 p-4 bg-green-100 border border-green-200 text-green-700 rounded-xl text-sm font-medium">
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="mb-6 p-4 bg-red-100 border border-red-200 text-red-700 rounded-xl text-sm font-medium">
{{ session('error') }}
</div>
@endif
@if($errors->any())
<div class="mb-6 p-4 bg-red-100 border border-red-200 text-red-700 rounded-xl text-sm font-medium">
<ul class="list-disc list-inside">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="grid grid-cols-1 gap-8">
{{-- FORM INFORMASI PROFIL --}}
<div class="bg-white p-8 rounded-2xl shadow-sm border border-slate-100">
<h4 class="text-lg font-bold text-[#0D215C] mb-6 flex items-center gap-2">
<i data-lucide="user" class="w-5 h-5"></i> Informasi Pribadi
</h4>
<form action="{{ route('settings.update') }}" method="POST" class="space-y-5">
@csrf
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="text-sm font-semibold text-slate-700 block mb-2">Nama Lengkap</label>
<input type="text" name="name" value="{{ session('user')['name'] ?? '' }}"
class="w-full p-3 rounded-xl bg-slate-50 border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition" readonly>
</div>
<div>
<label class="text-sm font-semibold text-slate-700 block mb-2">Username <span class="text-red-500">*</span></label>
<input type="text" name="username" value="{{ session('user')['username'] ?? '' }}"
class="w-full p-3 rounded-xl bg-slate-50 border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition">
</div>
</div>
<div class="pt-4">
<button type="submit" class="bg-[#0D215C] hover:bg-[#0a1a47] text-white px-8 py-3 rounded-xl font-bold transition shadow-lg shadow-blue-900/10">
Simpan Perubahan
</button>
</div>
</form>
</div>
@if($role !== 'user')
{{-- FORM GANTI PASSWORD --}}
<div class="bg-white p-8 rounded-2xl shadow-sm border border-slate-100">
<h4 class="text-lg font-bold text-[#0D215C] mb-6 flex items-center gap-2">
<i data-lucide="lock" class="w-5 h-5"></i> Keamanan Akun
</h4>
<form action="{{ route('settings.update') }}" method="POST" class="space-y-5">
@csrf
<input type="hidden" name="username" value="{{ session('user')['username'] ?? '' }}">
<div>
<label class="text-sm font-semibold text-slate-700 block mb-2">Kata Sandi Saat Ini</label>
<div class="relative">
<input type="password" name="current_password" id="current_password" autocomplete="off"
class="w-full p-3 pr-10 rounded-xl bg-slate-50 border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition">
<button type="button" onclick="togglePassword('current_password', this)" class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-blue-500 focus:outline-none">
<i data-lucide="eye" class="w-5 h-5 eye-icon"></i>
<i data-lucide="eye-off" class="w-5 h-5 eye-off-icon hidden"></i>
</button>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="text-sm font-semibold text-slate-700 block mb-2">Kata Sandi Baru</label>
<div class="relative">
<input type="password" name="password" id="password"
class="w-full p-3 pr-10 rounded-xl bg-slate-50 border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition">
<button type="button" onclick="togglePassword('password', this)" class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-blue-500 focus:outline-none">
<i data-lucide="eye" class="w-5 h-5 eye-icon"></i>
<i data-lucide="eye-off" class="w-5 h-5 eye-off-icon hidden"></i>
</button>
</div>
</div>
<div>
<label class="text-sm font-semibold text-slate-700 block mb-2">Konfirmasi Kata Sandi</label>
<div class="relative">
<input type="password" name="password_confirmation" id="password_confirmation"
class="w-full p-3 pr-10 rounded-xl bg-slate-50 border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition">
<button type="button" onclick="togglePassword('password_confirmation', this)" class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-blue-500 focus:outline-none">
<i data-lucide="eye" class="w-5 h-5 eye-icon"></i>
<i data-lucide="eye-off" class="w-5 h-5 eye-off-icon hidden"></i>
</button>
</div>
</div>
</div>
<div class="pt-4">
<button type="submit" class="bg-white border border-slate-200 hover:bg-slate-50 text-[#0D215C] px-8 py-3 rounded-xl font-bold transition">
Perbarui Kata Sandi
</button>
</div>
</form>
</div>
@endif
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
});
function togglePassword(inputId, btn) {
const input = document.getElementById(inputId);
const eye = btn.querySelector('.eye-icon');
const eyeOff = btn.querySelector('.eye-off-icon');
if (input.type === 'password') {
input.type = 'text';
eye.classList.add('hidden');
eyeOff.classList.remove('hidden');
btn.classList.add('text-blue-500');
btn.classList.remove('text-slate-400');
} else {
input.type = 'password';
eye.classList.remove('hidden');
eyeOff.classList.add('hidden');
btn.classList.remove('text-blue-500');
btn.classList.add('text-slate-400');
}
}
</script>
@endsection