Files
aplikasi-ketertiban/resources/views/users/index.blade.php
T

498 lines
34 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.
@extends('layouts.app')
@section('title', 'Manajemen User')
@section('content')
<div x-data="userManager()">
{{-- Header --}}
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 class="text-2xl font-bold text-gray-900">Manajemen User</h1>
<p class="text-sm text-gray-500 mt-1">Kelola pengguna yang berhak mengakses aplikasi</p>
</div>
<button @click="openCreate()"
class="inline-flex items-center px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm">
<i class="fa-solid fa-plus mr-2"></i>
Tambah User
</button>
</div>
{{-- Filter --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-6">
<form method="GET" class="p-4">
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div class="relative">
<i class="fa-solid fa-magnifying-glass absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-400 text-sm"></i>
<input type="text" name="search" value="{{ request('search') }}"
placeholder="Cari nama, username, atau email..."
class="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
<select name="role"
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition bg-white">
<option value="">Semua Role</option>
<option value="admin" @selected(request('role') == 'admin')>Admin</option>
<option value="bk" @selected(request('role') == 'bk')>BK</option>
<option value="wali_kelas" @selected(request('role') == 'wali_kelas')>Wali Kelas</option>
<option value="staff" @selected(request('role') == 'staff')>Staff</option>
<option value="kepala_sekolah" @selected(request('role') == 'kepala_sekolah')>Kepala Sekolah</option>
<option value="waka_kesiswaan" @selected(request('role') == 'waka_kesiswaan')>Waka Kesiswaan</option>
<option value="ketua_tim" @selected(request('role') == 'ketua_tim')>Ketua Tim</option>
<option value="other" @selected(request('role') == 'other')>Other</option>
</select>
<div class="flex space-x-2">
<button type="submit" class="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition">
<i class="fa-solid fa-filter mr-1.5"></i> Filter
</button>
@if(request()->anyFilled(['search','role']))
<a href="{{ route('users.index') }}" class="px-4 py-2.5 text-sm font-medium text-gray-600 bg-white border border-gray-300 rounded-xl hover:bg-gray-50 transition">
<i class="fa-solid fa-xmark"></i>
</a>
@endif
</div>
</div>
</form>
</div>
{{-- Table --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">User</th>
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Username</th>
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Role</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-5 py-3.5 text-right text-xs font-semibold text-gray-500 uppercase tracking-wider">Aksi</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@forelse($users as $user)
<tr class="hover:bg-gray-50 transition {{ !$user->is_active ? 'opacity-60' : '' }}">
<td class="px-5 py-4">
<div class="flex items-center space-x-3">
<div class="w-9 h-9 rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0 bg-blue-100 text-blue-600">
{{ strtoupper(substr($user->name, 0, 1)) }}
</div>
<div>
<p class="text-sm font-medium text-gray-900">{{ $user->name }}</p>
<p class="text-xs text-gray-400">{{ $user->email }}</p>
@if ($user->phone)
<p class="text-xs text-gray-400 mt-0.5"><i class="fa-solid fa-phone text-[10px]"></i> {{ $user->phone }}</p>
@endif
</div>
</div>
</td>
<td class="px-5 py-4 text-sm text-gray-700 font-mono">{{ $user->username }}</td>
<td class="px-5 py-4">
<div class="flex flex-wrap gap-1.5">
@foreach($user->roleList() as $r)
@php
$roleBadges = [
'admin' => ['bg-blue-100 text-blue-700', 'fa-shield-halved'],
'bk' => ['bg-cyan-100 text-cyan-700', 'fa-user-tie'],
'wali_kelas' => ['bg-emerald-100 text-emerald-700', 'fa-chalkboard-user'],
'staff' => ['bg-violet-100 text-violet-700', 'fa-user-gear'],
'kepala_sekolah' => ['bg-amber-100 text-amber-700', 'fa-crown'],
'waka_kesiswaan' => ['bg-rose-100 text-rose-700', 'fa-user-graduate'],
'ketua_tim' => ['bg-teal-100 text-teal-700', 'fa-user-group'],
'other' => ['bg-gray-100 text-gray-700', 'fa-user'],
];
$rb = $roleBadges[$r] ?? ['bg-gray-100 text-gray-700', 'fa-user'];
@endphp
<span class="inline-flex items-center px-2.5 py-1 text-xs font-semibold rounded-full {{ $rb[0] }}">
<i class="fa-solid {{ $rb[1] }} mr-1"></i>
{{ ucfirst(str_replace('_', ' ', $r)) }}
</span>
@endforeach
</div>
</td>
<td class="px-5 py-4 text-center">
@if($user->is_active)
<span class="inline-flex items-center px-2 py-0.5 text-xs font-medium bg-green-100 text-green-700 rounded-full">
<span class="w-1.5 h-1.5 bg-green-500 rounded-full mr-1.5"></span>
Aktif
</span>
@else
<span class="inline-flex items-center px-2 py-0.5 text-xs font-medium bg-gray-100 text-gray-500 rounded-full">
<span class="w-1.5 h-1.5 bg-gray-400 rounded-full mr-1.5"></span>
Nonaktif
</span>
@endif
</td>
<td class="px-5 py-4 text-right">
<div class="flex items-center justify-end space-x-1">
<button @click="openEdit({{ $user->id }}, '{{ addslashes($user->name) }}', '{{ $user->username }}', '{{ $user->email }}', '{{ addslashes($user->phone ?? '') }}', '{{ implode(',', $user->roleList()) }}', {{ $user->is_active ? 'true' : 'false' }})"
class="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition" title="Edit">
<i class="fa-solid fa-pen-to-square"></i>
</button>
<button onclick="resetPassword({{ $user->id }}, '{{ addslashes($user->name) }}')"
class="p-2 text-amber-600 hover:bg-amber-50 rounded-lg transition" title="Reset Password">
<i class="fa-solid fa-key"></i>
</button>
@if($user->id !== auth()->id())
<form action="{{ route('users.force-logout', $user->id) }}" method="POST" class="inline"
x-data x-on:submit.prevent="if(await window.confirmSwal({text:'Akhiri sesi login ' + '{{ addslashes($user->name) }}' + '? User harus login ulang.'})) $el.submit()">
@csrf
<button type="submit"
class="p-2 rounded-lg transition {{ $user->active_session_token ? 'text-slate-500 hover:bg-slate-100' : 'text-gray-300 cursor-not-allowed' }}"
title="{{ $user->active_session_token ? 'Paksa logout (akhiri sesi aktif)' : 'Tidak ada sesi aktif' }}"
@if(!$user->active_session_token) disabled @endif>
<i class="fa-solid fa-right-from-bracket"></i>
</button>
</form>
<form action="{{ route('users.send-wa', $user->id) }}" method="POST" class="inline"
x-data="sendWaData({{ $user->id }}, '{{ addslashes($user->name) }}', {{ $user->phone ? 'true' : 'false' }})"
x-on:submit.prevent="confirmAndSubmit()">
@csrf
<button type="submit"
class="p-2 rounded-lg transition {{ $user->phone ? 'text-emerald-600 hover:bg-emerald-50' : 'text-gray-300 cursor-not-allowed' }}"
title="{{ $user->phone ? 'Kirim akun via WhatsApp' : 'Isi nomor HP dulu untuk kirim WhatsApp' }}"
@if(!$user->phone) disabled @endif>
<i class="fa-brands fa-whatsapp"></i>
</button>
</form>
<form action="{{ route('users.destroy', $user->id) }}" method="POST" class="inline"
x-data x-on:submit.prevent="if(await window.confirmSwal({text:'Hapus user ini?'})) $el.submit()">
@csrf @method('DELETE')
<button type="submit"
class="p-2 text-red-500 hover:bg-red-50 rounded-lg transition">
<i class="fa-solid fa-trash-can"></i>
</button>
</form>
@endif
</div>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-5 py-16 text-center">
<i class="fa-solid fa-users text-gray-300 text-4xl mb-3"></i>
<p class="text-sm text-gray-500">Tidak ada user ditemukan</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($users->hasPages())
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50">
{{ $users->appends(request()->query())->links() }}
</div>
@endif
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50 flex justify-between text-xs text-gray-500">
<span>{{ $users->firstItem() ?? 0 }}–{{ $users->lastItem() ?? 0 }} dari {{ $users->total() }}</span>
<span>{{ $users->total() }} total</span>
</div>
</div>
{{-- Modal --}}
<div x-show="modalOpen"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 z-50 overflow-y-auto" style="display: none;">
<div class="flex items-center justify-center min-h-screen px-4 py-6">
<div class="fixed inset-0 bg-gray-900/50 backdrop-blur-sm"></div>
<div class="relative bg-white rounded-3xl shadow-2xl w-full max-w-xl mx-4 max-h-[92vh] overflow-y-auto">
{{-- Header hero --}}
<div class="relative overflow-hidden bg-gradient-to-r from-blue-600 via-blue-500 to-sky-400 px-7 py-6 sticky top-0 z-10">
<div class="pointer-events-none absolute -top-10 -right-10 w-44 h-44 rounded-full bg-white/10 blur-2xl"></div>
<div class="pointer-events-none absolute -bottom-14 -left-8 w-40 h-40 rounded-full bg-sky-300/20 blur-3xl"></div>
<div class="pointer-events-none absolute inset-0 opacity-[0.06]"
style="background-image: radial-gradient(circle at 25% 40%, #fff 1.5px, transparent 1.5px); background-size: 20px 20px;"></div>
<div class="relative z-10 flex items-center justify-between gap-4">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-2xl bg-white/15 flex items-center justify-center ring-1 ring-white/25 backdrop-blur-sm">
<i class="fa-solid text-white text-xl" :class="isEditing ? 'fa-pen-to-square' : 'fa-user-plus'"></i>
</div>
<div>
<h3 class="text-base font-black text-white" x-text="isEditing ? 'Edit User' : 'Tambah User'"></h3>
<p class="text-xs text-white/75" x-text="isEditing ? 'Ubah data pengguna' : 'Buat akun pengguna baru'"></p>
</div>
</div>
<button @click="modalOpen = false"
class="w-9 h-9 rounded-xl bg-white/10 ring-1 ring-white/25 flex items-center justify-center text-white hover:bg-white/25 transition flex-shrink-0">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
</div>
{{-- Form --}}
<form :action="isEditing ? `/users/${editId}` : '{{ route('users.store') }}'" method="POST" class="p-7">
@csrf
<input type="hidden" name="_method" :value="isEditing ? 'PUT' : 'POST'">
<div class="space-y-5">
{{-- Nama --}}
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">Nama Lengkap <span class="text-red-500">*</span></label>
<div class="relative">
<i class="fa-solid fa-user absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 text-sm pointer-events-none"></i>
<input type="text" x-model="formName" name="name" required
class="w-full pl-11 pr-4 py-3 border-2 border-gray-200 rounded-2xl text-sm font-semibold bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-4 focus:ring-blue-500/15 focus:border-blue-500 transition"
placeholder="Nama lengkap">
</div>
</div>
{{-- Username + Email --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">Username <span class="text-red-500">*</span></label>
<div class="relative">
<i class="fa-solid fa-at absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 text-sm pointer-events-none"></i>
<input type="text" x-model="formUsername" name="username" required
class="w-full pl-11 pr-4 py-3 border-2 border-gray-200 rounded-2xl text-sm font-semibold bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-4 focus:ring-blue-500/15 focus:border-blue-500 transition"
placeholder="username">
</div>
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">Email <span class="text-red-500">*</span></label>
<div class="relative">
<i class="fa-solid fa-envelope absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 text-sm pointer-events-none"></i>
<input type="email" x-model="formEmail" name="email" required
class="w-full pl-11 pr-4 py-3 border-2 border-gray-200 rounded-2xl text-sm font-semibold bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-4 focus:ring-blue-500/15 focus:border-blue-500 transition"
placeholder="email@sekolah.sch.id">
</div>
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">Nomor HP</label>
<div class="relative">
<i class="fa-solid fa-phone absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 text-sm pointer-events-none"></i>
<input type="tel" x-model="formPhone" name="phone"
class="w-full pl-11 pr-4 py-3 border-2 border-gray-200 rounded-2xl text-sm font-semibold bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-4 focus:ring-blue-500/15 focus:border-blue-500 transition"
placeholder="08xxxxxxxxxx">
</div>
<p class="text-xs text-gray-400 mt-1.5">Nomor untuk kontak &amp; notifikasi (opsional)</p>
</div>
</div>
{{-- Password --}}
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">
Password <span class="text-red-500">*</span>
<span x-show="isEditing" class="text-xs font-normal text-gray-400">(kosongkan jika tidak diubah)</span>
</label>
<div class="relative">
<i class="fa-solid fa-lock absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 text-sm pointer-events-none"></i>
<input :type="showPassword ? 'text' : 'password'" x-model="formPassword" name="password" :required="!isEditing" minlength="6"
class="w-full pl-11 pr-12 py-3 border-2 border-gray-200 rounded-2xl text-sm font-semibold bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-4 focus:ring-blue-500/15 focus:border-blue-500 transition"
placeholder="Min. 6 karakter">
<button type="button" @click="showPassword = !showPassword"
class="absolute right-3.5 top-1/2 -translate-y-1/2 w-8 h-8 rounded-lg flex items-center justify-center text-gray-400 hover:text-blue-600 hover:bg-blue-50 transition">
<i class="fa-solid text-sm" :class="showPassword ? 'fa-eye-slash' : 'fa-eye'"></i>
</button>
</div>
</div>
{{-- Role (multi) --}}
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">Role / Hak Akses <span class="text-red-500">*</span> <span class="text-xs font-normal text-gray-400">(bisa pilih lebih dari satu)</span></label>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2.5">
<template x-for="r in [
{ v: 'admin', label: 'Admin', desc: 'Full akses', icon: 'fa-user-shield', color: '#2563eb' },
{ v: 'bk', label: 'BK', desc: 'Input & laporan', icon: 'fa-user-tie', color: '#14b8a6' },
{ v: 'wali_kelas', label: 'Wali Kelas', desc: 'Lihat kelas sendiri', icon: 'fa-chalkboard-user', color: '#10b981' },
{ v: 'staff', label: 'Staff', desc: 'Terbatas', icon: 'fa-user-gear', color: '#8b5cf6' },
{ v: 'kepala_sekolah', label: 'Kepala Sekolah', desc: 'Pimpinan / persetujuan', icon: 'fa-crown', color: '#f59e0b' },
{ v: 'waka_kesiswaan', label: 'Waka Kesiswaan', desc: 'Pengelola kesiswaan', icon: 'fa-user-graduate', color: '#f43f5e' },
{ v: 'ketua_tim', label: 'Ketua Tim', desc: 'Koordinator tim', icon: 'fa-user-group', color: '#14b8a6' },
{ v: 'other', label: 'Other', desc: 'Guest / Terbatas', icon: 'fa-user', color: '#64748b' },
]" :key="r.v">
<label class="flex items-center gap-3 rounded-2xl border-2 p-3 cursor-pointer transition-all duration-150 active:scale-[0.98]"
:class="formRoles.includes(r.v) ? 'border-transparent text-white shadow-md ring-2' : 'border-gray-200 bg-gray-50/50 hover:border-gray-300'"
:style="formRoles.includes(r.v) ? 'background: linear-gradient(135deg, ' + r.color + ', ' + r.color + 'bb);' : ''">
<input type="checkbox" :name="'roles[]'" :value="r.v" x-model="formRoles" class="sr-only">
<div class="w-9 h-9 rounded-xl flex items-center justify-center shrink-0"
:class="formRoles.includes(r.v) ? 'bg-white/20' : 'bg-white shadow-sm border border-gray-200'">
<i :class="'fa-solid ' + r.icon" class="text-sm" :class="formRoles.includes(r.v) ? 'text-white' : 'text-gray-400'"></i>
</div>
<div class="min-w-0">
<p class="text-sm font-bold" :class="formRoles.includes(r.v) ? 'text-white' : 'text-gray-800'" x-text="r.label"></p>
<p class="text-[10px]" :class="formRoles.includes(r.v) ? 'text-white/70' : 'text-gray-400'" x-text="r.desc"></p>
</div>
<i class="fa-solid fa-circle-check ml-auto text-base"
:class="formRoles.includes(r.v) ? 'text-white' : 'text-gray-200'"></i>
</label>
</template>
</div>
</div>
{{-- Aktif (edit) --}}
<div x-show="isEditing" x-transition class="flex items-center justify-between p-4 bg-gray-50 rounded-2xl border border-gray-100">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-white shadow-sm border border-gray-200 flex items-center justify-center">
<i class="fa-solid fa-power-off text-emerald-500 text-xs"></i>
</div>
<div>
<p class="text-sm font-bold text-gray-900">Akun Aktif</p>
<p class="text-xs text-gray-500">Nonaktifkan untuk menonaktifkan akun</p>
</div>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" x-model="formActive" name="is_active" value="1" class="sr-only">
<span class="relative h-6 w-11 rounded-full transition-colors duration-200" :class="formActive ? 'bg-gradient-to-r from-emerald-500 to-teal-500' : 'bg-gray-300'">
<span class="absolute left-0.5 top-0.5 h-5 w-5 rounded-full bg-white shadow transition-transform duration-200" :class="formActive ? 'translate-x-5' : ''"></span>
</span>
</label>
</div>
</div>
{{-- Footer --}}
<div class="flex items-center justify-end gap-3 mt-7 pt-5 border-t border-gray-100">
<button type="button" @click="modalOpen = false"
class="px-6 py-3 text-sm font-bold text-gray-600 bg-white border-2 border-gray-200 rounded-2xl hover:bg-gray-50 transition">
Batal
</button>
<button type="submit"
class="px-7 py-3 text-sm font-black text-white bg-gradient-to-r from-blue-600 to-sky-500 rounded-2xl shadow-lg shadow-blue-200 hover:-translate-y-0.5 hover:brightness-105 transition-all inline-flex items-center gap-2 active:scale-95">
<i class="fa-solid fa-floppy-disk"></i>
<span x-text="isEditing ? 'Simpan Perubahan' : 'Tambah User'"></span>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@push('scripts')
<script>
function sendWaData(userId, userName, hasPhone) {
return {
confirmAndSubmit() {
if (!hasPhone) return;
const form = this.$el.closest('form');
Swal.fire({
title: 'Kirim Akun via WhatsApp',
html: 'Kirim link aplikasi, username, dan <b>password baru</b> ke <b>' + userName + '</b>?' +
'<br><br><span style="font-size:12px;color:#6b7280">Password baru akan dibuat otomatis (password lama tidak bisa dikirim karena tersimpan ter-enkripsi).</span>',
icon: 'question',
confirmButtonText: '<i class="fa-brands fa-whatsapp"></i> Ya, Kirim',
confirmButtonColor: '#25D366',
showCancelButton: true,
cancelButtonText: 'Batal',
}).then((result) => {
if (!result.isConfirmed || !form) return;
fetch(form.action, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Accept': 'application/json',
},
})
.then(r => r.json().catch(() => ({ error: 'Respons tidak valid dari server' })))
.then(data => {
if (data.error) {
Swal.fire({ icon: 'error', title: 'Gagal', text: data.error });
return;
}
const msgLines = [
'<b>📲 Akun Aplikasi ' + (data.app_name || '') + '</b>',
'Yth. ' + (data.name || userName) + ',',
'Link: ' + (data.app_url || ''),
'Username: <b>' + (data.username || '') + '</b>',
'Password: <b>' + (data.password || '') + '</b>',
'<span style="color:#6b7280;font-size:12px">Mohon segera ganti password setelah login.</span>',
].join('<br>');
Swal.fire({
icon: 'success',
title: 'Akun Siap Dikirim',
html: '<div style="text-align:left;font-size:13px;line-height:1.8">' + msgLines + '</div>' +
'<a href="' + data.url + '" target="_blank" rel="noopener" ' +
'style="display:inline-block;margin-top:16px;padding:11px 26px;background:#25D366;color:#fff;' +
'border-radius:12px;font-weight:700;text-decoration:none;font-size:14px">' +
'<i class="fa-brands fa-whatsapp"></i> Buka WhatsApp</a>' +
'<p style="font-size:11px;color:#9ca3af;margin-top:10px">WhatsApp dibuka di tab baru — aplikasi tetap terbuka.</p>',
showConfirmButton: false,
showCloseButton: true,
});
})
.catch(() => {
Swal.fire({ icon: 'error', title: 'Gagal', text: 'Tidak dapat menghubungi server.' });
});
});
}
};
}
function userManager() {
return {
modalOpen: false, isEditing: false, editId: null,
formName: '', formUsername: '', formEmail: '', formPhone: '', formPassword: '', formRoles: ['bk'], formActive: true,
showPassword: false,
get roleInfo() {
const map = {
admin: { icon: 'fa-user-shield', color: '#2563eb' },
bk: { icon: 'fa-user-tie', color: '#14b8a6' },
wali_kelas: { icon: 'fa-chalkboard-user', color: '#10b981' },
staff: { icon: 'fa-user-gear', color: '#8b5cf6' },
kepala_sekolah: { icon: 'fa-crown', color: '#f59e0b' },
waka_kesiswaan: { icon: 'fa-user-graduate', color: '#f43f5e' },
ketua_tim: { icon: 'fa-user-group', color: '#14b8a6' },
other: { icon: 'fa-user', color: '#64748b' },
};
const primary = this.formRoles[0] || 'bk';
return map[primary] || { icon: 'fa-user', color: '#64748b' };
},
openCreate() {
this.isEditing = false; this.editId = null;
this.formName = ''; this.formUsername = ''; this.formEmail = ''; this.formPhone = ''; this.formPassword = '';
this.formRoles = ['bk']; this.formActive = true; this.showPassword = false; this.modalOpen = true;
},
openEdit(id, name, username, email, phone, rolesCsv, active) {
this.isEditing = true; this.editId = id;
this.formName = name; this.formUsername = username; this.formEmail = email; this.formPhone = phone;
this.formPassword = '';
this.formRoles = rolesCsv ? rolesCsv.split(',') : ['bk'];
this.formActive = active; this.showPassword = false; this.modalOpen = true;
}
};
}
function resetPassword(userId, userName) {
Swal.fire({
title: 'Reset Password untuk ' + userName,
html: '<input type="password" id="new-password" class="swal2-input" placeholder="Password baru (min. 6 karakter)" minlength="6">' +
'<input type="password" id="confirm-password" class="swal2-input" placeholder="Konfirmasi password">',
confirmButtonText: '<i class="fa-solid fa-floppy-disk"></i> Simpan',
showCancelButton: true,
cancelButtonText: 'Batal',
preConfirm: () => {
const pwd = document.getElementById('new-password').value;
const confirm = document.getElementById('confirm-password').value;
if (!pwd || pwd.length < 6) {
Swal.showValidationMessage('Password minimal 6 karakter');
return false;
}
if (pwd !== confirm) {
Swal.showValidationMessage('Password tidak cocok');
return false;
}
return pwd;
}
}).then((result) => {
if (result.isConfirmed) {
fetch('{{ url('users') }}/' + userId + '/reset-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Accept': 'application/json',
},
body: JSON.stringify({ new_password: result.value }),
}).then(r => {
if (r.redirected) window.location.href = r.url;
else location.reload();
});
}
});
}
</script>
@endpush
@endsection