sidakpelem/resources/views/landing/components/features.blade.php

504 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<section id="features" class="features section">
<div class="container section-title" data-aos="fade-up">
<h2>Informasi</h2>
<p>halooo Warga Desa Pelem Ayoo, inilo fiturnya ayooo</p>
</div>
<div class="container">
<div class="d-flex justify-content-center">
<ul class="nav nav-tabs" data-aos="fade-up" data-aos-delay="100">
<li class="nav-item">
<a class="nav-link active show" data-bs-toggle="tab" data-bs-target="#features-tab-1">
<h4>Kehadiran Pegawai</h4>
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" data-bs-target="#features-tab-2">
<h4>Struktur Desa</h4>
</a>
</li>
{{-- <li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" data-bs-target="#features-tab-3">
<h4>Pengumuman & berita</h4>
</a>
</li> --}}
</ul>
</div>
<div class="tab-content" data-aos="fade-up" data-aos-delay="200">
<div class="tab-pane fade active show" id="features-tab-1">
<div class="row">
<div class="col-12">
<div class="card shadow-sm border-0">
<div class="card-header bg-white">
<h4 class="mb-0">Kehadiran Pegawai ·
{{ \Carbon\Carbon::today()->translatedFormat('d F Y') }}</h4>
<small class="text-muted">Realtime update tanpa refresh</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table align-middle">
<thead class="table-light">
<tr>
<th>Nama</th>
<th>Jabatan</th>
<th>Phone</th>
<th>Tanggal</th>
<th>Check-In</th>
<th>Check-Out</th>
<th>Status</th>
</tr>
</thead>
<tbody id="members-body">
@foreach ($members as $m)
@php
// Normalisasi status
$statusLower = strtolower($m['status'] ?? '');
// Mapping: status => [bg-class, text-class, label]
$badgeMap = [
'hadir' => ['bg-success-subtle', 'text-success', 'Hadir'],
'izin' => ['bg-warning-subtle', 'text-warning', 'Izin'],
'sakit' => ['bg-info-subtle', 'text-info', 'Sakit'],
'alpha' => ['bg-danger-subtle', 'text-danger', 'Alpha'],
];
// Ambil entry, fallback ke 'alpha'
[$bgClass, $textClass, $label] =
$badgeMap[$statusLower] ?? $badgeMap['alpha'];
@endphp
<tr data-user-id="{{ $m['id'] }}">
<td data-col="name">{{ $m['name'] }}</td>
<td data-col="role">{{ $m['role'] }}</td>
<td data-col="phone">{{ $m['phone'] }}</td>
<td data-col="date">{{ $m['date'] }}</td>
<td data-col="checkin">{{ $m['checkin'] }}</td>
<td data-col="checkout">{{ $m['checkout'] ?? '-' }}</td>
<td data-col="status">
<span
class="badge {{ $bgClass }} {{ $textClass }} fw-semibold px-3 py-2">
{{ $label }}
</span>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div id="attn-hint" class="small text-muted"></div>
</div>
</div>
</div>
</div>
</div>
{{-- Menampilkan Bagan Struktur Organisasi --}}
<div class="tab-content" data-aos="fade-up" data-aos-delay="200">
<div class="tab-pane fade" id="features-tab-2">
<div class="row justify-content-center">
<div class="col-lg-10 text-center">
<img src="{{ asset('assets/images/bagandesa.jpg') }}" alt="Struktur Organisasi Desa"
class="img-fluid rounded shadow-lg mb-3"
style="max-height: 500px; object-fit: contain; border: 4px solid #f8f9fa;">
<p class="fst-italic text-muted mt-3">Bagan struktur organisasi Desa Pelem</p>
</div>
</div>
</div>
</div>
{{-- Menampilkan Pengumuman dan Berita --}}
{{-- <div class="tab-pane fade" id="features-tab-3">
<div class="row">
<div class="col-12">
<div class="d-flex flex-wrap justify-content-center gap-2 mb-4" id="category-buttons">
<button class="btn btn-primary btn-sm px-3 py-2 category-filter" data-category="all">
Semua Kategori
</button>
</div>
<div class="row" id="news-grid">
<div class="col-12 text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2 text-muted">Memuat berita...</p>
</div>
</div>
<div class="text-center mt-4" id="load-more-container" style="display: none;">
<button class="btn btn-outline-primary" id="load-more-btn">
Muat Berita Lainnya
</button>
</div>
</div>
</div>
</div> --}}
</div>
</div>
</section>
<style>
.news-card {
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
border-radius: 12px;
overflow: hidden;
}
.news-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1) !important;
}
.category-filter {
transition: all 0.2s ease-in-out;
border-radius: 20px;
}
.category-filter:hover {
transform: translateY(-1px);
}
/* === PERUBAHAN DIMULAI DI SINI === */
/* Mengubah warna solid button menjadi hijau */
.features .btn-primary {
background: #077a7d;
border-color: #077a7d;
}
/* Mengubah warna outline button (border dan teks) menjadi hijau */
.features .btn-outline-primary {
color: #077a7d;
border-color: #077a7d;
}
/* Mengubah warna background saat hover pada outline button */
.features .btn-outline-primary:hover {
background-color: #077a7d;
color: #fff;
/* Agar teks menjadi putih saat di-hover */
}
/* Mengubah warna spinner loading menjadi hijau */
.features .text-primary {
color: #077a7d !important;
/* !important diperlukan untuk menimpa utility class bootstrap */
}
/* === PERUBAHAN SELESAI === */
#news-grid {
min-height: 200px;
}
.spinner-border {
width: 3rem;
height: 3rem;
}
.card-img-top {
transition: transform 0.3s ease-in-out;
}
.news-card:hover .card-img-top {
transform: scale(1.05);
}
/* .badge {
font-size: 0.75rem;
padding: 0.5rem 0.75rem;
border-radius: 12px;
} */
</style>
<script src="https://js.pusher.com/8.4.0/pusher.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/laravel-echo@1.16.1/dist/echo.iife.js"></script>
<script>
// Realtime Absensi
(function() {
// helper badge
function statusBadge(status) {
const s = (status || '').toLowerCase().trim();
// 1) Jika kosong, tampilkan neutral "-"
if (!s) {
return '<span class="badge bg-secondary-subtle text-secondary fw-semibold px-3 py-2">-</span>';
}
// 2) Kondisi khusus
switch (s) {
case 'hadir':
return '<span class="badge bg-success-subtle text-success fw-semibold px-3 py-2">Hadir</span>';
// Izin disetujui maupun "izin" biasa
case 'izin':
case 'izin_disetujui':
return '<span class="badge bg-warning-subtle text-warning fw-semibold px-3 py-2">Izin</span>';
case 'sakit':
return '<span class="badge bg-info-subtle text-info fw-semibold px-3 py-2">Sakit</span>';
case 'alpha':
return '<span class="badge bg-danger-subtle text-danger fw-semibold px-3 py-2">Alpha</span>';
// 3) Semua selain di atas → neutral
default:
return '<span class="badge bg-secondary-subtle text-secondary fw-semibold px-3 py-2">-</span>';
}
}
// format time helper
function formatTime(val) {
if (!val) return '-';
const d = new Date(val);
if (isNaN(d.getTime())) return val; // fallback jika bukan ISO format
return d.toLocaleTimeString('id-ID', {
hour: '2-digit',
minute: '2-digit'
});
}
// patch 1 baris di tabel
function patchAttendanceRow(update) {
const {
user_id,
status,
check_in,
check_out
} = update;
const tr = document.querySelector(`tr[data-user-id="${user_id}"]`);
if (!tr) return;
const tdCheckin = tr.querySelector('[data-col="checkin"]');
const tdCheckout = tr.querySelector('[data-col="checkout"]');
const tdStatus = tr.querySelector('[data-col="status"]');
if (tdCheckin) tdCheckin.textContent = formatTime(check_in);
if (tdCheckout) tdCheckout.textContent = formatTime(check_out);
if (tdStatus) tdStatus.innerHTML = statusBadge(status);
}
// refresh penuh tabel
async function refreshMembers() {
try {
const res = await fetch('/api/attendance/today', {
cache: 'no-store'
});
if (!res.ok) return;
const rows = await res.json();
const today = new Date().toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric'
});
const html = rows.map(r => `
<tr data-user-id="${r.id}">
<td data-col="name">${r.name ?? '-'}</td>
<td data-col="role">${r.role ?? '-'}</td>
<td data-col="phone">${r.phone ?? '-'}</td>
<td data-col="date">${today}</td>
<td data-col="checkin">${formatTime(r.check_in)}</td>
<td data-col="checkout">${formatTime(r.check_out)}</td>
<td data-col="status">${statusBadge(r.status)}</td>
</tr>
`).join('');
document.getElementById('members-body').innerHTML = html;
console.log('[TABLE] Updated');
} catch (e) {
console.warn('[TABLE] Refresh failed', e);
}
}
// init Echo Reverb
window.Pusher = window.Pusher || Pusher;
const echo = new window.Echo({
broadcaster: 'pusher',
key: "{{ env('PUSHER_APP_KEY') }}",
cluster: "{{ env('PUSHER_APP_CLUSTER', 'ap1') }}",
forceTLS: "{{ env('PUSHER_SCHEME', 'https') }}" === 'https',
});
echo.channel('attendance.global')
.subscribed(() => {
console.log('[Echo] Subscribed: attendance.global');
document.getElementById('attn-hint').textContent = 'Realtime terhubung';
})
.error(err => console.error('[Echo] Channel error:', err))
.listen('.attendance.updated', e => {
console.log('[Attendance] Update:', e);
patchAttendanceRow(e);
});
setInterval(refreshMembers, 60_000); // sync tiap menit
refreshMembers(); // initial load
})();
document.addEventListener('DOMContentLoaded', function() {
let currentPage = 1;
let currentCategory = 'all';
let loading = false;
// Load categories and initial news
loadCategories();
loadNews();
// Load categories dynamically
function loadCategories() {
console.log('Loading categories...');
fetch('/api/news/categories')
.then(response => {
console.log('Categories response status:', response.status);
return response.json();
})
.then(categories => {
console.log('Categories loaded:', categories);
const categoryButtons = document.getElementById('category-buttons');
const allButton = categoryButtons.querySelector('[data-category="all"]');
// Clear existing buttons except "Semua Kategori"
categoryButtons.innerHTML = '';
categoryButtons.appendChild(allButton);
// Add dynamic category buttons
categories.forEach(category => {
const button = document.createElement('button');
button.className =
'btn btn-outline-primary btn-sm px-3 py-2 category-filter';
button.setAttribute('data-category', category);
button.textContent = category;
button.addEventListener('click', handleCategoryClick);
categoryButtons.appendChild(button);
});
})
.catch(error => {
console.error('Error loading categories:', error);
// Fallback to default categories if API fails
const defaultCategories = ['Infrastruktur', 'Ekonomi', 'Kesehatan', 'Budaya',
'Pemerintahan', 'Pendidikan', 'Lingkungan', 'Teknologi'
];
const categoryButtons = document.getElementById('category-buttons');
const allButton = categoryButtons.querySelector('[data-category="all"]');
categoryButtons.innerHTML = '';
categoryButtons.appendChild(allButton);
defaultCategories.forEach(category => {
const button = document.createElement('button');
button.className =
'btn btn-outline-primary btn-sm px-3 py-2 category-filter';
button.setAttribute('data-category', category);
button.textContent = category;
button.addEventListener('click', handleCategoryClick);
categoryButtons.appendChild(button);
});
// Also try to load news with fallback
//loadNewsWithFallback();
});
}
// Category filter click handler
function handleCategoryClick() {
const category = this.dataset.category;
// Update button states
document.querySelectorAll('.category-filter').forEach(btn => {
btn.classList.remove('btn-primary');
btn.classList.add('btn-outline-primary');
});
this.classList.remove('btn-outline-primary');
this.classList.add('btn-primary');
// Reset and load news
currentCategory = category;
currentPage = 1;
document.getElementById('news-grid').innerHTML = '';
loadNews();
}
// Add event listener to "Semua Kategori" button
document.querySelector('[data-category="all"]').addEventListener('click', handleCategoryClick);
// Load more button handler
document.getElementById('load-more-btn').addEventListener('click', function() {
if (!loading) {
currentPage++;
loadNews(true);
}
});
function loadNews(append = false) {
if (loading) return;
loading = true;
// Show loading indicator
if (!append) {
document.getElementById('news-grid').innerHTML = `
<div class="col-12 text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2 text-muted">Memuat berita...</p>
</div>
`;
}
const url = new URL('/news', window.location.origin);
url.searchParams.set('category', currentCategory);
url.searchParams.set('page', currentPage);
url.searchParams.set('ajax', '1');
console.log('Fetching URL:', url.toString());
fetch(url, {
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json',
'Content-Type': 'application/json',
}
})
.then(response => {
console.log('Response status:', response.status);
console.log('Response headers:', response.headers);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text().then(text => {
console.log('Response text:', text);
try {
return JSON.parse(text);
} catch (e) {
console.error('JSON parse error:', e);
throw new Error('Invalid JSON response');
}
});
})
.then(data => {
if (append) {
document.getElementById('news-grid').innerHTML += data.html;
} else {
document.getElementById('news-grid').innerHTML = data.html;
}
// Show/hide load more button
if (data.hasMore) {
document.getElementById('load-more-container').style.display = 'block';
} else {
document.getElementById('load-more-container').style.display = 'none';
}
loading = false;
})
.catch(error => {
console.error('Error loading news:', error);
});
}
});
</script>