Compare commits

...

3 Commits

Author SHA1 Message Date
Daeng Deni Mardaeni
58b53a0284 feat(daftar-pustaka): 💄 Tingkatkan Tampilan Tombol dan Kartu
Perubahan ini mencakup beberapa penyesuaian pada tata letak dan gaya untuk meningkatkan pengalaman pengguna di halaman daftar pustaka.

- **Tombol Lebar Penuh**: Tombol "Hapus" dan "Edit" sekarang memiliki lebar penuh (`w-full`) untuk konsistensi dan kemudahan penggunaan.
- **Tata Letak Tombol**: Tombol-tombol diatur dalam tata letak kolom (`flex-col`) dengan jarak (`gap-2`) untuk tampilan yang lebih rapi.
- **Posisi Tombol**: Tombol-tombol sekarang selalu berada di bagian bawah kartu, terlepas dari panjang konten, dengan menggunakan `mt-auto`.
- **Struktur Kartu**: Struktur kartu diubah menjadi `flex flex-col` untuk memungkinkan penataan posisi tombol yang lebih baik.
2025-12-18 17:48:57 +07:00
Daeng Deni Mardaeni
bb0da7626b fix(model): 🐛 Perbaiki Foreign Key pada Relasi Daftar Pustaka
Memperbaiki relasi `hasMany` pada model `CategoryDaftarPustaka` untuk mereferensikan `DaftarPustaka` dengan benar.

- **Perbaikan Relasi**: Mengubah relasi `daftarPustaka` untuk secara eksplisit mendefinisikan foreign key sebagai `category_id`.
- **Pencegahan Error**: Perubahan ini mengatasi error `SQLSTATE[42S22]: Column not found` yang terjadi karena Eloquent mencoba mencari kolom `category_daftar_pustaka_id` yang tidak ada.
- **Konsistensi Skema**: Memastikan bahwa relasi model sekarang sesuai dengan skema database yang sebenarnya, di mana tabel `daftar_pustaka` menggunakan `category_id` untuk relasi ke kategori.
2025-12-18 17:46:24 +07:00
Daeng Deni Mardaeni
8a6ab059f5 feat(controller): Tambahkan Error Handling pada Pemuatan Kategori
Menambahkan mekanisme penanganan error yang tangguh pada metode `index` di `DaftarPustakaController` untuk memastikan halaman tetap berfungsi meskipun terjadi kegagalan pada query database.

- **Implementasi Try-Catch**: Membungkus query `CategoryDaftarPustaka::withCount('daftarPustaka')` di dalam blok `try-catch`.
- **Pencatatan Error**: Jika terjadi `Exception` (misalnya, tabel relasi belum ada atau terjadi error SQL), pesan error akan dicatat menggunakan `Log::warning()` untuk kemudahan debugging.
- **Mekanisme Fallback**: Apabila query `withCount` gagal, controller akan menjalankan query alternatif `CategoryDaftarPustaka::get()` yang mengambil data kategori tanpa jumlah relasi. Ini memastikan bahwa halaman daftar pustaka tetap dapat ditampilkan kepada pengguna.
- **Penambahan Dependensi**: Menambahkan `use Exception;` dan `use Illuminate\Support\Facades\Log;` untuk mendukung fungsionalitas ini.
2025-12-18 17:46:13 +07:00
4 changed files with 188 additions and 54 deletions

View File

