Compare commits
6 Commits
6cf4432642
...
staging
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f402c0831a | ||
|
|
ceca0aa5e8 | ||
|
|
43361f81e7 | ||
|
|
58b53a0284 | ||
|
|
bb0da7626b | ||
|
|
8a6ab059f5 |
@@ -20,6 +20,7 @@
|
||||
use Modules\Usermanagement\Models\User;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Modules\Lpj\Services\ImageResizeService;
|
||||
|
||||
/**
|
||||
* Format tanggal ke dalam format Bahasa Indonesia
|
||||
@@ -775,3 +776,20 @@
|
||||
Log::error('Tidak dapat memparsing timestamp dengan format apapun: "' . $timestamp . '"');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!function_exists('resize_image')) {
|
||||
/**
|
||||
* Merubah ukuran gambar secara on-the-fly dan mengembalikan path-nya.
|
||||
*
|
||||
* @param string $path Path asli gambar.
|
||||
* @param int|null $width Lebar yang diinginkan.
|
||||
* @param int|null $height Tinggi yang diinginkan (opsional, akan menjaga rasio aspek jika null).
|
||||
* @param int $quality Kualitas gambar (1-100).
|
||||
* @return string Path gambar yang sudah di-resize.
|
||||
*/
|
||||
function resize_image(string $path, ?int $width, ?int $height = null, int $quality = 80): string
|
||||
{
|
||||
|
||||
return app(ImageResizeService::class)->resize($path, $width, $height, $quality);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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', [
|
||||
|
||||
@@ -21,7 +21,7 @@ class CategoryDaftarPustaka extends Model
|
||||
];
|
||||
|
||||
public function daftarPustaka(){
|
||||
return $this->hasMany(DaftarPustaka::class);
|
||||
return $this->hasMany(DaftarPustaka::class, 'category_id');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
73
app/Services/ImageResizeService.php
Normal file
73
app/Services/ImageResizeService.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Lpj\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Intervention\Image\Laravel\Facades\Image;
|
||||
|
||||
class ImageResizeService
|
||||
{
|
||||
/**
|
||||
* Resize an image and store it.
|
||||
*
|
||||
* @param string $originalPath
|
||||
* @param int $width
|
||||
* @param int|null $height
|
||||
* @param int $quality
|
||||
* @return string
|
||||
*/
|
||||
public function resize(string $originalPath, ?int $width, ?int $height = null, int $quality = 80): string
|
||||
{
|
||||
if (empty($originalPath) || !Storage::disk('public')->exists($originalPath)) {
|
||||
Log::warning("Image Service: Original path is empty or does not exist: {$originalPath}");
|
||||
return '';
|
||||
}
|
||||
|
||||
$height = null;
|
||||
|
||||
$pathinfo = pathinfo($originalPath);
|
||||
|
||||
// Kembali menggunakan direktori 'resized' dan menyertakan dimensi di nama file
|
||||
$resizedPath = "{$pathinfo['dirname']}/resized/{$pathinfo['filename']}_{$width}x{$height}_{$quality}.{$pathinfo['extension']}";
|
||||
|
||||
if (Storage::disk('public')->exists($resizedPath)) {
|
||||
return $resizedPath;
|
||||
}
|
||||
|
||||
try {
|
||||
$originalFullPath = Storage::disk('public')->path($originalPath);
|
||||
$newFullPath = Storage::disk('public')->path($resizedPath);
|
||||
|
||||
$image = Image::read($originalFullPath);
|
||||
|
||||
// Resize dengan menjaga aspek rasio jika height null
|
||||
if ($width && $height) {
|
||||
// Paksa resize ke dimensi yang ditentukan, abaikan aspek rasio
|
||||
$image->resize($width, $height);
|
||||
} elseif ($width && !$height) {
|
||||
// Resize hanya berdasarkan width, tinggi menyesuaikan aspek rasio
|
||||
$image->scale(width: $width);
|
||||
} elseif (!$width && $height) {
|
||||
// Resize hanya berdasarkan height, lebar menyesuaikan aspek rasio
|
||||
$image->scale(height: $height);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!Storage::disk('public')->exists(dirname($resizedPath))) {
|
||||
Storage::disk('public')->makeDirectory(dirname($resizedPath));
|
||||
}
|
||||
|
||||
// Simpan dengan kualitas yang ditentukan
|
||||
$image->save($newFullPath, $quality);
|
||||
|
||||
Log::info("Image Service: Successfully resized {$originalPath} to {$resizedPath} with quality {$quality}%.");
|
||||
|
||||
return $resizedPath;
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Image Service: Resize failed for {$originalPath}. Error: " . $e->getMessage());
|
||||
return $originalPath; // Fallback ke gambar asli jika gagal
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,6 +347,7 @@ class PreviewLaporanService
|
||||
$pdf->setPaper('A4', 'portrait');
|
||||
$pdf->set_option('isHtml5ParserEnabled', true);
|
||||
$pdf->set_option('isPhpEnabled', true);
|
||||
$pdf->set_option('dpi', '96');
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"permission": "",
|
||||
"roles": [
|
||||
"administrator",
|
||||
"admin",
|
||||
"pemohon-ao",
|
||||
"pemohon-eo",
|
||||
"DD Appraisal",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -154,10 +154,12 @@
|
||||
? $fallbackPath
|
||||
: $originalPath;
|
||||
|
||||
$resizedPath = resize_image($pathToUse, 800, 400, 25);
|
||||
|
||||
$filePath =
|
||||
$statusLpj == 1
|
||||
? storage_path('app/public/' . $pathToUse)
|
||||
: asset('storage/' . $pathToUse);
|
||||
? storage_path('app/public/' . $resizedPath)
|
||||
: asset('storage/' . $resizedPath);
|
||||
|
||||
$extension = strtolower(pathinfo($pathToUse, PATHINFO_EXTENSION));
|
||||
$isImage = in_array($extension, [
|
||||
@@ -248,12 +250,16 @@
|
||||
style="align-content: center; text-align: center; margin-bottom: 20px">
|
||||
@foreach ($chunkedPhotos as $item)
|
||||
@php
|
||||
$originalPath = $item['path'];
|
||||
$resizedPath = resize_image($originalPath, 800, 400, 25);
|
||||
|
||||
|
||||
$filePath =
|
||||
$statusLpj == 1
|
||||
? storage_path('app/public/' . $item['path'])
|
||||
: asset('storage/' . $item['path']);
|
||||
? storage_path('app/public/' . $resizedPath)
|
||||
: asset('storage/' . $resizedPath);
|
||||
|
||||
$extension = strtolower(pathinfo($item['path'], PATHINFO_EXTENSION));
|
||||
$extension = strtolower(pathinfo($originalPath, PATHINFO_EXTENSION));
|
||||
$isImage = in_array($extension, [
|
||||
'jpg',
|
||||
'jpeg',
|
||||
|
||||
Reference in New Issue
Block a user