finishing

This commit is contained in:
rahmagustin 2026-02-19 13:18:51 +07:00
parent 217fb8907e
commit 26bbeb897f
6 changed files with 342 additions and 259 deletions

View File

@ -29,7 +29,11 @@ public function show($id)
public function tanggapi(Request $request, $id)
{
$request->validate([
'tanggapan_admin' => 'required'
'tanggapan_admin' => 'required|string|min:5|max:1000'
], [
'tanggapan_admin.required' => 'Tanggapan admin wajib diisi.',
'tanggapan_admin.min' => 'Tanggapan minimal 5 karakter.',
'tanggapan_admin.max' => 'Tanggapan maksimal 1000 karakter.',
]);
$aduan = AduanTps::findOrFail($id);

View File

@ -20,49 +20,46 @@ public function update(Request $request)
{
$admin = Auth::user();
$request->validate([
'name' => 'required|string|max:255',
'username' => 'required|string|max:100',
'foto' => 'nullable|image|mimes:jpg,jpeg,png|max:2048',
'password' => 'nullable|min:6|confirmed',
]);
$request->validate(
[
'name' => 'required|string|max:255',
'username' => 'required|string|max:100|unique:users,username,' . $admin->id_users . ',id_users',
'foto' => 'nullable|image|mimes:jpg,jpeg,png|max:2048',
'password' => 'nullable|min:6|confirmed',
],
[
'name.required' => 'Nama wajib diisi',
'username.required' => 'Username wajib diisi',
'username.unique' => 'Username sudah digunakan',
'foto.image' => 'File harus berupa gambar',
'foto.mimes' => 'Format foto harus JPG atau PNG',
'foto.max' => 'Ukuran foto maksimal 2 MB',
'password.min' => 'Password minimal 6 karakter',
'password.confirmed' => 'Konfirmasi password tidak cocok',
]
);
// ======================
// UPDATE DATA DASAR
// ======================
$admin->name = $request->name;
// update data dasar
$admin->name = $request->name;
$admin->username = $request->username;
// ======================
// PROSES FOTO
// ======================
// upload foto
if ($request->hasFile('foto')) {
$path = public_path('assets/admin/foto-admin');
// buat folder kalau belum ada
if (!File::exists($path)) {
File::makeDirectory($path, 0755, true);
}
// hapus foto lama
if ($admin->foto && File::exists($path.'/'.$admin->foto)) {
File::delete($path.'/'.$admin->foto);
if ($admin->foto && file_exists(public_path('assets/admin/foto-admin/' . $admin->foto))) {
unlink(public_path('assets/admin/foto-admin/' . $admin->foto));
}
$file = $request->file('foto');
$namaFoto = time().'_'.uniqid().'.'.$file->getClientOriginalExtension();
$namaFoto = time() . '_' . uniqid() . '.' . $file->getClientOriginalExtension();
// SIMPAN FOTO (INI KUNCI)
$file->move($path, $namaFoto);
$file->move(public_path('assets/admin/foto-admin'), $namaFoto);
// simpan ke DB
$admin->foto = $namaFoto;
}
// ======================
// PASSWORD
// ======================
// update password jika diisi
if ($request->filled('password')) {
$admin->password = Hash::make($request->password);
}

View File

@ -8,16 +8,20 @@
class AduanController extends Controller
{
/**
* Menampilkan halaman form aduan TPS
*/
public function index(Request $request)
{
$title = 'Aduan TPS';
$tps = null;
if ($request->filled('tps_id')) {
$tps = LokasiTps::with('kategori')
->where('id_tps', $request->tps_id)
->first();
}
$listTps = LokasiTps::orderBy('nama_tps')->get();
return view('user.aduan-tps', compact(
@ -27,24 +31,42 @@ public function index(Request $request)
));
}
/**
* Menyimpan data aduan TPS
*/
public function store(Request $request)
{
$request->validate([
'lokasi_tps_id' => 'required|exists:lokasi_tps,id_tps',
'nama_pelapor' => 'required|string|max:100',
'alamat_pelapor'=> 'required|string',
'no_pelapor' => 'required|string|max:20',
'isi_aduan' => 'required|string',
'bukti_foto' => 'required|image|mimes:jpg,jpeg,png|max:4096',
'lokasi_tps_id' => 'required|exists:lokasi_tps,id_tps',
'nama_pelapor' => 'required|string|max:100',
'alamat_pelapor' => 'required|string',
'no_pelapor' => 'required|digits_between:1,13|regex:/^[0-9]+$/',
'isi_aduan' => 'required|string',
'bukti_foto' => 'required|image|mimes:jpg,jpeg,png|max:4096',
], [
'lokasi_tps_id.required' => 'TPS wajib dipilih',
'bukti_foto.required' => 'Foto bukti wajib diunggah',
'bukti_foto.image' => 'File harus berupa gambar',
'lokasi_tps_id.required' => 'TPS wajib dipilih',
'lokasi_tps_id.exists' => 'TPS tidak valid',
'nama_pelapor.required' => 'Nama pelapor wajib diisi',
'alamat_pelapor.required' => 'Alamat pelapor wajib diisi',
'no_pelapor.required' => 'Nomor HP wajib diisi',
'no_pelapor.digits_between'=> 'Nomor HP maksimal 13 angka',
'no_pelapor.regex' => 'Nomor HP hanya boleh berisi angka',
'isi_aduan.required' => 'Isi aduan wajib diisi',
'bukti_foto.required' => 'Foto bukti wajib diunggah',
'bukti_foto.image' => 'File harus berupa gambar',
'bukti_foto.mimes' => 'Format gambar harus jpg, jpeg, atau png',
'bukti_foto.max' => 'Ukuran gambar maksimal 4 MB',
]);
// Upload foto bukti
$fotoPath = $request->file('bukti_foto')
->store('aduan', 'public');
// Simpan ke database
AduanTps::create([
'lokasi_tps_id' => $request->lokasi_tps_id,
'nama_pelapor' => $request->nama_pelapor,

View File

@ -1,94 +1,83 @@
@extends('admin.template')
@section('content')
<div class="content-wrapper">
<div class="row">
<div class="col-12 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<div class="content-wrapper">
<div class="row">
<div class="col-12 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h4 class="card-title">Tanggapi Aduan TPS</h4>
<p class="card-description">
Form tanggapan admin terhadap aduan masyarakat
</p>
<h4 class="card-title">Tanggapi Aduan TPS</h4>
<p class="card-description">
Form tanggapan admin terhadap aduan masyarakat
</p>
<form action="{{ route('admin.aduan.tanggapi', $aduan->id_aduan) }}"
method="POST"
class="forms-sample">
@csrf
<form action="{{ route('admin.aduan.tanggapi', $aduan->id_aduan) }}" method="POST"
class="forms-sample">
@csrf
{{-- TPS --}}
<div class="form-group">
<label>TPS yang Diadukan</label>
<input type="text" class="form-control"
value="{{ $aduan->lokasiTps->nama_tps ?? '-' }}"
readonly>
<small class="text-muted">
{{ $aduan->lokasiTps->alamat_tps ?? '' }}
</small>
</div>
{{-- Nama Pelapor --}}
<div class="form-group">
<label>Nama Pelapor</label>
<input type="text"
class="form-control"
value="{{ $aduan->nama_pelapor }}"
readonly>
</div>
{{-- Alamat Pelapor --}}
<div class="form-group">
<label>Alamat Pelapor</label>
<input type="text"
class="form-control"
value="{{ $aduan->alamat_pelapor }}"
readonly>
</div>
{{-- Isi Aduan --}}
<div class="form-group">
<label>Isi Aduan</label>
<textarea class="form-control"
rows="4"
readonly>{{ $aduan->isi_aduan }}</textarea>
</div>
{{-- Bukti Foto --}}
@if ($aduan->bukti_foto)
{{-- TPS --}}
<div class="form-group">
<label>Bukti Foto</label><br>
<img src="{{ asset('storage/' . $aduan->bukti_foto) }}"
alt="Bukti Aduan"
class="border rounded img-fluid"
style="max-height: 260px;">
<label>TPS yang Diadukan</label>
<input type="text" class="form-control" value="{{ $aduan->lokasiTps->nama_tps ?? '-' }}"
readonly>
<small class="text-muted">
{{ $aduan->lokasiTps->alamat_tps ?? '' }}
</small>
</div>
@endif
{{-- Tanggapan Admin --}}
<div class="form-group">
<label>Tanggapan Admin</label>
<textarea name="tanggapan_admin"
class="form-control"
rows="4"
placeholder="Masukkan tanggapan admin..."
required>{{ old('tanggapan_admin', $aduan->tanggapan_admin) }}</textarea>
</div>
{{-- Nama Pelapor --}}
<div class="form-group">
<label>Nama Pelapor</label>
<input type="text" class="form-control" value="{{ $aduan->nama_pelapor }}" readonly>
</div>
{{-- Tombol --}}
<button type="submit" class="mr-2 btn btn-primary">
Simpan Tanggapan
</button>
<a href="{{ route('admin.aduan.index') }}"
class="btn btn-light">
Kembali
</a>
{{-- Alamat Pelapor --}}
<div class="form-group">
<label>Alamat Pelapor</label>
<input type="text" class="form-control" value="{{ $aduan->alamat_pelapor }}" readonly>
</div>
</form>
{{-- Isi Aduan --}}
<div class="form-group">
<label>Isi Aduan</label>
<textarea class="form-control" rows="4" readonly>{{ $aduan->isi_aduan }}</textarea>
</div>
{{-- Bukti Foto --}}
@if ($aduan->bukti_foto)
<div class="form-group">
<label>Bukti Foto</label><br>
<img src="{{ asset('storage/' . $aduan->bukti_foto) }}" alt="Bukti Aduan"
class="border rounded img-fluid" style="max-height: 260px;">
</div>
@endif
{{-- Tanggapan Admin --}}
<div class="form-group">
<label>Tanggapan Admin</label>
<textarea name="tanggapan_admin" class="form-control @error('tanggapan_admin') is-invalid @enderror" rows="4"
placeholder="Masukkan tanggapan admin...">{{ old('tanggapan_admin', $aduan->tanggapan_admin) }}</textarea>
@error('tanggapan_admin')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
{{-- Tombol --}}
<button type="submit" class="mr-2 btn btn-primary">
Simpan Tanggapan
</button>
<a href="{{ route('admin.aduan.index') }}" class="btn btn-light">
Kembali
</a>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -9,6 +9,7 @@
<h4 class="mb-4 card-title">Profil Admin</h4>
{{-- SUCCESS --}}
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
@ -20,7 +21,7 @@
<div class="row">
<!-- KIRI : FOTO -->
{{-- KIRI : FOTO --}}
<div class="text-center col-md-4 border-right">
<img
@ -34,7 +35,16 @@ class="mb-3 rounded-circle"
>
<div class="form-group">
<input type="file" name="foto" class="form-control-file">
<input type="file"
name="foto"
class="form-control-file @error('foto') is-invalid @enderror">
@error('foto')
<div class="mt-1 text-danger">
{{ $message }}
</div>
@enderror
<small class="mt-2 text-muted d-block">
JPG / PNG, maksimal 2MB
</small>
@ -42,40 +52,66 @@ class="mb-3 rounded-circle"
</div>
<!-- KANAN : FORM -->
{{-- KANAN : FORM --}}
<div class="col-md-8">
{{-- NAMA --}}
<div class="form-group">
<label>Nama</label>
<input type="text" name="name"
class="form-control"
value="{{ $admin->name }}" required>
<input type="text"
name="name"
class="form-control @error('name') is-invalid @enderror"
value="{{ old('name', $admin->name) }}">
@error('name')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
{{-- USERNAME --}}
<div class="form-group">
<label>Username</label>
<input type="text" name="username"
class="form-control"
value="{{ $admin->username }}" required>
<input type="text"
name="username"
class="form-control @error('username') is-invalid @enderror"
value="{{ old('username', $admin->username) }}">
@error('username')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
{{-- ROLE --}}
<div class="form-group">
<label>Role</label>
<input type="text"
class="form-control"
value="{{ $admin->role }}" disabled>
value="{{ $admin->role }}"
disabled>
</div>
<hr>
{{-- PASSWORD --}}
<div class="form-group">
<label>Password Baru (opsional)</label>
<input type="password"
name="password"
class="form-control"
class="form-control @error('password') is-invalid @enderror"
placeholder="Kosongkan jika tidak diganti">
@error('password')
<div class="invalid-feedback">
{{ $message }}
</div>
@enderror
</div>
{{-- KONFIRMASI PASSWORD --}}
<div class="form-group">
<label>Konfirmasi Password</label>
<input type="password"
@ -83,6 +119,7 @@ class="form-control"
class="form-control">
</div>
{{-- BUTTON --}}
<div class="mt-4">
<button type="submit" class="btn btn-primary">
Simpan

View File

@ -2,143 +2,177 @@
@section('content')
<!-- Page Title -->
<div class="page-title">
<div class="container d-lg-flex justify-content-between align-items-center">
<h1 class="mb-2 mb-lg-0">Aduan TPS</h1>
<nav class="breadcrumbs">
<ol>
<li><a href="{{ url('/') }}">Beranda</a></li>
<li class="current">Aduan TPS</li>
</ol>
</nav>
</div>
<!-- Page Title -->
<div class="page-title">
<div class="container d-lg-flex justify-content-between align-items-center">
<h1 class="mb-2 mb-lg-0">Aduan TPS</h1>
<nav class="breadcrumbs">
<ol>
<li><a href="{{ url('/') }}">Beranda</a></li>
<li class="current">Aduan TPS</li>
</ol>
</nav>
</div>
<!-- End Page Title -->
</div>
<!-- End Page Title -->
<section id="contact" class="contact section">
<section id="contact" class="contact section">
<div class="container" data-aos="fade">
<div class="row gy-5 gx-lg-5">
<div class="container" data-aos="fade">
<div class="row gy-5 gx-lg-5">
{{-- KOLOM KIRI --}}
<div class="col-lg-4">
<div class="info">
<h3 class="mb-3">TPS yang Diadukan</h3>
{{-- KOLOM KIRI --}}
<div class="col-lg-4">
<div class="info">
<h3 class="mb-3">TPS yang Diadukan</h3>
{{-- FOTO TPS --}}
<div class="mb-3 d-none" id="foto-wrapper">
<img id="foto-tps" class="img-fluid w-100"
style="object-fit:cover; max-height:220px;">
</div>
{{-- FOTO TPS --}}
<div class="mb-3 d-none" id="foto-wrapper">
<img id="foto-tps" class="img-fluid w-100" style="object-fit:cover; max-height:220px;">
</div>
{{-- INFO TPS --}}
<div id="info-tps">
@if ($tps)
<p class="mb-1"><strong>{{ $tps->nama_tps }}</strong></p>
<p class="mb-1 text-muted">{{ $tps->alamat_tps }}</p>
@else
<p class="mb-2 text-muted">Silakan pilih TPS yang akan diadukan</p>
{{-- INFO TPS --}}
<div id="info-tps">
@if ($tps)
<p class="mb-1"><strong>{{ $tps->nama_tps }}</strong></p>
<p class="mb-1 text-muted">{{ $tps->alamat_tps }}</p>
@else
<p class="mb-2 text-muted">
Silakan pilih TPS yang akan diadukan
</p>
<select id="pilih-tps" class="form-select">
<option value="">Pilih TPS</option>
@foreach ($listTps as $item)
<option value="{{ $item->id_tps }}"
data-nama="{{ $item->nama_tps }}"
data-alamat="{{ $item->alamat_tps }}"
data-foto="{{ $item->foto_tps ? asset('storage/'.$item->foto_tps) : asset('assets/user/img/no-image.png') }}">
{{ $item->nama_tps }}
</option>
@endforeach
</select>
<select id="pilih-tps" class="form-select">
<option value="">Pilih TPS</option>
@foreach ($listTps as $item)
<option value="{{ $item->id_tps }}" data-nama="{{ $item->nama_tps }}"
data-alamat="{{ $item->alamat_tps }}"
data-foto="{{ $item->foto_tps ? asset('storage/' . $item->foto_tps) : asset('assets/user/img/no-image.png') }}">
{{ $item->nama_tps }}
</option>
@endforeach
</select>
@endif
</div>
@error('lokasi_tps_id')
<small class="text-danger">{{ $message }}</small>
@enderror
@endif
</div>
</div>
{{-- KOLOM KANAN --}}
<div class="col-lg-8">
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
<form action="{{ route('user.aduan.store') }}" method="POST" class="aduan-form"
enctype="multipart/form-data">
@csrf
{{-- PENTING --}}
<input type="hidden" name="lokasi_tps_id" id="lokasi_tps_id"
value="{{ $tps ? $tps->id_tps : '' }}">
<div class="row">
<div class="col-md-6 form-group">
<input type="text" name="nama_pelapor" class="form-control" placeholder="Nama Pelapor"
required>
</div>
<div class="mt-3 col-md-6 form-group mt-md-0">
<input type="tel" name="no_pelapor" class="form-control" placeholder="No. Telp Pelapor"
required>
</div>
</div>
<div class="mt-3 form-group">
<input type="text" name="alamat_pelapor" class="form-control" placeholder="Alamat Pelapor"
required>
</div>
<div class="mt-3 form-group">
<textarea name="isi_aduan" class="form-control" placeholder="Isi Aduan" required></textarea>
</div>
{{-- FILE INPUT (SUDAH SERAGAM) --}}
<div class="mt-3 form-group">
<input type="file" name="bukti_foto" class="form-control file-input" accept="image/*">
</div>
<div class="mt-4 text-center">
<button type="submit" class="px-4 btn btn-success">Kirim Aduan</button>
</div>
</form>
</div>
</div>
{{-- KOLOM KANAN --}}
<div class="col-lg-8">
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
<form action="{{ route('user.aduan.store') }}" method="POST"
enctype="multipart/form-data" class="aduan-form">
@csrf
{{-- HIDDEN TPS --}}
<input type="hidden" name="lokasi_tps_id" id="lokasi_tps_id"
value="{{ old('lokasi_tps_id', $tps ? $tps->id_tps : '') }}">
<div class="row">
{{-- NAMA --}}
<div class="col-md-6 form-group">
<input type="text"
name="nama_pelapor"
class="form-control"
placeholder="Nama Pelapor"
value="{{ old('nama_pelapor') }}">
@error('nama_pelapor')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
{{-- NO TELP --}}
<div class="mt-3 col-md-6 form-group mt-md-0">
<input type="tel"
name="no_pelapor"
class="form-control"
placeholder="No. Telp Pelapor"
value="{{ old('no_pelapor') }}">
@error('no_pelapor')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
</div>
{{-- ALAMAT --}}
<div class="mt-3 form-group">
<input type="text"
name="alamat_pelapor"
class="form-control"
placeholder="Alamat Pelapor"
value="{{ old('alamat_pelapor') }}">
@error('alamat_pelapor')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
{{-- ISI ADUAN --}}
<div class="mt-3 form-group">
<textarea name="isi_aduan"
class="form-control"
placeholder="Isi Aduan">{{ old('isi_aduan') }}</textarea>
@error('isi_aduan')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
{{-- FOTO --}}
<div class="mt-3 form-group">
<input type="file"
name="bukti_foto"
class="form-control"
accept="image/*">
@error('bukti_foto')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
<div class="mt-4 text-center">
<button type="submit" class="px-4 btn btn-success">
Kirim Aduan
</button>
</div>
</form>
</div>
</div>
</section>
</div>
</section>
{{-- SCRIPT TPS --}}
<script>
document.addEventListener('DOMContentLoaded', function() {
const selectTps = document.getElementById('pilih-tps');
const infoTps = document.getElementById('info-tps');
const fotoWrapper = document.getElementById('foto-wrapper');
const fotoTps = document.getElementById('foto-tps');
const inputLokasi = document.getElementById('lokasi_tps_id');
{{-- SCRIPT PILIH TPS --}}
<script>
document.addEventListener('DOMContentLoaded', function () {
const selectTps = document.getElementById('pilih-tps');
const infoTps = document.getElementById('info-tps');
const fotoWrapper = document.getElementById('foto-wrapper');
const fotoTps = document.getElementById('foto-tps');
const inputLokasi = document.getElementById('lokasi_tps_id');
if (!selectTps) return;
if (!selectTps) return;
selectTps.addEventListener('change', function() {
if (!this.value) return;
selectTps.addEventListener('change', function () {
if (!this.value) return;
const option = this.options[this.selectedIndex];
const option = this.options[this.selectedIndex];
inputLokasi.value = this.value;
inputLokasi.value = this.value;
fotoTps.src = option.dataset.foto;
fotoWrapper.classList.remove('d-none');
fotoTps.src = option.dataset.foto;
fotoWrapper.classList.remove('d-none');
infoTps.innerHTML = `
infoTps.innerHTML = `
<p class="mb-1"><strong>${option.dataset.nama}</strong></p>
<p class="mb-2 text-muted">${option.dataset.alamat}</p>
<button type="button" class="btn btn-sm btn-outline-secondary" id="ganti-tps">
@ -146,16 +180,16 @@
</button>
`;
document.getElementById('ganti-tps').onclick = () => {
inputLokasi.value = '';
fotoWrapper.classList.add('d-none');
infoTps.innerHTML =
'<p class="mb-2 text-muted">Silakan pilih TPS yang akan diadukan</p>';
infoTps.appendChild(selectTps);
selectTps.value = '';
};
});
});
</script>
document.getElementById('ganti-tps').onclick = () => {
inputLokasi.value = '';
fotoWrapper.classList.add('d-none');
infoTps.innerHTML =
'<p class="mb-2 text-muted">Silakan pilih TPS yang akan diadukan</p>';
infoTps.appendChild(selectTps);
selectTps.value = '';
};
});
});
</script>
@endsection