update daftar pengguna

This commit is contained in:
2026-06-15 12:05:03 +07:00
parent 9d3b64a54d
commit d7e2cd7692
9 changed files with 471 additions and 272 deletions
+283 -53
View File
@@ -1,56 +1,286 @@
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasAddUser" aria-labelledby="offcanvasAddUserLabel">
<div class="offcanvas-header border-bottom">
<h5 id="offcanvasAddUserLabel" class="offcanvas-title">Tambah User Baru</h5>
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body mx-0 flex-grow-0 p-6 h-100">
<form class="add-new-user pt-0" id="formAddUser" action="{{ route('users.store') }}" method="POST">
@csrf
<div class="mb-4">
<label class="form-label" for="add-user-fullname">Nama Lengkap</label>
<input type="text" class="form-control" id="add-user-fullname" placeholder="John Doe" name="name" required />
</div>
<div class="mb-4">
<label class="form-label" for="add-user-username">Nama akun</label>
<input type="text" class="form-control" id="add-user-username" placeholder="john" name="username" required />
</div>
<div class="mb-4">
<label class="form-label" for="add-user-email">Email</label>
<input type="email" id="add-user-email" class="form-control" placeholder="john.doe@example.com" name="email" required />
</div>
<div class="mb-4">
<label class="form-label" for="add-user-role">User Role</label>
<select id="add-user-role" class="form-select" name="role" required>
<option value="">Pilih Role</option>
@foreach($roles as $roleEnum)
<option value="{{ $roleEnum->value }}">{{ ucfirst(str_replace('_', ' ', $roleEnum->value)) }}</option>
@endforeach
</select>
</div>
<div class="mb-4 form-password-toggle">
<label class="form-label" for="add-user-password">Password</label>
<div class="input-group input-group-merge">
<input type="password" id="add-user-password" name="password" class="form-control" placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;" required />
<span class="input-group-text cursor-pointer" onclick="togglePassword(this)">
<i class="ti tabler-eye-off"></i>
</span>
@extends('layout.main')
@section('content')
<div class="container-xxl flex-grow-1 container-p-y">
<div class="card">
<div class="card-header border-bottom mb-4 d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Tambah User Baru</h5>
<a href="{{ route('users.index') }}" class="btn btn-sm btn-label-secondary">
<i class="ti tabler-arrow-left me-1"></i> Kembali
</a>
</div>
<div class="card-body">
@if(session('error'))
<div class="alert alert-danger alert-dismissible" role="alert">
{{ session('error') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
</div>
<div class="mb-4 form-password-toggle">
<label class="form-label" for="add-user-password-confirm">Konfirmasi Password</label>
<div class="input-group input-group-merge">
<input type="password" id="add-user-password-confirm" name="password_confirmation" class="form-control" placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;" required />
<span class="input-group-text cursor-pointer" onclick="togglePassword(this)">
<i class="ti tabler-eye-off"></i>
</span>
@endif
<form id="formAddUser" action="{{ route('users.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<h6 class="mb-3">Informasi Akun</h6>
<div class="row">
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-profile-nama">Nama Lengkap<span class="text-danger">*</span></label>
<input type="text" class="form-control @error('nama') is-invalid @enderror" id="add-profile-nama" name="nama" value="{{ old('nama') }}" />
@error('nama')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-profile-email">Email<span class="text-danger">*</span></label>
<input type="email" class="form-control @error('email') is-invalid @enderror" id="add-profile-email" name="email" value="{{ old('email') }}" />
@error('email')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
</div>
<div class="mt-6 d-flex gap-3">
<button type="submit" class="btn btn-primary flex-grow-1 data-submit">Simpan</button>
<button type="reset" class="btn btn-label-secondary flex-grow-1" data-bs-dismiss="offcanvas">Batal</button>
</div>
</form>
<div class="row">
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-profile-username">Nama Akun<span class="text-danger">*</span></label>
<input type="text" class="form-control @error('username') is-invalid @enderror" id="add-profile-username" name="username" value="{{ old('username') }}" />
@error('username')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-profile-role">Role<span class="text-danger">*</span></label>
<select id="add-profile-role" name="role" class="select2 form-select @error('role') is-invalid @enderror" data-placeholder="Pilih Role">
<option value="">Pilih Role</option>
@foreach($roles as $roleEnum)
<option value="{{ $roleEnum->value }}" {{ old('role', \App\Enums\Role::PETANI->value) === $roleEnum->value ? 'selected' : '' }}>
{{ ucfirst(str_replace('_', ' ', $roleEnum->value)) }}
</option>
@endforeach
</select>
@error('role')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-profile-telepon">No. Telepon<span class="text-danger">*</span></label>
<input type="text" class="form-control @error('telepon') is-invalid @enderror" id="add-profile-telepon" name="telepon" value="{{ old('telepon') }}" />
@error('telepon')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4 form-control-validation form-password-toggle">
<label class="form-label" for="add-profile-password">Password<span class="text-danger">*</span></label>
<div class="input-group input-group-merge">
<input type="password" id="add-profile-password" name="password" class="form-control @error('password') is-invalid @enderror" />
<button type="button" class="input-group-text cursor-pointer password-toggle" aria-label="Lihat password">
<i class="ti tabler-eye-off"></i>
</button>
</div>
@error('password')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
</div>
<div class="col-md-6 mb-4 form-control-validation form-password-toggle">
<label class="form-label" for="add-profile-password-confirm">Konfirmasi Password<span class="text-danger">*</span></label>
<div class="input-group input-group-merge">
<input type="password" id="add-profile-password-confirm" name="password_confirmation" class="form-control" />
<button type="button" class="input-group-text cursor-pointer password-toggle" aria-label="Lihat konfirmasi password">
<i class="ti tabler-eye-off"></i>
</button>
</div>
</div>
</div>
<hr class="my-4">
<h6 class="mb-3">Lokasi & Alamat</h6>
<div class="row">
<div class="col-md-6 mb-4">
<label class="form-label" for="add-profile-provinsi">Provinsi</label>
<select id="add-profile-provinsi" class="select2 form-select" data-placeholder="Pilih Provinsi">
<option value="">Pilih Provinsi</option>
@foreach($provinsi as $prov)
<option value="{{ $prov->id }}">{{ $prov->nama }}</option>
@endforeach
</select>
</div>
<div class="col-md-6 mb-4">
<label class="form-label" for="add-profile-kabupaten">Kabupaten/Kota</label>
<select id="add-profile-kabupaten" class="select2 form-select" data-placeholder="Pilih Kabupaten/Kota">
<option value="">Pilih Kabupaten/Kota</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<label class="form-label" for="add-profile-kecamatan">Kecamatan</label>
<select id="add-profile-kecamatan" class="select2 form-select" data-placeholder="Pilih Kecamatan">
<option value="">Pilih Kecamatan</option>
</select>
</div>
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-profile-desa">Desa/Kelurahan<span class="text-danger">*</span></label>
<select id="add-profile-desa" name="desa_kelurahan_id" class="select2 form-select @error('desa_kelurahan_id') is-invalid @enderror" data-placeholder="Pilih Desa/Kelurahan">
<option value="">Pilih Desa/Kelurahan</option>
</select>
@error('desa_kelurahan_id')<div class="invalid-feedback d-block">{{ $message }}</div>@enderror
</div>
</div>
<div class="mb-4">
<label class="form-label" for="add-profile-alamat">Alamat Lengkap</label>
<textarea class="form-control" id="add-profile-alamat" name="alamat" rows="2">{{ old('alamat') }}</textarea>
</div>
<hr class="my-4">
<h6 class="mb-3">Dokumen Pribadi</h6>
<div class="row">
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-profile-no-ktp">NIK (KTP)</label>
<input type="text" class="form-control @error('ktp') is-invalid @enderror" id="add-profile-no-ktp" name="ktp" value="{{ old('ktp') }}" />
@error('ktp')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-profile-no-kk">No. KK</label>
<input type="text" class="form-control @error('kk') is-invalid @enderror" id="add-profile-no-kk" name="kk" value="{{ old('kk') }}" />
@error('kk')<div class="invalid-feedback">{{ $message }}</div>@enderror
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-profile-file-ktp">File KTP</label>
<input type="file" class="form-control @error('file_ktp') is-invalid @enderror" id="add-profile-file-ktp" name="file_ktp" accept="image/*,.pdf" />
@error('file_ktp')<div class="invalid-feedback">{{ $message }}</div>@enderror
<div id="add-preview-container-ktp" class="mt-3 d-none">
<img id="add-preview-image-ktp" src="" alt="Preview KTP" class="img-thumbnail d-none" style="width: 100%; max-height: 250px; object-fit: contain; background-color: #f8f9fa;">
<div id="add-preview-pdf-ktp" class="badge bg-label-danger p-2 w-100 d-none"><i class="ti tabler-file-type-pdf ti-sm me-1"></i> Dokumen PDF (Baru Terpilih)</div>
</div>
</div>
<div class="col-md-6 mb-4 form-control-validation">
<label class="form-label" for="add-profile-file-kk">File KK</label>
<input type="file" class="form-control @error('file_kk') is-invalid @enderror" id="add-profile-file-kk" name="file_kk" accept="image/*,.pdf" />
@error('file_kk')<div class="invalid-feedback">{{ $message }}</div>@enderror
<div id="add-preview-container-kk" class="mt-3 d-none">
<img id="add-preview-image-kk" src="" alt="Preview KK" class="img-thumbnail d-none" style="width: 100%; max-height: 250px; object-fit: contain; background-color: #f8f9fa;">
<div id="add-preview-pdf-kk" class="badge bg-label-danger p-2 w-100 d-none"><i class="ti tabler-file-type-pdf ti-sm me-1"></i> Dokumen PDF (Baru Terpilih)</div>
</div>
</div>
</div>
<div class="mt-4 text-end">
<a href="{{ route('users.index') }}" class="btn btn-label-secondary me-2">Batal</a>
<button type="submit" class="btn btn-primary">Simpan Perubahan</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
'use strict';
$(document).ready(function () {
$('.select2').select2();
$(document).on('click', '.password-toggle', function () {
const input = this.closest('.input-group').querySelector('input');
const icon = this.querySelector('i');
const isHidden = input.type === 'password';
input.type = isHidden ? 'text' : 'password';
icon.className = isHidden ? 'ti tabler-eye' : 'ti tabler-eye-off';
this.setAttribute('aria-label', isHidden ? 'Sembunyikan password' : 'Lihat password');
});
function handleFilePreview(inputId, containerId, imageId, pdfId) {
$(`#${inputId}`).on('change', function(event) {
const file = event.target.files[0];
const $container = $(`#${containerId}`);
const $image = $(`#${imageId}`);
const $pdf = $(`#${pdfId}`);
if (!file) {
$container.addClass('d-none');
return;
}
$container.removeClass('d-none');
if (file.type.match('image.*')) {
const reader = new FileReader();
reader.onload = function(e) {
$image.attr('src', e.target.result).removeClass('d-none');
$pdf.addClass('d-none');
};
reader.readAsDataURL(file);
} else if (file.type === 'application/pdf') {
$image.addClass('d-none').attr('src', '');
$pdf.removeClass('d-none');
} else {
$container.addClass('d-none');
}
});
}
function setupCascadingDropdown() {
$('#add-profile-provinsi').on('change', function () {
const provinsiId = $(this).val();
const $kabupaten = $('#add-profile-kabupaten');
const $kecamatan = $('#add-profile-kecamatan');
const $desa = $('#add-profile-desa');
$kabupaten.empty().append('<option value="">Pilih Kabupaten/Kota</option>').trigger('change');
$kecamatan.empty().append('<option value="">Pilih Kecamatan</option>').trigger('change');
$desa.empty().append('<option value="">Pilih Desa/Kelurahan</option>').trigger('change');
if (provinsiId) {
$.get(`/kabupatenkotabyprovinsi/${provinsiId}`, function (res) {
const dataList = Array.isArray(res) ? res : (res.data || []);
$.each(dataList, function (key, val) {
$kabupaten.append(`<option value="${val.id}">${val.nama}</option>`);
});
});
}
});
$('#add-profile-kabupaten').on('change', function () {
const kabupatenId = $(this).val();
const $kecamatan = $('#add-profile-kecamatan');
const $desa = $('#add-profile-desa');
$kecamatan.empty().append('<option value="">Pilih Kecamatan</option>').trigger('change');
$desa.empty().append('<option value="">Pilih Desa/Kelurahan</option>').trigger('change');
if (kabupatenId) {
$.get(`/kecamatanbykabupatenkota/${kabupatenId}`, function (res) {
const dataList = Array.isArray(res) ? res : (res.data || []);
$.each(dataList, function (key, val) {
$kecamatan.append(`<option value="${val.id}">${val.nama}</option>`);
});
});
}
});
$('#add-profile-kecamatan').on('change', function () {
const kecamatanId = $(this).val();
const $desa = $('#add-profile-desa');
$desa.empty().append('<option value="">Pilih Desa/Kelurahan</option>').trigger('change');
if (kecamatanId) {
$.get(`/desakelurahanbykecamatan/${kecamatanId}`, function (res) {
const dataList = Array.isArray(res) ? res : (res.data || []);
$.each(dataList, function (key, val) {
$desa.append(`<option value="${val.id}">${val.nama}</option>`);
});
});
}
});
}
handleFilePreview('add-profile-file-ktp', 'add-preview-container-ktp', 'add-preview-image-ktp', 'add-preview-pdf-ktp');
handleFilePreview('add-profile-file-kk', 'add-preview-container-kk', 'add-preview-image-kk', 'add-preview-pdf-kk');
setupCascadingDropdown();
});
</script>
@endpush
@@ -85,9 +85,9 @@
<div class="card">
<div class="card-header border-bottom d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Daftar Pengguna</h5>
<button class="btn btn-sm btn-primary" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasAddUser">
<i class="ti tabler-plus me-1"></i> Add New User
</button>
<a class="btn btn-sm btn-primary" href="{{ route('users.create') }}">
<i class="ti tabler-plus me-1"></i> Tambah User Baru
</a>
</div>
<form method="GET" action="{{ route('users.index') }}" id="filterForm">
@@ -204,7 +204,6 @@
{{ $users->appends(request()->query())->links('pagination::bootstrap-5') }}
</div>
</div>
@include('konfigurasi.user.add')
@include('konfigurasi.user.edit')
</div>
@endsection
@@ -369,77 +368,5 @@
});
});
});
document.addEventListener("DOMContentLoaded", function() {
function togglePassword(element) {
const input = element.parentElement.querySelector('input');
const icon = element.querySelector('i');
if (input.type === 'password') {
input.type = 'text';
icon.className = 'ti ti-eye tabler-eye';
} else {
input.type = 'password';
icon.className = 'ti ti-eye-off tabler-eye-off';
}
}
const formAddUser = document.getElementById('formAddUser');
const offcanvasElement = document.getElementById('offcanvasAddUser');
if (formAddUser && offcanvasElement) {
formAddUser.addEventListener('submit', function(e) {
e.preventDefault();
let formData = new FormData(this);
let submitBtn = this.querySelector('button[type="submit"]');
submitBtn.disabled = true;
let originalText = submitBtn.innerHTML;
submitBtn.innerHTML = 'Menyimpan...';
fetch(this.action, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;
if (data.status === 'success') {
let bsOffcanvas = bootstrap.Offcanvas.getInstance(offcanvasElement) || new bootstrap.Offcanvas(offcanvasElement);
bsOffcanvas.hide();
Swal.fire({
icon: 'success',
title: 'Berhasil!',
text: data.message,
showConfirmButton: false,
timer: 1500
}).then(() => {
window.location.reload();
});
} else {
let errorMsg = data.message || 'Terjadi kesalahan.';
if (data.errors) {
errorMsg = Object.values(data.errors).map(err => err.join(', ')).join('<br>');
}
Swal.fire({ icon: 'error', title: 'Gagal!', html: errorMsg });
}
})
.catch(error => {
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;
Swal.fire({ icon: 'error', title: 'Error', text: 'Koneksi bermasalah atau terjadi kesalahan server.' });
});
});
}
});
</script>
@endpush
@endpush