Initial commit - lms-v2 + CLAUDE.md
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
|
||||
<style>
|
||||
/* Penyesuaian UI Select2 agar senada dengan Tailwind dan menyembunyikan elemen asli */
|
||||
.select2-container .select2-selection--single { height: 42px; border-radius: 0.75rem; border-color: #e2e8f0; background-color: #f8fafc; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 42px; font-size: 0.875rem; color: #334155; padding-left: 1rem; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow { height: 40px; right: 10px; }
|
||||
.select2-hidden-accessible { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important; }
|
||||
</style>
|
||||
|
||||
<div class="max-w-5xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.employees.index') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Tambah Karyawan Baru</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="bg-blue-50/50 p-4 border-b border-slate-100 flex items-start">
|
||||
<svg class="w-5 h-5 text-blue-600 mt-0.5 mr-3 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
<p class="text-sm text-blue-800">Lengkapi formulir di bawah ini. Password default: <span class="font-mono font-bold bg-white px-2 py-0.5 rounded border border-blue-200">12345678</span></p>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('admin.employees.store') }}" method="POST" enctype="multipart/form-data" class="p-6 sm:p-8 space-y-6">
|
||||
@csrf
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">NIK <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="nik" value="{{ old('nik') }}" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
@error('nik') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">No. KTP</label>
|
||||
<input type="text" name="identity_number" value="{{ old('identity_number') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Inisial <span class="text-blue-500 font-normal text-xs">(Auto Suggest)</span></label>
|
||||
<input type="text" name="initial" id="initial" value="{{ old('initial') }}" maxlength="3" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm uppercase">
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Nama Depan <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="first_name" id="first_name" value="{{ old('first_name') }}" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
@error('first_name') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Nama Belakang</label>
|
||||
<input type="text" name="last_name" id="last_name" value="{{ old('last_name') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Email Perusahaan <span class="text-red-500">*</span></label>
|
||||
<input type="email" name="email" value="{{ old('email') }}" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
@error('email') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">No. HP / WA</label>
|
||||
<input type="text" name="phone" value="{{ old('phone') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Jenis Kelamin <span class="text-red-500">*</span></label>
|
||||
<select name="gender" class="select2 w-full text-sm" required>
|
||||
<option value="">-- Pilih --</option>
|
||||
<option value="L" {{ old('gender') == 'L' ? 'selected' : '' }}>Laki-laki</option>
|
||||
<option value="P" {{ old('gender') == 'P' ? 'selected' : '' }}>Perempuan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Tanggal Lahir</label>
|
||||
<input type="date" name="date_of_birth" value="{{ old('date_of_birth') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Tanggal Bergabung</label>
|
||||
<input type="date" name="join_date" value="{{ old('join_date') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Departemen <span class="text-red-500">*</span></label>
|
||||
<select name="department_id" id="deptSelect" class="select2 w-full text-sm" required>
|
||||
<option value="">-- Pilih Departemen --</option>
|
||||
@foreach($departments as $dept)
|
||||
<option value="{{ $dept->id }}" {{ old('department_id') == $dept->id ? 'selected' : '' }}>{{ $dept->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Posisi / Jabatan <span class="text-red-500">*</span></label>
|
||||
<select name="position_id" id="posSelect" class="select2 w-full text-sm" required>
|
||||
<option value="">-- Pilih Dept Terlebih Dahulu --</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 p-4 border border-slate-200 rounded-xl">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Hak Akses Sistem Utama <span class="text-red-500">*</span></label>
|
||||
<select name="role" class="select2 w-full text-sm" required>
|
||||
<option value="karyawan" {{ old('role') == 'karyawan' ? 'selected' : '' }}>Trainee (Karyawan)</option>
|
||||
@if(auth()->user()->hasRole('superadmin'))
|
||||
<option value="admin" {{ old('role') == 'admin' ? 'selected' : '' }}>Administrator</option>
|
||||
@endif
|
||||
</select>
|
||||
|
||||
<label class="flex items-center space-x-2 cursor-pointer mt-4">
|
||||
<input type="checkbox" name="is_trainer" value="1" {{ old('is_trainer') ? 'checked' : '' }} class="w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500">
|
||||
<span class="text-sm font-semibold text-slate-700">Jadikan Trainer / Pemateri juga</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 pt-6 mt-6">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Modul SOP Training (Marketing)</label>
|
||||
<select name="training_matrix_id" class="select2 w-full text-sm">
|
||||
<option value="">-- Kosongkan jika bukan Plant Dept --</option>
|
||||
@foreach($trainingMatrices as $matrix)
|
||||
<option value="{{ $matrix->id }}" {{ old('training_matrix_id') == $matrix->id ? 'selected' : '' }}>{{ $matrix->title ?? $matrix->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 pt-6 mt-6" x-data="{ documents: [{ id: 1 }], addDocument() { this.documents.push({ id: Date.now() }); }, removeDocument(id) { this.documents = this.documents.filter(doc => doc.id !== id); } }">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700">Dokumen Karyawan</label>
|
||||
<p class="text-xs text-slate-500 mt-1">CV, KTP, Ijazah (Otomatis terunggah ke Nextcloud)</p>
|
||||
</div>
|
||||
<button type="button" @click="addDocument()" class="text-xs bg-blue-50 text-blue-700 font-bold px-3 py-1.5 rounded-lg hover:bg-blue-100">
|
||||
+ Tambah Kolom File
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<template x-for="(doc, index) in documents" :key="doc.id">
|
||||
<div class="flex items-center gap-3 bg-slate-50 p-3 rounded-lg border border-slate-200">
|
||||
<span class="text-sm font-bold text-slate-400 w-6" x-text="index + 1 + '.'"></span>
|
||||
<input type="file" name="documents[]" class="flex-1 text-sm file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 cursor-pointer">
|
||||
<button type="button" x-show="documents.length > 1" @click="removeDocument(doc.id)" class="text-red-500 hover:text-red-700 p-2">
|
||||
Hapus
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-6 flex justify-end space-x-3 border-t border-slate-100 mt-8">
|
||||
<a href="{{ route('admin.employees.index') }}" class="px-5 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal</a>
|
||||
<button type="submit" class="px-5 py-2.5 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-700 rounded-xl shadow-md transition-all">Simpan Karyawan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// 1. Inisialisasi Select2 di semua dropdown yang ada class select2
|
||||
$('.select2').select2({ width: '100%' });
|
||||
|
||||
// 2. Logika Dropdown Bertingkat (Dept -> Posisi)
|
||||
const mapping = @json($deptPosMapping ?? []);
|
||||
const posSelect = $('#posSelect');
|
||||
const deptSelect = $('#deptSelect');
|
||||
const oldPos = "{{ old('position_id') }}";
|
||||
|
||||
function updatePositionDropdown() {
|
||||
let deptId = deptSelect.val();
|
||||
posSelect.empty();
|
||||
posSelect.append(new Option('-- Pilih Jabatan --', ''));
|
||||
|
||||
if (deptId && mapping[deptId]) {
|
||||
mapping[deptId].forEach(function(pos) {
|
||||
let selected = (pos.id == oldPos);
|
||||
posSelect.append(new Option(pos.name, pos.id, false, selected));
|
||||
});
|
||||
} else {
|
||||
// Munculkan semua jika Dept belum terpilih
|
||||
@foreach($positions as $p)
|
||||
posSelect.append(new Option("{{ $p->name }}", "{{ $p->id }}", false, ("{{ $p->id }}" == oldPos)));
|
||||
@endforeach
|
||||
}
|
||||
posSelect.trigger('change.select2');
|
||||
}
|
||||
|
||||
deptSelect.on('change', updatePositionDropdown);
|
||||
updatePositionDropdown(); // Eksekusi saat load pertama kali
|
||||
});
|
||||
|
||||
// 3. FITUR AUTO-SUGGEST INITIAL 3 DIGIT
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const existingInitials = @json($existingInitials ?? []);
|
||||
const firstNameInput = document.getElementById('first_name');
|
||||
const lastNameInput = document.getElementById('last_name');
|
||||
const initialInput = document.getElementById('initial');
|
||||
|
||||
function generateInitial() {
|
||||
let first = firstNameInput.value.trim().toLowerCase();
|
||||
let last = lastNameInput.value.trim().toLowerCase();
|
||||
let fullName = (first + ' ' + last).trim();
|
||||
|
||||
if (fullName.length === 0) return;
|
||||
|
||||
let words = fullName.split(/\s+/).filter(w => w.length > 0);
|
||||
let initial = '';
|
||||
|
||||
// Algoritma: 3 kata ambil huruf pertama, 2 kata ambil 1 dpn & 2 blkg, 1 kata ambil 3 awal.
|
||||
if (words.length >= 3) {
|
||||
initial = words[0][0] + words[1][0] + words[2][0];
|
||||
} else if (words.length === 2) {
|
||||
initial = words[0][0] + words[1].substring(0, 2);
|
||||
} else if (words.length === 1) {
|
||||
initial = words[0].substring(0, 3);
|
||||
}
|
||||
|
||||
initial = initial.replace(/[^a-z]/g, '');
|
||||
while(initial.length < 3) { initial += 'x'; }
|
||||
initial = initial.substring(0, 3);
|
||||
|
||||
let baseInitial = initial.substring(0, 2);
|
||||
let counter = 0;
|
||||
let alphabet = 'abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
// Cek ke DB: jika inisial sudah ada, ganti huruf ke-3 secara alphabet.
|
||||
while (existingInitials.includes(initial) && counter < 26) {
|
||||
initial = baseInitial + alphabet[counter];
|
||||
counter++;
|
||||
}
|
||||
initialInput.value = initial.toUpperCase();
|
||||
}
|
||||
|
||||
firstNameInput.addEventListener('input', generateInitial);
|
||||
lastNameInput.addEventListener('input', generateInitial);
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,171 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
|
||||
<style>
|
||||
.select2-container .select2-selection--single { height: 42px; border-radius: 0.75rem; border-color: #e2e8f0; background-color: #f8fafc; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 42px; font-size: 0.875rem; color: #334155; padding-left: 1rem; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow { height: 40px; right: 10px; }
|
||||
.select2-hidden-accessible { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important; }
|
||||
</style>
|
||||
|
||||
<div class="max-w-5xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.employees.index') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Edit Data Karyawan</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<form action="{{ route('admin.employees.update', $employee->id) }}" method="POST" enctype="multipart/form-data" class="p-6 sm:p-8 space-y-6">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">NIK <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="nik" value="{{ old('nik', $employee->nik) }}" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">No. KTP</label>
|
||||
<input type="text" name="identity_number" value="{{ old('identity_number', $employee->identity_number) }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Inisial</label>
|
||||
<input type="text" name="initial" value="{{ old('initial', $employee->initial) }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm uppercase">
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Nama Depan <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="first_name" value="{{ old('first_name', $employee->first_name) }}" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Nama Belakang</label>
|
||||
<input type="text" name="last_name" value="{{ old('last_name', $employee->last_name) }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Email Perusahaan <span class="text-red-500">*</span></label>
|
||||
<input type="email" name="email" value="{{ old('email', $employee->email) }}" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">No. HP / WA</label>
|
||||
<input type="text" name="phone" value="{{ old('phone', $employee->phone) }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Jenis Kelamin <span class="text-red-500">*</span></label>
|
||||
<select name="gender" class="select2 w-full text-sm" required>
|
||||
<option value="L" {{ old('gender', $employee->gender) == 'L' ? 'selected' : '' }}>Laki-laki</option>
|
||||
<option value="P" {{ old('gender', $employee->gender) == 'P' ? 'selected' : '' }}>Perempuan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Tanggal Lahir</label>
|
||||
<input type="date" name="date_of_birth" value="{{ old('date_of_birth', $employee->date_of_birth ? \Carbon\Carbon::parse($employee->date_of_birth)->format('Y-m-d') : '') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Tanggal Bergabung</label>
|
||||
<input type="date" name="join_date" value="{{ old('join_date', $employee->join_date ? \Carbon\Carbon::parse($employee->join_date)->format('Y-m-d') : '') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Departemen <span class="text-red-500">*</span></label>
|
||||
<select name="department_id" id="deptSelect" class="select2 w-full text-sm" required>
|
||||
@foreach($departments as $dept)
|
||||
<option value="{{ $dept->id }}" {{ old('department_id', $employee->department_id) == $dept->id ? 'selected' : '' }}>{{ $dept->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Posisi / Jabatan <span class="text-red-500">*</span></label>
|
||||
<select name="position_id" id="posSelect" class="select2 w-full text-sm" required>
|
||||
<option value="">-- Pilih Jabatan --</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 p-4 border border-slate-200 rounded-xl">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Hak Akses Sistem Utama <span class="text-red-500">*</span></label>
|
||||
<select name="role" class="select2 w-full text-sm" required>
|
||||
<option value="karyawan" {{ $employee->hasRole('admin') ? '' : 'selected' }}>Trainee (Karyawan)</option>
|
||||
@if(auth()->user()->hasRole('superadmin'))
|
||||
<option value="admin" {{ $employee->hasRole('admin') ? 'selected' : '' }}>Administrator</option>
|
||||
@endif
|
||||
</select>
|
||||
|
||||
<label class="flex items-center space-x-2 cursor-pointer mt-4">
|
||||
<input type="checkbox" name="is_trainer" value="1" {{ $employee->hasRole('trainer') ? 'checked' : '' }} class="w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500">
|
||||
<span class="text-sm font-semibold text-slate-700">Tetapkan sebagai Trainer / Pemateri</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 p-4 border border-slate-200 rounded-xl">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Status Karyawan <span class="text-red-500">*</span></label>
|
||||
<label class="flex items-center space-x-2 cursor-pointer mt-2">
|
||||
<input type="checkbox" name="is_active" value="1" {{ old('is_active', $employee->is_active ?? true) ? 'checked' : '' }} class="w-4 h-4 text-emerald-600 border-slate-300 rounded focus:ring-emerald-500">
|
||||
<span class="text-sm font-semibold text-slate-700">Akun Aktif</span>
|
||||
</label>
|
||||
<p class="text-[11px] text-slate-500 mt-2">Hilangkan centang jika karyawan resign.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 pt-6 mt-6">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Modul SOP Training (Marketing)</label>
|
||||
<select name="training_matrix_id" class="select2 w-full text-sm">
|
||||
<option value="">-- Kosongkan jika bukan marketing --</option>
|
||||
@foreach($trainingMatrices as $matrix)
|
||||
<option value="{{ $matrix->id }}" {{ old('training_matrix_id', $employee->training_matrix_id) == $matrix->id ? 'selected' : '' }}>{{ $matrix->title ?? $matrix->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 pt-6 mt-6">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Reset Password (Opsional)</label>
|
||||
<input type="password" name="password" placeholder="Kosongkan jika tidak ingin mengubah password" class="w-full md:w-1/2 px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div class="pt-6 flex justify-end space-x-3 border-t border-slate-100 mt-8">
|
||||
<a href="{{ route('admin.employees.index') }}" class="px-5 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal</a>
|
||||
<button type="submit" class="px-5 py-2.5 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-700 rounded-xl shadow-md transition-all">Perbarui Karyawan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.select2').select2({ width: '100%' });
|
||||
|
||||
// Logika Dropdown Bertingkat untuk Edit (Sama dengan Create)
|
||||
const mapping = @json($deptPosMapping ?? []);
|
||||
const posSelect = $('#posSelect');
|
||||
const deptSelect = $('#deptSelect');
|
||||
const defaultPos = "{{ old('position_id', $employee->position_id) }}";
|
||||
|
||||
function updatePositionDropdown() {
|
||||
let deptId = deptSelect.val();
|
||||
posSelect.empty();
|
||||
posSelect.append(new Option('-- Pilih Jabatan --', ''));
|
||||
|
||||
if (deptId && mapping[deptId]) {
|
||||
mapping[deptId].forEach(function(pos) {
|
||||
let selected = (pos.id == defaultPos);
|
||||
posSelect.append(new Option(pos.name, pos.id, false, selected));
|
||||
});
|
||||
} else {
|
||||
@foreach($positions as $p)
|
||||
posSelect.append(new Option("{{ $p->name }}", "{{ $p->id }}", false, ("{{ $p->id }}" == defaultPos)));
|
||||
@endforeach
|
||||
}
|
||||
posSelect.trigger('change.select2');
|
||||
}
|
||||
|
||||
deptSelect.on('change', updatePositionDropdown);
|
||||
updatePositionDropdown();
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,72 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-7xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.employees.import') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Pratinjau Data Import</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-amber-50 border-l-4 border-amber-500 p-4 mb-6 rounded-r-lg">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-amber-400" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" /></svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-amber-800 font-semibold">Tinjau data Anda!</p>
|
||||
<p class="text-sm text-amber-700 mt-1">Data di bawah ini belum tersimpan. Pastikan nama kolom dan isi data sudah sesuai. Baris yang NIK, Nama Depan, atau Email-nya kosong akan dilewati (diabaikan).</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden mb-6">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm whitespace-nowrap">
|
||||
<thead class="bg-slate-50 text-slate-500 text-xs uppercase tracking-wider border-b border-slate-200">
|
||||
<tr>
|
||||
<th class="px-6 py-4 font-semibold">NIK</th>
|
||||
<th class="px-6 py-4 font-semibold">Nama Depan</th>
|
||||
<th class="px-6 py-4 font-semibold">Nama Belakang</th>
|
||||
<th class="px-6 py-4 font-semibold">Email</th>
|
||||
<th class="px-6 py-4 font-semibold">Telepon</th>
|
||||
<th class="px-6 py-4 font-semibold">ID Dept</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-slate-700">
|
||||
@foreach($rows as $index => $row)
|
||||
@if($index < 20) <!-- Batasi preview 20 baris saja agar browser tidak berat -->
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="px-6 py-3 font-mono font-bold">{{ $row['nik'] ?? '-' }}</td>
|
||||
<td class="px-6 py-3">{{ $row['nama_depan'] ?? '-' }}</td>
|
||||
<td class="px-6 py-3">{{ $row['nama_belakang'] ?? '-' }}</td>
|
||||
<td class="px-6 py-3 text-blue-600">{{ $row['email'] ?? '-' }}</td>
|
||||
<td class="px-6 py-3">{{ $row['telepon'] ?? '-' }}</td>
|
||||
<td class="px-6 py-3">{{ $row['department_id'] ?? '-' }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if(count($rows) > 20)
|
||||
<div class="p-4 text-center text-sm text-slate-500 border-t border-slate-100 bg-slate-50">
|
||||
... Menampilkan 20 baris pertama dari total <b>{{ count($rows) }}</b> baris data.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Tombol Aksi -->
|
||||
<div class="flex items-center justify-end space-x-4">
|
||||
<a href="{{ route('admin.employees.import') }}" class="px-6 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal & Kembali</a>
|
||||
<form action="{{ route('admin.employees.import.process') }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="px-6 py-2.5 text-sm font-semibold text-white bg-emerald-600 hover:bg-emerald-700 rounded-xl shadow-md transition-all flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
|
||||
Konfirmasi & Simpan Permanen
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,42 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.employees.index') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Import Data Karyawan</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Kotak Bantuan Template -->
|
||||
<div class="bg-blue-50 rounded-2xl p-6 border border-blue-100">
|
||||
<h3 class="text-lg font-bold text-blue-900 mb-2">1. Unduh Template</h3>
|
||||
<p class="text-sm text-blue-700 mb-6">Agar proses import berjalan lancar, pastikan Anda menggunakan template Excel/CSV standar dari sistem. Kolom wajib diisi adalah NIK, Nama, dan Email.</p>
|
||||
<a href="{{ route('admin.employees.import.template') }}" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-semibold hover:bg-blue-700 shadow-sm transition-all">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>
|
||||
Download Template
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Kotak Upload File -->
|
||||
<div class="bg-white rounded-2xl p-6 border border-slate-200 shadow-sm">
|
||||
<h3 class="text-lg font-bold text-slate-800 mb-2">2. Unggah File Data</h3>
|
||||
<p class="text-sm text-blue-700 mb-6 max-w-lg mx-auto">Gunakan format Excel standar dari sistem. Kolom wajib isi: NIK, Nama Depan, Jenis Kelamin dan Email. Tanggal gunakan format YYYY-MM-DD.</p>
|
||||
|
||||
<form action="{{ route('admin.employees.import.preview') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="mb-6">
|
||||
<input type="file" name="file" accept=".xlsx, .xls, .csv" required class="w-full text-sm text-slate-500 file:mr-4 file:py-2.5 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 cursor-pointer border border-slate-200 rounded-lg p-1 bg-slate-50">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full flex justify-center items-center px-4 py-2.5 text-sm font-semibold text-white bg-slate-800 hover:bg-slate-900 rounded-lg transition-all shadow-md">
|
||||
Lanjutkan ke Preview
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path></svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,288 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.2.0"></script>
|
||||
|
||||
<style>
|
||||
/* Kustomisasi Scrollbar */
|
||||
.custom-scrollbar::-webkit-scrollbar { height: 6px; }
|
||||
.custom-scrollbar::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 4px; }
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
||||
</style>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"
|
||||
x-data="{ showCols: { nik: true, name: true, role: true, department: true, action: true }, showCharts: false }">
|
||||
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-4">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Manajemen Karyawan</h2>
|
||||
<p class="text-sm text-slate-500 mt-1">Total <span class="font-bold text-blue-600">{{ $totalRecords }}</span> data karyawan terdaftar.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
|
||||
<button @click="showCharts = !showCharts" class="inline-flex items-center px-4 py-2 bg-slate-100 text-slate-700 border border-slate-200 rounded-lg text-sm font-semibold hover:bg-slate-200 shadow-sm transition-all">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>
|
||||
<span x-text="showCharts ? 'Sembunyikan Grafik' : 'Lihat Grafik'"></span>
|
||||
</button>
|
||||
|
||||
<div x-data="{ exportMenuOpen: false }" class="relative">
|
||||
<button @click="exportMenuOpen = !exportMenuOpen" @click.away="exportMenuOpen = false" class="inline-flex items-center px-4 py-2 bg-emerald-50 text-emerald-700 border border-emerald-200 rounded-lg text-sm font-semibold hover:bg-emerald-100 shadow-sm transition-all">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path></svg>
|
||||
Export / Import
|
||||
</button>
|
||||
|
||||
<div x-show="exportMenuOpen" x-transition style="display: none;" class="absolute right-0 mt-2 w-48 bg-white border border-slate-100 rounded-xl shadow-xl py-2 z-50">
|
||||
|
||||
<a href="{{ route('admin.employees.export.excel', request()->query()) }}" class="flex items-center px-4 py-2 text-sm text-slate-700 hover:bg-slate-50">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-500 mr-2"></span> Export to Excel
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.employees.export.pdf', request()->query()) }}" target="_blank" class="flex items-center px-4 py-2 text-sm text-slate-700 hover:bg-slate-50">
|
||||
<span class="w-2 h-2 rounded-full bg-red-500 mr-2"></span> Export to PDF
|
||||
</a>
|
||||
|
||||
<div class="border-t border-slate-100 my-1"></div>
|
||||
|
||||
<a href="{{ route('admin.employees.import') }}" class="w-full flex items-center px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 text-left">
|
||||
<span class="w-2 h-2 rounded-full bg-blue-500 mr-2"></span> Import dari Excel
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="{{ route('admin.employees.create') }}" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-semibold hover:bg-blue-700 shadow-sm transition-all">
|
||||
+ Tambah Karyawan
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div x-show="showCharts" x-transition style="display: none;" class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||
<div class="bg-white p-5 rounded-2xl border border-slate-200 shadow-sm">
|
||||
<h3 class="text-sm font-bold text-slate-700 mb-4">Distribusi per Departemen (Geser 👉)</h3>
|
||||
<div class="overflow-x-auto w-full pb-2 custom-scrollbar">
|
||||
<div id="deptChartContainer" style="height: 220px;">
|
||||
<canvas id="deptChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-5 rounded-2xl border border-slate-200 shadow-sm flex flex-col h-full">
|
||||
<h3 class="text-sm font-bold text-slate-700 mb-4">Distribusi per Jabatan</h3>
|
||||
<div class="relative h-40 w-full mb-4">
|
||||
<canvas id="posChart"></canvas>
|
||||
</div>
|
||||
<div class="overflow-x-auto pb-2 custom-scrollbar mt-auto">
|
||||
<div id="posLegend" class="flex gap-2 whitespace-nowrap min-w-max px-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-5 rounded-t-2xl border border-slate-200 border-b-0 relative z-10">
|
||||
<form action="{{ route('admin.employees.index') }}" method="GET" class="flex flex-col md:flex-row gap-4 items-end">
|
||||
<div class="w-full md:w-1/3">
|
||||
<label class="block text-xs font-semibold text-slate-500 mb-1">Pencarian Semua Kolom</label>
|
||||
<input type="text" name="search" value="{{ request('search') }}" placeholder="Ketik keyword apapun..."
|
||||
class="w-full px-4 py-2 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-indigo-500">
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-1/4">
|
||||
<label class="block text-xs font-semibold text-slate-500 mb-1">Pilih Departemen</label>
|
||||
<select name="department_id" id="deptSelect" class="select2 w-full">
|
||||
<option value="">Semua Departemen</option>
|
||||
@foreach($departments as $dept)
|
||||
<option value="{{ $dept->id }}" {{ request('department_id') == $dept->id ? 'selected' : '' }}>
|
||||
{{ $dept->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-1/4">
|
||||
<label class="block text-xs font-semibold text-slate-500 mb-1">Pilih Jabatan</label>
|
||||
<select name="position_id" id="posSelect" class="select2 w-full">
|
||||
<option value="">Semua Jabatan</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-auto flex gap-2">
|
||||
<button type="submit" class="px-5 py-2 bg-slate-800 text-white rounded-xl text-sm font-semibold hover:bg-slate-700">Filter</button>
|
||||
@if(request()->hasAny(['search', 'department_id', 'position_id']))
|
||||
<a href="{{ route('admin.employees.index') }}" class="px-5 py-2 bg-red-50 text-red-600 border border-red-100 rounded-xl text-sm font-semibold hover:bg-red-100">Reset</a>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border border-slate-200 rounded-b-2xl overflow-hidden shadow-sm relative z-0 mt-4 md:mt-0">
|
||||
<div class="overflow-x-auto custom-scrollbar">
|
||||
<table class="w-full text-left text-sm whitespace-nowrap">
|
||||
<thead class="bg-slate-50/80 text-slate-500 text-xs uppercase tracking-wider border-b border-slate-200">
|
||||
<tr>
|
||||
<th x-show="showCols.nik" class="px-6 py-4 font-semibold">NIK & Inisial</th>
|
||||
<th x-show="showCols.name" class="px-6 py-4 font-semibold">Profil & Kontak</th>
|
||||
<th x-show="showCols.role" class="px-6 py-4 font-semibold text-center">Hak Akses Sistem</th>
|
||||
<th x-show="showCols.department" class="px-6 py-4 font-semibold">Penempatan</th>
|
||||
<th x-show="showCols.action" class="px-6 py-4 font-semibold text-right">Opsi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-slate-700">
|
||||
@forelse($employees as $emp)
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td x-show="showCols.nik" class="px-6 py-4">
|
||||
<div class="font-mono font-bold text-slate-900">{{ $emp->nik ?? '-' }}</div>
|
||||
<div class="mt-1">
|
||||
<span class="bg-indigo-50 text-indigo-700 px-2 py-0.5 rounded text-[10px] font-bold border border-indigo-100">
|
||||
{{ $emp->initial ?? 'N/A' }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td x-show="showCols.name" class="px-6 py-4">
|
||||
<div class="font-bold text-slate-900">
|
||||
{{ $emp->first_name }} {{ $emp->last_name }}
|
||||
@if(!$emp->is_active)
|
||||
<span class="ml-2 px-2 py-0.5 bg-red-100 text-red-600 text-[10px] font-bold rounded uppercase">Non-Aktif</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="text-xs text-slate-500">{{ $emp->email }}</div>
|
||||
</td>
|
||||
|
||||
<td x-show="showCols.role" class="px-6 py-4 text-center">
|
||||
<div class="flex flex-wrap gap-1 justify-center">
|
||||
@php
|
||||
$isTrainer = $emp->hasRole('trainer') || $emp->is_trainer == 1 || (isset($emp->role) && strtolower($emp->role) == 'trainer');
|
||||
$isAdmin = $emp->hasRole('admin') || $emp->hasRole('superadmin') || (isset($emp->role) && strtolower($emp->role) == 'admin');
|
||||
@endphp
|
||||
|
||||
@if($isAdmin)
|
||||
<span class="px-2 py-1 rounded text-[10px] font-bold uppercase tracking-wide bg-red-100 text-red-700">Admin</span>
|
||||
@endif
|
||||
|
||||
@if($isTrainer)
|
||||
<span class="px-2 py-1 rounded text-[10px] font-bold uppercase tracking-wide bg-amber-100 text-amber-700">Trainer</span>
|
||||
@endif
|
||||
|
||||
<span class="px-2 py-1 rounded text-[10px] font-bold uppercase tracking-wide bg-emerald-100 text-emerald-700">Trainee</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td x-show="showCols.department" class="px-6 py-4">
|
||||
<div class="text-sm font-semibold text-slate-800">{{ $emp->position->name ?? '-' }}</div>
|
||||
<div class="text-xs text-slate-500 mt-0.5">{{ $emp->department->name ?? '-' }}</div>
|
||||
</td>
|
||||
|
||||
<td x-show="showCols.action" class="px-6 py-4 text-right space-x-2">
|
||||
<a href="{{ route('admin.employees.show', $emp->id) }}" class="text-slate-400 hover:text-indigo-600 font-medium text-sm">Detail</a>
|
||||
<a href="{{ route('admin.employees.edit', $emp->id) }}" class="text-indigo-600 hover:text-indigo-800 font-medium text-sm">Edit</a>
|
||||
|
||||
<form action="{{ route('admin.employees.destroy', $emp->id) }}" method="POST" class="inline-block" onsubmit="return confirm('Nonaktifkan karyawan ini? Data historis tidak akan dihapus.');">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="text-red-500 hover:text-red-700 font-medium text-sm ml-2">Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="px-6 py-10 text-center text-slate-500 italic">Data tidak ditemukan.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if(method_exists($employees, 'hasPages') && $employees->hasPages())
|
||||
<div class="p-4 border-t border-slate-100 bg-slate-50/50">
|
||||
{{ $employees->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.select2').select2({ width: '100%' });
|
||||
|
||||
const mapping = @json($deptPosMapping ?? []);
|
||||
const posSelect = $('#posSelect');
|
||||
const deptSelect = $('#deptSelect');
|
||||
const oldPos = "{{ request('position_id') }}";
|
||||
|
||||
function updatePositionDropdown() {
|
||||
let deptId = deptSelect.val();
|
||||
posSelect.empty();
|
||||
posSelect.append(new Option('Semua Jabatan', ''));
|
||||
|
||||
if (deptId && mapping[deptId]) {
|
||||
mapping[deptId].forEach(function(pos) {
|
||||
let selected = (pos.id == oldPos);
|
||||
posSelect.append(new Option(pos.name, pos.id, false, selected));
|
||||
});
|
||||
} else {
|
||||
@foreach($positions as $p)
|
||||
posSelect.append(new Option("{{ $p->name }}", "{{ $p->id }}", false, ("{{ $p->id }}" == oldPos)));
|
||||
@endforeach
|
||||
}
|
||||
posSelect.trigger('change.select2');
|
||||
}
|
||||
|
||||
deptSelect.on('change', updatePositionDropdown);
|
||||
updatePositionDropdown();
|
||||
});
|
||||
|
||||
// KONFIGURASI GRAFIK
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
Chart.register(ChartDataLabels);
|
||||
const chartColors = ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#06B6D4', '#14B8A6', '#F43F5E', '#84CC16', '#3B82F6', '#F97316', '#6366F1'];
|
||||
|
||||
const rawDeptData = @json($chartDept ?? []);
|
||||
const deptLabels = rawDeptData.map(d => d.department ? d.department.name : 'N/A');
|
||||
const deptCounts = rawDeptData.map(d => d.total);
|
||||
const deptBgColors = rawDeptData.map((d, i) => chartColors[i % chartColors.length]);
|
||||
|
||||
const minDeptWidth = Math.max(600, rawDeptData.length * 60);
|
||||
document.getElementById('deptChartContainer').style.width = minDeptWidth + 'px';
|
||||
|
||||
new Chart(document.getElementById('deptChart'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: deptLabels,
|
||||
datasets: [{ data: deptCounts, backgroundColor: deptBgColors, borderRadius: 4 }]
|
||||
},
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false }, datalabels: { color: '#1e293b', anchor: 'end', align: 'top', font: { weight: 'bold' } } },
|
||||
scales: { y: { beginAtZero: true, grace: '10%' }, x: { ticks: { autoSkip: false, maxRotation: 45, minRotation: 45 } } }
|
||||
}
|
||||
});
|
||||
|
||||
const rawPosData = @json($chartPos ?? []);
|
||||
const posLabels = rawPosData.map(d => d.position ? d.position.name : 'N/A');
|
||||
const posCounts = rawPosData.map(d => d.total);
|
||||
const posBgColors = rawPosData.map((d, i) => chartColors[(i + 3) % chartColors.length]);
|
||||
|
||||
new Chart(document.getElementById('posChart'), {
|
||||
type: 'doughnut',
|
||||
data: { labels: posLabels, datasets: [{ data: posCounts, backgroundColor: posBgColors, borderWidth: 2 }] },
|
||||
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, datalabels: { display: false } }, cutout: '65%' }
|
||||
});
|
||||
|
||||
const legendContainer = document.getElementById('posLegend');
|
||||
posLabels.forEach((label, index) => {
|
||||
const color = posBgColors[index];
|
||||
const count = posCounts[index];
|
||||
legendContainer.innerHTML += `<div class="flex items-center text-[11px] text-slate-600 bg-slate-50 px-2.5 py-1.5 rounded-lg border border-slate-200 shadow-sm shrink-0"><span class="w-3 h-3 rounded-full mr-2" style="background-color: ${color}"></span><span class="font-bold text-slate-800 mr-1">${count}</span> ${label}</div>`;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.select2-container .select2-selection--single { height: 38px; border-radius: 0.75rem; border-color: #e2e8f0; background-color: #f8fafc; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 38px; font-size: 0.875rem; color: #334155; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow { height: 36px; }
|
||||
</style>
|
||||
@endsection
|
||||
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Laporan Data Karyawan</title>
|
||||
<style>
|
||||
body { font-family: Helvetica, Arial, sans-serif; font-size: 10px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
||||
th, td { border: 1px solid #cbd5e1; padding: 6px; text-align: left; }
|
||||
th { background-color: #f8fafc; font-weight: bold; text-transform: uppercase; }
|
||||
.header { border-bottom: 2px solid #334155; padding-bottom: 10px; margin-bottom: 10px; }
|
||||
.header h2 { margin: 0 0 5px 0; font-size: 18px; color: #1e293b; }
|
||||
.header p { margin: 2px 0; font-size: 11px; color: #64748b; }
|
||||
.badge-active { color: #16a34a; font-weight: bold; }
|
||||
.badge-inactive { color: #dc2626; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<h2>Laporan Data Karyawan</h2>
|
||||
<p><b>Total Data:</b> {{ $employees->count() }} Karyawan (Sesuai Filter)</p>
|
||||
<p><b>Dicetak pada:</b> {{ \Carbon\Carbon::now()->translatedFormat('d F Y - H:i:s') }}</p>
|
||||
<p><b>Dicetak oleh:</b> {{ auth()->user()->first_name }} {{ auth()->user()->last_name }}</p>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="3%">No</th>
|
||||
<th width="12%">NIK & Inisial</th>
|
||||
<th width="20%">Nama Lengkap</th>
|
||||
<th width="15%">Email</th>
|
||||
<th width="5%">L/P</th>
|
||||
<th width="15%">Departemen</th>
|
||||
<th width="15%">Jabatan</th>
|
||||
<th width="10%">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($employees as $index => $emp)
|
||||
<tr>
|
||||
<td style="text-align: center;">{{ $index + 1 }}</td>
|
||||
<td>{{ $emp->nik }} <br> <small>{{ strtoupper($emp->initial ?? '') }}</small></td>
|
||||
<td>{{ $emp->first_name }} {{ $emp->last_name }}</td>
|
||||
<td>{{ $emp->email }}</td>
|
||||
<td style="text-align: center;">{{ $emp->gender }}</td>
|
||||
<td>{{ $emp->department->name ?? '-' }}</td>
|
||||
<td>{{ $emp->position->name ?? '-' }}</td>
|
||||
<td>
|
||||
<span class="{{ $emp->is_active ? 'badge-active' : 'badge-inactive' }}">
|
||||
{{ $emp->is_active ? 'Aktif' : 'Non-Aktif' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,127 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
|
||||
<!-- HEADER & TOMBOL AKSI -->
|
||||
<div class="mb-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<a href="{{ route('admin.employees.index') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Profil Karyawan</h2>
|
||||
</div>
|
||||
<div class="flex space-x-3">
|
||||
<a href="{{ route('admin.employees.edit', $employee->id) }}" class="px-4 py-2 bg-white border border-slate-200 text-slate-700 rounded-lg text-sm font-semibold hover:bg-slate-50 flex items-center shadow-sm">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
|
||||
Edit Data
|
||||
</a>
|
||||
<form action="{{ route('admin.employees.destroy', $employee->id) }}" method="POST" onsubmit="return confirm('Apakah Anda yakin ingin menghapus karyawan ini secara permanen?');">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="px-4 py-2 bg-red-50 text-red-600 border border-red-100 rounded-lg text-sm font-semibold hover:bg-red-100 flex items-center shadow-sm">
|
||||
Hapus
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KARTU PROFIL UTAMA -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden mb-6">
|
||||
<div class="bg-gradient-to-r from-blue-600 to-indigo-700 h-24 sm:h-32"></div>
|
||||
<div class="px-6 sm:px-8 pb-8 relative">
|
||||
<div class="-mt-12 sm:-mt-16 flex justify-between items-end mb-4">
|
||||
<!-- Avatar Inisial -->
|
||||
<div class="w-24 h-24 sm:w-32 sm:h-32 bg-white rounded-full p-1.5 shadow-lg">
|
||||
<div class="w-full h-full bg-blue-100 rounded-full flex items-center justify-center text-blue-700 text-3xl font-bold border-2 border-blue-200">
|
||||
{{ strtoupper(substr($employee->first_name, 0, 1)) }}{{ $employee->last_name ? strtoupper(substr($employee->last_name, 0, 1)) : '' }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Status Role Spatie -->
|
||||
<div class="mb-2">
|
||||
@foreach($employee->roles as $role)
|
||||
<span class="px-3 py-1 text-xs font-bold rounded-full bg-slate-900 text-white uppercase tracking-wider shadow-sm">
|
||||
Role: {{ $role->name }}
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 class="text-2xl font-extrabold text-slate-900">{{ $employee->first_name }} {{ $employee->last_name }}</h1>
|
||||
<p class="text-slate-500 font-medium">NIK: <span class="text-slate-800">{{ $employee->nik }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DETAIL INFORMASI -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
|
||||
<!-- Kolom Kiri: Info Pekerjaan -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6">
|
||||
<h3 class="text-sm font-bold text-slate-800 uppercase tracking-wider mb-4 pb-2 border-b border-slate-100">Informasi Pekerjaan</h3>
|
||||
|
||||
<dl class="space-y-4">
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Departemen</dt>
|
||||
<dd class="text-sm font-bold text-indigo-700 mt-1">{{ $employee->department->name ?? 'Belum ditentukan' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Posisi / Jabatan</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">{{ $employee->position->name ?? 'Belum ditentukan' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Tanggal Bergabung</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">{{ $employee->join_date ? $employee->join_date->translatedFormat('d F Y') : '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Matrix Training Utama</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">
|
||||
@if($employee->training_matrix_id)
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-emerald-100 text-emerald-800">
|
||||
Matriks Tersedia (ID: {{ $employee->training_matrix_id }})
|
||||
</span>
|
||||
@else
|
||||
<span class="text-slate-400 italic">Belum dikaitkan ke matriks</span>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Kolom Kanan: Info Personal -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6">
|
||||
<h3 class="text-sm font-bold text-slate-800 uppercase tracking-wider mb-4 pb-2 border-b border-slate-100">Informasi Personal & Kontak</h3>
|
||||
|
||||
<dl class="space-y-4 grid grid-cols-2 gap-x-4">
|
||||
<div class="col-span-2">
|
||||
<dt class="text-xs font-semibold text-slate-500">Email Perusahaan</dt>
|
||||
<dd class="text-sm font-bold text-blue-600 mt-1">{{ $employee->email }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">No. HP / Telepon</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">{{ $employee->phone ?? '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Nomor KTP</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">{{ $employee->identity_number ?? '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Jenis Kelamin</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">
|
||||
{{ $employee->gender == 'L' ? 'Laki-laki' : ($employee->gender == 'P' ? 'Perempuan' : '-') }}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Tanggal Lahir</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">{{ $employee->date_of_birth ? $employee->date_of_birth->translatedFormat('d F Y') : '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Inisial Nama</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1 uppercase">{{ $employee->initial ?? '-' }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user