@@ -7,6 +7,8 @@ use Illuminate\Http\Request;
use Modules\Lpj\Models\CategoryDaftarPustaka;
use Modules\Lpj\Services\DaftarPustakaService;
use Modules\Lpj\Http\Requests\DaftarPustakaRequest;
use Exception;
use Illuminate\Support\Facades\Log;
class DaftarPustakaController extends Controller
{
@@ -22,7 +24,15 @@ class DaftarPustakaController extends Controller
*/
public function index(Request $request)
{
$categories = CategoryDaftarPustaka::all();
// Get categories with count of daftar pustaka
try {
$categories = CategoryDaftarPustaka::withCount('daftarPustaka')->get();
} catch (\Exception $e) {
// Handle jika tabel belum ada atau error lainnya
Log::warning('Error loading categories with count: ' . $e->getMessage());
$categories = CategoryDaftarPustaka::get(); // Fallback tanpa count
}
$daftar_pustaka = $this->daftarPustaka->getAllDaftarPustaka($request);
return view('lpj::daftar-pustaka.index', [

View File

@@ -21,7 +21,7 @@ class CategoryDaftarPustaka extends Model
];
public function daftarPustaka(){
return $this->hasMany(DaftarPustaka::class);
return $this->hasMany(DaftarPustaka::class, 'category_id');
}
}

View File

@@ -22,6 +22,7 @@
"permission": "",
"roles": [
"administrator",
"admin",
"pemohon-ao",
"pemohon-eo",
"DD Appraisal",

View File

@@ -6,6 +6,78 @@
@section('content')
<style>
/* Styling untuk sidebar kategori */
.category-sidebar {
background: #f8fafc;
border-radius: 8px;
padding: 1.5rem;
height: fit-content;
}
.category-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 1rem;
margin: 0.25rem 0;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid transparent;
}
.category-item:hover {
background: #e2e8f0;
border-color: #cbd5e1;
}
.category-item.active {
background: #3b82f6;
color: white;
border-color: #2563eb;
}
.category-count {
background: #e2e8f0;
color: #64748b;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
}
.category-item.active .category-count {
background: rgba(255, 255, 255, 0.2);
color: white;
}
.sidebar-title {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 1rem;
color: #1e293b;
}
/* Loading state */
body.loading {
cursor: wait;
}
body.loading * {
pointer-events: none;
}
@media (max-width: 768px) {
.category-sidebar {
margin-bottom: 1.5rem;
}
.category-item {
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
}
}
@media (max-width: 768px) {
#previewContent {
height: 400px;
@@ -15,7 +87,34 @@
</style>
<div class="flex flex-col gap-7 items-stretch">
<div class="flex flex-col gap-7 lg:flex-row">
<!-- Sidebar Kategori -->
<div class="w-full lg:w-80">
<div class="category-sidebar">
<h3 class="sidebar-title">
<i class="mr-2 ki-filled ki-category"></i>
Kategori
</h3>
<!-- Semua Kategori -->
<div class="category-item {{ !request('category') ? 'active' : '' }}" onclick="filterByCategory('', 'Semua Kategori')">
<span>Semua Kategori</span>
<span class="category-count">{{ $total ?? 0 }}</span>
</div>
<!-- Daftar Kategori -->
@foreach ($categories as $category)
<div class="category-item {{ request('category') == $category->id ? 'active' : '' }}" onclick="filterByCategory({{ $category->id }}, '{{ $category->name }}')">
<span>{{ $category->name }}</span>
<span class="category-count">{{ $category->daftarPustaka_count ?? 0 }}</span>
</div>
@endforeach
</div>
</div>
<!-- Konten Utama -->
<div class="flex-1">
<div class="flex flex-col gap-7 items-stretch">
<div class="flex gap-3 items-center w-full">
<div class="w-full input">
<i class="ki-filled ki-magnifier">
@@ -43,12 +142,6 @@
{{ $total }} items.
</h3>
<div class="flex gap-2.5">
<select id="category_id" name="category_id" class="select tomselect w-[300px]" multiple>
<option value="" selected disabled>Filter by Category</option>
@foreach ($categories as $item)
<option value="{{ $item->id }}">{{ $item->name }}</option>
@endforeach
</select>
<div class="flex toggle-group" data-kt-tabs="true" data-kt-tabs-initialized="true">
<a class="btn btn-icon active selected" data-kt-tab-toggle="#daftar_pustaka_grid" onclick="showGrid()"
href="javascript:void(0)">
@@ -74,8 +167,8 @@
<div class="grid grid-cols-1 gap-4 md:grid-cols-3 lg:grid-cols-4" id="daftar_pustaka_grid">
@if (isset($daftar_pustaka))
@foreach ($daftar_pustaka as $item)
<div class="border shadow-none card">
<a class="show-pustaka h-[300px] bg-gray-200 w-full block" href="{{ route('daftar-pustaka.show', $item->id) }}"
<div class="flex flex-col border shadow-none card">
<a class="show-pustaka h-[150px] bg-gray-200 w-full block" href="{{ route('daftar-pustaka.show', $item->id) }}"
data-url="{{ $item->attachment }}">
<div class="flex overflow-hidden justify-center items-center p-4 w-full h-full">
<div class="flex justify-center items-center text-red-500 rounded">
@@ -84,39 +177,33 @@
</div>
</a>
<div class="card-body">
<a href="{{ route('daftar-pustaka.show', $item->id) }}">
<h3 class="font-medium text-gray-900 cursor-pointer text-md hover:text-primary">
{{ $item->judul }}</h3>
<p class="text-gray-700 text-2sm">
{{-- batasi panjang deskripsi 50 --}}
{{ substr($item->deskripsi, 0, 50) }}
</p>
</a>
<div class="flex gap-2.5 justify-between items-center mt-2">
<p class="text-xs text-gray-700 rounded-full badge badge-xs badge-outline badge-success">
# {{ $item->category->name }}</p>
@auth
@if (auth()->user()->hasRole(['administrator', 'admin']))
<div>
<a class="btn btn-xs btn-danger" onclick="deleteData({{ $item->id }})">
<i class="ki-filled ki-trash">
</i>
Hapus
</a>
<a class="btn btn-xs btn-info"
href="{{ route('daftar-pustaka.edit', $item->id) }}">
<i class="ki-filled ki-pencil">
</i>
Edit
</a>
</div>
@endif
@endauth
<div class="flex flex-col flex-grow p-4 card-body">
<div>
<p class="text-xs text-gray-700 badge badge-xs badge-outline badge-success">
# {{ $item->category->name }}</p>
</div>
<div class="flex-grow mt-2">
<a href="{{ route('daftar-pustaka.show', $item->id) }}">
<h3 class="text-sm font-bold text-gray-900 hover:text-primary">
{{ $item->judul }}</h3>
<p class="text-xs text-gray-700">
{{-- batasi panjang deskripsi 50 --}}
{{ substr($item->deskripsi, 0, 50) }}
</p>
</a>
</div>
@if (auth()->user()->hasRole(['administrator', 'admin']))
<div class="flex gap-2 items-center mt-5">
<a class="w-full text-center btn btn-xs btn-danger" onclick="deleteData({{ $item->id }})">
<i class="ki-filled ki-trash"></i>
Hapus
</a>
<a class="w-full text-center btn btn-xs btn-primary" href="{{ route('daftar-pustaka.edit', $item->id) }}">
<i class="ki-filled ki-pencil"></i>
Edit
</a>
</div>
@endif
</div>
</div>
@endforeach
@@ -143,9 +230,11 @@
</a>
</div>
<a href="{{ route('daftar-pustaka.show', $item->id) }}">
<div class="flex flex-col gap-2 cursor-pointer">
<div class="flex items-center mt-1">
<a class="text-sm font-medium hover:text-primary text-mono leading-5.5">
<div class="flex flex-col cursor-pointer">
<p class="text-xs text-gray-700 badge badge-xs badge-outline badge-success">
# {{ $item->category->name }}</p>
<div class="flex items-center">
<a class="text-sm font-bold hover:text-primary text-mono leading-5.5">
{{ $item->judul }}
</div>
</a>
@@ -161,8 +250,6 @@
</a>
</div>
<div class="flex gap-1.5 items-center">
<p class="text-xs text-gray-700 rounded-full badge badge-sm badge-outline badge-success">
# {{ $item->category->name }}</p>
@auth
@if (auth()->user()->hasRole(['administrator', 'admin']))
<div>
@@ -277,14 +364,18 @@
function filterSearch() {
const search = document.getElementById('search')?.value || '';
const select = document.getElementById('category_id');
const selectedCategories = Array.from(select.selectedOptions).map(option => option.value);
const params = new URLSearchParams(window.location.search);
const categoryParam = selectedCategories.join(',');
if (search) {
params.set('search', search);
} else {
params.delete('search');
}
const url = "{{ route('daftar-pustaka.index') }}?search=" + encodeURIComponent(search) + "&category=" +
encodeURIComponent(categoryParam);
window.location.href = url;
// Reset to first page when searching
params.set('page', '1');
window.location.href = `{{ route('daftar-pustaka.index') }}?${params.toString()}`;
}
@@ -292,5 +383,37 @@
const url = "{{ route('daftar-pustaka.index') }}";
window.location.href = url;
}
function filterByCategory(categoryId, categoryName) {
// Add loading state
document.body.classList.add('loading');
// Update active state
document.querySelectorAll('.category-item').forEach(item => {
item.classList.remove('active');
});
event.currentTarget.classList.add('active');
// Get current search parameter
const search = document.getElementById('search')?.value || '';
// Build URL with category parameter
const params = new URLSearchParams(window.location.search);
if (search) {
params.set('search', search);
}
if (categoryId) {
params.set('category', categoryId);
} else {
params.delete('category');
}
// Reset to first page when filtering
params.set('page', '1');
window.location.href = `{{ route('daftar-pustaka.index') }}?${params.toString()}`;
}
</script>
@endpush