407 lines
23 KiB
PHP
407 lines
23 KiB
PHP
<x-app-layout>
|
|
@push('styles')
|
|
<link rel="stylesheet" href="{{ asset('css/akunteknisi.css') }}">
|
|
<link rel="stylesheet" href="{{ asset('css/tugas.css') }}">
|
|
<style>
|
|
.akt-inner { max-width: 1400px; }
|
|
.time-select {
|
|
border-radius: 8px; border: 1px solid #e2e8f0; padding: 8px;
|
|
font-family: var(--akt-font); font-size: 13px; outline: none;
|
|
cursor: pointer; background: #fff;
|
|
}
|
|
.time-select:focus { border-color: var(--akt-green); }
|
|
|
|
.badge-kategori {
|
|
padding: 4px 10px; border-radius: 8px; font-size: 10px; font-weight: 800;
|
|
display: inline-flex; align-items: center; gap: 5px;
|
|
}
|
|
.badge-urgent { background: #fffbeb; color: #b45309; border: 1px solid #fde68a; }
|
|
.badge-normal { background: #f0fdf4; color: #15803d; border: 1px solid #bbf7d0; }
|
|
|
|
/* Override tugas.css for consistency with Data Teknisi */
|
|
.pug-stats { grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 30px; }
|
|
.pug-stat { padding: 24px; border-radius: 20px; }
|
|
</style>
|
|
@endpush
|
|
|
|
<div class="akt-wrap">
|
|
<div class="akt-inner">
|
|
{{-- ── PAGE HEADER ── --}}
|
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:30px;">
|
|
<div style="display:flex; align-items:center; gap:15px;">
|
|
<div class="pug-icon-wrap" style="width: 54px; height: 54px; font-size: 22px;">
|
|
<i class="fas fa-user-check"></i>
|
|
</div>
|
|
<div>
|
|
<h1 class="pug-page-title" style="font-size: 24px;">Monitoring Absensi</h1>
|
|
<p class="pug-page-sub">Kelola & monitor kehadiran teknisi lapangan</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ── STATISTICS CARDS ── --}}
|
|
<div class="pug-stats">
|
|
<div class="pug-stat pug-stat-total" onclick="setStatus('')" style="cursor: pointer;" title="Semua Data">
|
|
<div class="pug-stat-icon"><i class="fas fa-users"></i></div>
|
|
<div class="pug-stat-label">TOTAL ABSENSI</div>
|
|
<div class="pug-stat-val">{{ $counts['total'] ?? 0 }}</div>
|
|
<div class="pug-page-sub" style="margin-top: 5px;">Semua teknisi terdaftar</div>
|
|
</div>
|
|
<div class="pug-stat pug-stat-selesai" onclick="setStatus('hadir')" style="cursor: pointer;" title="Filter: Hadir">
|
|
<div class="pug-stat-icon" style="background: var(--pug-green-l); color: var(--pug-green);"><i class="fas fa-check-circle"></i></div>
|
|
<div class="pug-stat-label">HADIR</div>
|
|
<div class="pug-stat-val">{{ $counts['hadir'] ?? 0 }}</div>
|
|
<div class="pug-page-sub" style="margin-top: 5px;">Teknisi yang sudah masuk</div>
|
|
</div>
|
|
<div class="pug-stat pug-stat-proses" onclick="setStatus('izin')" style="cursor: pointer;" title="Filter: Izin/Sakit">
|
|
<div class="pug-stat-icon" style="background: var(--pug-violet-l); color: var(--pug-violet);"><i class="fas fa-info-circle"></i></div>
|
|
<div class="pug-stat-label">IZIN / SAKIT</div>
|
|
<div class="pug-stat-val">{{ $counts['izin'] ?? 0 }}</div>
|
|
<div class="pug-page-sub" style="margin-top: 5px;">Izin berhalangan kerja</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toolbar Filter -->
|
|
<form action="{{ route('absensi.index') }}" method="GET" class="pug-toolbar" style="margin-bottom: 24px; padding: 18px 24px;">
|
|
<input type="hidden" name="status" id="statusInput" value="{{ request('status') }}">
|
|
<div class="pug-toolbar-form">
|
|
<div class="pug-toolbar-item" style="flex: 0 0 auto;">
|
|
<label class="pug-field-label">Rentang Tanggal</label>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<input type="date" name="start_date" class="pug-input" value="{{ request('start_date') }}" style="width: 150px; padding: 10px 8px;">
|
|
<span class="text-muted small">s/d</span>
|
|
<input type="date" name="end_date" class="pug-input" value="{{ request('end_date') }}" style="width: 150px; padding: 10px 8px;">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pug-toolbar-item" style="flex: 1; min-width: 200px;">
|
|
<label class="pug-field-label">Cari Teknisi</label>
|
|
<div class="pug-input-icon">
|
|
<i class="fas fa-user-tie"></i>
|
|
<select name="teknisi" class="pug-input" onchange="this.form.submit()">
|
|
<option value="">Semua Teknisi</option>
|
|
@foreach($teknisis as $t)
|
|
<option value="{{ $t->id_teknisi }}" {{ request('teknisi') == $t->id_teknisi ? 'selected' : '' }}>{{ $t->nama }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pug-toolbar-item item-btn" style="flex: 0 0 auto;">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<a href="{{ route('absensi.index') }}" class="pug-reset-btn" style="height: 44px;">
|
|
<i class="fas fa-undo"></i> Reset
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
{{-- ── PANEL TABLE ── --}}
|
|
<div class="pug-panel">
|
|
<div class="pug-panel-head">
|
|
<div class="pug-panel-head-left">
|
|
<i class="fas fa-table"></i> DAFTAR LIST ABSENSI
|
|
</div>
|
|
<div class="pug-panel-head-right">
|
|
Menampilkan {{ $absensis->total() }} data absensi
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="pug-table">
|
|
<thead>
|
|
<tr>
|
|
<th width="60">#</th>
|
|
<th>Teknisi</th>
|
|
<th>Tanggal</th>
|
|
<th>Jam Masuk</th>
|
|
<th>Jam Keluar</th>
|
|
<th>Durasi</th>
|
|
<th>Status</th>
|
|
<th width="100">Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($absensis as $index => $abs)
|
|
<tr>
|
|
<td>
|
|
<span class="pug-rownum">{{ str_pad($absensis->firstItem() + $index, 2, '0', STR_PAD_LEFT) }}</span>
|
|
</td>
|
|
<td>
|
|
<div style="display:flex; align-items:center; gap:12px;">
|
|
<div class="pug-av">
|
|
{{ strtoupper(substr($abs->teknisi->nama ?? 'T', 0, 1)) }}
|
|
</div>
|
|
<div>
|
|
<div class="pug-av-name">{{ $abs->teknisi->nama ?? 'Unknown' }}</div>
|
|
<div class="pug-av-sub">{{ $abs->teknisi->email ?? '—' }}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="pug-date">{{ \Carbon\Carbon::parse($abs->tanggal)->format('d/m/Y') }}</div>
|
|
<div style="font-size: 11px; color: var(--pug-t4);">{{ \Carbon\Carbon::parse($abs->tanggal)->isoFormat('dddd') }}</div>
|
|
</td>
|
|
<td>
|
|
@if($abs->jam_masuk)
|
|
<div style="font-weight: 700; color: var(--pug-t1);">{{ \Carbon\Carbon::parse($abs->jam_masuk)->format('H:i') }}</div>
|
|
@php
|
|
$kat = $abs->kategori_kerja;
|
|
$badgeClass = $kat == 'Kerja Urgent' ? 'badge-urgent' : 'badge-normal';
|
|
$icon = $kat == 'Kerja Urgent' ? 'exclamation-triangle' : 'sun';
|
|
@endphp
|
|
<div class="badge-kategori {{ $badgeClass }}" style="margin-top: 4px;">
|
|
<i class="fas fa-{{ $icon }}"></i> {{ $kat }}
|
|
</div>
|
|
@else
|
|
<span style="color: var(--pug-t4);">—</span>
|
|
@endif
|
|
</td>
|
|
<td>
|
|
<span style="font-weight: 700;">{{ $abs->jam_keluar ? \Carbon\Carbon::parse($abs->jam_keluar)->format('H:i') : '—' }}</span>
|
|
</td>
|
|
<td>
|
|
<span style="font-weight: 600;">{{ $abs->durasi_kerja_formatted ?? '—' }}</span>
|
|
</td>
|
|
<td>
|
|
@php
|
|
$s = strtolower($abs->status);
|
|
$badgeClass = 'pug-badge-green';
|
|
$icon = 'check-circle';
|
|
if($s == 'izin' || $s == 'sakit') { $badgeClass = 'pug-badge-violet'; $icon = 'info-circle'; }
|
|
@endphp
|
|
<span class="pug-badge {{ $badgeClass }}">
|
|
<i class="fas fa-{{ $icon }}"></i> {{ ucfirst($abs->status) }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div class="pug-actions">
|
|
@if(!empty($abs->latitude) && !empty($abs->longitude) && $abs->latitude !== '0' && $abs->longitude !== '0')
|
|
<a href="https://maps.google.com/?q={{ $abs->latitude }},{{ $abs->longitude }}" target="_blank" class="pug-action-btn" style="background: #eff6ff; color: #3b82f6; border-color: #bfdbfe;" title="Cek Lokasi GPS">
|
|
<i class="fas fa-map-marker-alt"></i>
|
|
</a>
|
|
@endif
|
|
<button class="pug-action-btn pug-action-view" onclick="showDetail({{ $abs->id_absensi }})" title="Lihat Detail">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button class="pug-action-btn pug-action-edit" onclick="editAbsensi({{ $abs->id_absensi }})" title="Edit Data">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="8">
|
|
<div class="pug-empty">
|
|
<div class="pug-empty-icon"><i class="fas fa-calendar-times"></i></div>
|
|
<div class="pug-empty-title">Data Absensi Tidak Ditemukan</div>
|
|
<div class="pug-empty-sub">Gunakan filter untuk mencari data di tanggal lain.</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
@if($absensis->hasPages())
|
|
<div class="pug-pag">
|
|
{{ $absensis->appends(request()->query())->links() }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Detail Absensi -->
|
|
<div class="pug-overlay" id="detailModal">
|
|
<div class="pug-modal pug-modal-lg">
|
|
<div class="pug-modal-header">
|
|
<div class="pug-modal-title"><i class="fas fa-info-circle"></i> DETAIL ABSENSI TEKNISI</div>
|
|
<button class="pug-close-btn" onclick="closeModal('detailModal')"><i class="fas fa-times"></i></button>
|
|
</div>
|
|
<div id="detailContent"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Edit Absensi -->
|
|
<div class="pug-overlay" id="editModal">
|
|
<div class="pug-modal pug-modal-sm">
|
|
<div class="pug-modal-header">
|
|
<div class="pug-modal-title"><i class="fas fa-edit"></i> EDIT DATA ABSENSI</div>
|
|
<button class="pug-close-btn" onclick="closeModal('editModal')"><i class="fas fa-times"></i></button>
|
|
</div>
|
|
<form id="editForm">
|
|
@csrf @method('PUT')
|
|
<input type="hidden" id="edit_id">
|
|
<div class="pug-form-grid">
|
|
<div class="pug-form-full">
|
|
<label class="pug-form-label"><i class="fas fa-user"></i> TEKNISI & TANGGAL</label>
|
|
<p id="edit_nama" style="font-weight: 700; color: #1e293b; margin-top: 4px;"></p>
|
|
<p id="edit_tanggal" style="font-size: 12px; color: #64748b;"></p>
|
|
</div>
|
|
<div class="pug-form-field">
|
|
<label class="pug-form-label">Jam Masuk</label>
|
|
<div class="d-flex gap-2">
|
|
<select id="h_masuk" class="pug-select">
|
|
<option value="">Jam</option>
|
|
@for($i=0; $i<24; $i++) <option value="{{ sprintf('%02d', $i) }}">{{ sprintf('%02d', $i) }}</option> @endfor
|
|
</select>
|
|
<select id="m_masuk" class="pug-select">
|
|
<option value="">Menit</option>
|
|
@for($i=0; $i<60; $i++) <option value="{{ sprintf('%02d', $i) }}">{{ sprintf('%02d', $i) }}</option> @endfor
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="pug-form-field">
|
|
<label class="pug-form-label">Jam Keluar</label>
|
|
<div class="d-flex gap-2">
|
|
<select id="h_keluar" class="pug-select">
|
|
<option value="">Jam</option>
|
|
@for($i=0; $i<24; $i++) <option value="{{ sprintf('%02d', $i) }}">{{ sprintf('%02d', $i) }}</option> @endfor
|
|
</select>
|
|
<select id="m_keluar" class="pug-select">
|
|
<option value="">Menit</option>
|
|
@for($i=0; $i<60; $i++) <option value="{{ sprintf('%02d', $i) }}">{{ sprintf('%02d', $i) }}</option> @endfor
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="pug-form-field">
|
|
<label class="pug-form-label">Status</label>
|
|
<select name="status" id="edit_status" class="pug-select">
|
|
<option value="hadir">Hadir</option>
|
|
<option value="izin">Izin</option>
|
|
<option value="sakit">Sakit</option>
|
|
</select>
|
|
</div>
|
|
<div class="pug-form-field">
|
|
<label class="pug-form-label">Keterangan</label>
|
|
<textarea name="keterangan" id="edit_keterangan" class="pug-textarea" rows="2"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="pug-modal-footer">
|
|
<button type="button" onclick="closeModal('editModal')" class="pug-btn-cancel">Batal</button>
|
|
<button type="button" onclick="submitEdit()" class="pug-btn-submit">Simpan Perubahan</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const appUrl = "{{ url('/') }}";
|
|
|
|
function setStatus(val) {
|
|
document.getElementById('statusInput').value = val;
|
|
document.querySelector('.pug-toolbar').submit();
|
|
}
|
|
|
|
function closeModal(id) { document.getElementById(id).classList.remove('show'); }
|
|
|
|
function showDetail(id) {
|
|
const modal = document.getElementById('detailModal');
|
|
const content = document.getElementById('detailContent');
|
|
modal.classList.add('show');
|
|
content.innerHTML = '<div class="text-center py-5"><div class="spinner-border text-success"></div><p class="mt-2 text-muted">Memuat data...</p></div>';
|
|
|
|
fetch(`${appUrl}/absensi/${id}`, { headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' } })
|
|
.then(r => r.json())
|
|
.then(res => {
|
|
if(res.success) {
|
|
const d = res.data;
|
|
content.innerHTML = `
|
|
<div class="row">
|
|
<div class="col-md-7">
|
|
<div class="pug-detail-section">
|
|
<div class="pug-detail-section-title">INFORMASI TEKNISI</div>
|
|
<div class="pug-detail-grid">
|
|
<div class="pug-form-field">
|
|
<div class="pug-detail-item-label">Nama</div>
|
|
<div class="pug-detail-item-val">${d.teknisi ? d.teknisi.nama : '—'}</div>
|
|
</div>
|
|
<div class="pug-form-field">
|
|
<div class="pug-detail-item-label">Status Absen</div>
|
|
<span class="pug-badge ${d.status == 'hadir' ? 'pug-badge-green' : 'pug-badge-violet'}">${d.status_formatted}</span>
|
|
</div>
|
|
<div class="pug-form-field">
|
|
<div class="pug-detail-item-label">Jam Masuk</div>
|
|
<div class="pug-detail-item-val">${d.jam_masuk || '-'}</div>
|
|
</div>
|
|
<div class="pug-form-field">
|
|
<div class="pug-detail-item-label">Jam Keluar</div>
|
|
<div class="pug-detail-item-val">${d.jam_keluar || '-'}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="pug-detail-section">
|
|
<div class="pug-detail-section-title">KATEGORI & CATATAN</div>
|
|
<div class="pug-detail-item-val mb-2" style="color:${d.kategori_kerja === 'Kerja Urgent' ? '#f59e0b' : '#15803d'};">
|
|
<i class="fas fa-${d.kategori_kerja === 'Kerja Urgent' ? 'exclamation-triangle' : 'sun'}"></i> ${d.kategori_kerja}
|
|
</div>
|
|
<p class="small text-muted mb-0 italic">${d.keterangan || 'Tidak ada catatan.'}</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-5">
|
|
<div style="display: flex; gap: 12px;">
|
|
<div class="pug-photo-container mb-0" style="flex: 1;">
|
|
<div class="pug-photo-label"><i class="fas fa-sign-in-alt"></i> FOTO MASUK</div>
|
|
${d.foto_masuk_url ? `<img src="${d.foto_masuk_url}" class="pug-photo-item" onclick="window.open('${d.foto_masuk_url}', '_blank')">` : '<div class="pug-photo-empty">Tidak ada foto</div>'}
|
|
</div>
|
|
<div class="pug-photo-container" style="flex: 1;">
|
|
<div class="pug-photo-label"><i class="fas fa-sign-out-alt"></i> FOTO KELUAR</div>
|
|
${d.foto_keluar_url ? `<img src="${d.foto_keluar_url}" class="pug-photo-item" onclick="window.open('${d.foto_keluar_url}', '_blank')">` : '<div class="pug-photo-empty">Tidak ada foto</div>'}
|
|
</div>
|
|
</div>
|
|
<div style="font-size: 11px; color: var(--pug-t3); text-align: center; margin-top: 8px;">Klik foto untuk melihat ukuran penuh</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
});
|
|
}
|
|
|
|
function editAbsensi(id) {
|
|
const modal = document.getElementById('editModal');
|
|
modal.classList.add('show');
|
|
fetch(`${appUrl}/absensi/${id}`, { headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' } })
|
|
.then(r => r.json())
|
|
.then(res => {
|
|
if(res.success) {
|
|
const d = res.data;
|
|
document.getElementById('edit_id').value = d.id;
|
|
document.getElementById('edit_nama').textContent = d.teknisi.nama;
|
|
document.getElementById('edit_tanggal').textContent = d.tanggal_full;
|
|
if(d.jam_masuk && d.jam_masuk !== '-') {
|
|
const [h, m] = d.jam_masuk.split(':');
|
|
document.getElementById('h_masuk').value = h;
|
|
document.getElementById('m_masuk').value = m;
|
|
}
|
|
if(d.jam_keluar && d.jam_keluar !== '-') {
|
|
const [h, m] = d.jam_keluar.split(':');
|
|
document.getElementById('h_keluar').value = h;
|
|
document.getElementById('m_keluar').value = m;
|
|
}
|
|
document.getElementById('edit_status').value = d.status;
|
|
document.getElementById('edit_keterangan').value = d.keterangan !== '-' ? d.keterangan : '';
|
|
}
|
|
});
|
|
}
|
|
|
|
function submitEdit() {
|
|
const id = document.getElementById('edit_id').value;
|
|
const formData = new FormData(document.getElementById('editForm'));
|
|
const hM = document.getElementById('h_masuk').value; const mM = document.getElementById('m_masuk').value;
|
|
const hK = document.getElementById('h_keluar').value; const mK = document.getElementById('m_keluar').value;
|
|
if(hM && mM) formData.append('jam_masuk', `${hM}:${mM}`);
|
|
if(hK && mK) formData.append('jam_keluar', `${hK}:${mK}`);
|
|
|
|
fetch(`${appUrl}/absensi/${id}`, {
|
|
method: 'POST',
|
|
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' },
|
|
body: formData
|
|
})
|
|
.then(r => r.json())
|
|
.then(res => { if(res.success) location.reload(); else alert('Gagal simpan!'); });
|
|
}
|
|
</script>
|
|
</x-app-layout> |