423 lines
21 KiB
PHP
423 lines
21 KiB
PHP
@extends('layouts.admin.app')
|
|
|
|
@section('content')
|
|
<div class="py-12">
|
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
|
@if(session('success'))
|
|
<div id="alert-success" class="flex items-center p-4 mb-4 text-green-800 rounded-lg bg-green-50" role="alert">
|
|
<i class="fas fa-check-circle flex-shrink-0 w-4 h-4"></i>
|
|
<div class="ms-3 text-sm font-medium">
|
|
{{ session('success') }}
|
|
</div>
|
|
<button type="button" class="ms-auto -mx-1.5 -my-1.5 bg-green-50 text-green-500 rounded-lg focus:ring-2 focus:ring-green-400 p-1.5 hover:bg-green-200 inline-flex items-center justify-center h-8 w-8" data-dismiss-target="#alert-success" aria-label="Close">
|
|
<span class="sr-only">Close</span>
|
|
<i class="fas fa-times w-3 h-3"></i>
|
|
</button>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Add Category Button -->
|
|
<div class="mb-4">
|
|
<button type="button" id="addCategoryBtn" class="text-white bg-red-600 hover:bg-red-700 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 border border-red-700 shadow-sm">
|
|
<i class="fas fa-plus-circle mr-2"></i>Add New Gallery Category
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Gallery Categories Section -->
|
|
<div class="bg-white dark:bg-gray-800 shadow-md rounded-lg mb-8">
|
|
<div class="p-6">
|
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-6">
|
|
Gallery Categories
|
|
</h3>
|
|
|
|
<!-- Categories Grid -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
@foreach($categories as $category)
|
|
<div class="bg-white dark:bg-gray-700 rounded-lg shadow-md">
|
|
<div class="relative">
|
|
<img src="{{ asset('storage/' . $category->thumbnail) }}" alt="{{ $category->name }}" class="w-full h-48 object-cover rounded-t-lg">
|
|
<div class="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/70 to-transparent">
|
|
<h4 class="text-lg font-semibold text-white">{{ $category->name }}</h4>
|
|
</div>
|
|
</div>
|
|
<div class="p-4">
|
|
<p class="text-sm text-gray-600 dark:text-gray-300 mb-4">{{ $category->description }}</p>
|
|
<div class="flex items-center justify-between">
|
|
<button onclick="editCategory({{ $category->id }})" class="text-blue-600 hover:text-blue-800 flex items-center bg-blue-50 px-3 py-1 rounded-md border border-blue-200 hover:bg-blue-100">
|
|
<i class="fas fa-edit mr-1"></i>
|
|
<span>Edit</span>
|
|
</button>
|
|
<button onclick="deleteCategory({{ $category->id }})" class="text-red-600 hover:text-red-800 flex items-center bg-red-50 px-3 py-1 rounded-md border border-red-200 hover:bg-red-100">
|
|
<i class="fas fa-trash-alt mr-1"></i>
|
|
<span>Delete</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Category Images Sections -->
|
|
@foreach($categories as $category)
|
|
<div class="bg-white dark:bg-gray-800 shadow-md rounded-lg mb-8">
|
|
<div class="p-6">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<div>
|
|
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">{{ $category->name }}</h3>
|
|
<p class="text-sm text-gray-500 mt-1">{{ $category->images->count() }} images</p>
|
|
</div>
|
|
<button onclick="addImage({{ $category->id }})" class="text-white bg-red-600 hover:bg-red-700 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-4 py-2 border border-red-700 shadow-sm">
|
|
<i class="fas fa-plus-circle mr-2"></i>Add Image
|
|
</button>
|
|
</div>
|
|
|
|
@if($category->images->isEmpty())
|
|
<div class="text-center py-8 text-gray-500">
|
|
<i class="fas fa-images text-4xl mb-2"></i>
|
|
<p>No images yet in this category.</p>
|
|
<p class="text-sm mt-2">Click the "Add Image" button to add your first image.</p>
|
|
</div>
|
|
@else
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
@foreach($category->images as $image)
|
|
<div class="relative group">
|
|
<img src="{{ asset('storage/' . $image->image) }}" alt="{{ $image->caption }}" class="w-full h-48 object-cover rounded-lg">
|
|
<div class="absolute inset-0 bg-black bg-opacity-50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-lg flex items-center justify-center space-x-4">
|
|
<button onclick="editImage({{ $image->id }})" class="text-white hover:text-blue-400 flex items-center bg-blue-600 hover:bg-blue-700 px-3 py-1 rounded-md">
|
|
<i class="fas fa-edit mr-1"></i>
|
|
<span>Edit</span>
|
|
</button>
|
|
<button onclick="deleteImage({{ $image->id }})" class="text-white hover:text-red-400 flex items-center bg-red-600 hover:bg-red-700 px-3 py-1 rounded-md">
|
|
<i class="fas fa-trash-alt mr-1"></i>
|
|
<span>Delete</span>
|
|
</button>
|
|
</div>
|
|
@if($image->caption)
|
|
<div class="absolute bottom-0 left-0 right-0 p-2 bg-black bg-opacity-50 text-white text-sm">
|
|
{{ $image->caption }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
@push('scripts')
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
<script>
|
|
// Auto-hide alerts after 3 seconds
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
setTimeout(function() {
|
|
const alerts = document.querySelectorAll('[role="alert"]');
|
|
alerts.forEach(alert => {
|
|
alert.style.display = 'none';
|
|
});
|
|
}, 3000);
|
|
});
|
|
|
|
// Add New Category
|
|
document.getElementById('addCategoryBtn').addEventListener('click', function() {
|
|
Swal.fire({
|
|
title: 'Add New Gallery Category',
|
|
html: `
|
|
<form id="addCategoryForm" class="space-y-4">
|
|
<div class="text-left">
|
|
<label class="block text-sm font-medium text-gray-700">Category Name</label>
|
|
<input type="text" id="name" name="name" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-maroon focus:ring-maroon">
|
|
</div>
|
|
<div class="text-left">
|
|
<label class="block text-sm font-medium text-gray-700">Description</label>
|
|
<textarea id="description" name="description" required rows="3" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-maroon focus:ring-maroon"></textarea>
|
|
</div>
|
|
<div class="text-left">
|
|
<label class="block text-sm font-medium text-gray-700">Thumbnail</label>
|
|
<input type="file" id="thumbnail" name="thumbnail" accept="image/*" required class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-maroon file:text-white hover:file:bg-red-800">
|
|
<p class="mt-1 text-sm text-gray-500">Recommended size: 800x600 pixels</p>
|
|
</div>
|
|
</form>
|
|
`,
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Add Category',
|
|
confirmButtonColor: '#800000',
|
|
preConfirm: () => {
|
|
const formData = new FormData(document.getElementById('addCategoryForm'));
|
|
return fetch('{{ route("admin.gallery.store-category") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
},
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.catch(error => {
|
|
Swal.showValidationMessage(`Request failed: ${error}`)
|
|
})
|
|
}
|
|
}).then((result) => {
|
|
if (result.isConfirmed && result.value.success) {
|
|
Swal.fire('Success!', result.value.message, 'success')
|
|
.then(() => {
|
|
location.reload();
|
|
});
|
|
} else if (result.value && !result.value.success) {
|
|
Swal.fire('Error!', result.value.message, 'error');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Edit Category
|
|
function editCategory(id) {
|
|
fetch(`/admin/gallery/category/${id}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const category = data.data;
|
|
Swal.fire({
|
|
title: 'Edit Gallery Category',
|
|
html: `
|
|
<form id="editCategoryForm" class="space-y-4">
|
|
<input type="hidden" name="_method" value="PUT">
|
|
<div class="text-left">
|
|
<label class="block text-sm font-medium text-gray-700">Category Name</label>
|
|
<input type="text" id="edit_name" name="name" value="${category.name}" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-maroon focus:ring-maroon">
|
|
</div>
|
|
<div class="text-left">
|
|
<label class="block text-sm font-medium text-gray-700">Description</label>
|
|
<textarea id="edit_description" name="description" required rows="3" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-maroon focus:ring-maroon">${category.description}</textarea>
|
|
</div>
|
|
<div class="text-left">
|
|
<label class="block text-sm font-medium text-gray-700">Current Thumbnail</label>
|
|
<img src="/storage/${category.thumbnail}" class="w-32 h-32 object-cover rounded-lg mb-2">
|
|
<label class="block text-sm font-medium text-gray-700">New Thumbnail (Optional)</label>
|
|
<input type="file" id="edit_thumbnail" name="thumbnail" accept="image/*" class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-maroon file:text-white hover:file:bg-red-800">
|
|
<p class="mt-1 text-sm text-gray-500">Leave empty to keep current thumbnail</p>
|
|
</div>
|
|
</form>
|
|
`,
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Save Changes',
|
|
confirmButtonColor: '#800000',
|
|
preConfirm: () => {
|
|
const formData = new FormData(document.getElementById('editCategoryForm'));
|
|
return fetch(`/admin/gallery/category/${id}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
},
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.catch(error => {
|
|
Swal.showValidationMessage(`Request failed: ${error}`)
|
|
})
|
|
}
|
|
}).then((result) => {
|
|
if (result.isConfirmed && result.value.success) {
|
|
Swal.fire('Success!', result.value.message, 'success')
|
|
.then(() => {
|
|
location.reload();
|
|
});
|
|
} else if (result.value && !result.value.success) {
|
|
Swal.fire('Error!', result.value.message, 'error');
|
|
}
|
|
});
|
|
} else {
|
|
Swal.fire('Error!', 'Failed to fetch category data', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
Swal.fire('Error!', 'Failed to fetch category data', 'error');
|
|
});
|
|
}
|
|
|
|
// Delete Category
|
|
function deleteCategory(id) {
|
|
Swal.fire({
|
|
title: 'Delete Category?',
|
|
text: "This will delete all images in this category. This action cannot be undone!",
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#800000',
|
|
cancelButtonColor: '#3085d6',
|
|
confirmButtonText: 'Yes, delete it!',
|
|
cancelButtonText: 'Cancel'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
fetch(`/admin/gallery/category/${id}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
Swal.fire('Deleted!', data.message, 'success')
|
|
.then(() => {
|
|
location.reload();
|
|
});
|
|
} else {
|
|
Swal.fire('Error!', data.message, 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
Swal.fire('Error!', 'Something went wrong.', 'error');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add Image
|
|
function addImage(categoryId) {
|
|
Swal.fire({
|
|
title: 'Add New Image',
|
|
html: `
|
|
<form id="addImageForm" class="space-y-4">
|
|
<div class="text-left">
|
|
<label class="block text-sm font-medium text-gray-700">Image</label>
|
|
<input type="file" id="image" name="image" accept="image/*" required class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-maroon file:text-white hover:file:bg-red-800">
|
|
<p class="mt-1 text-sm text-gray-500">Recommended size: 1200x800 pixels</p>
|
|
</div>
|
|
<div class="text-left">
|
|
<label class="block text-sm font-medium text-gray-700">Caption (Optional)</label>
|
|
<input type="text" id="caption" name="caption" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-maroon focus:ring-maroon">
|
|
<p class="mt-1 text-sm text-gray-500">A short description of the image</p>
|
|
</div>
|
|
</form>
|
|
`,
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Add Image',
|
|
confirmButtonColor: '#800000',
|
|
preConfirm: () => {
|
|
const formData = new FormData(document.getElementById('addImageForm'));
|
|
return fetch(`/admin/gallery/category/${categoryId}/image`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
},
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.catch(error => {
|
|
Swal.showValidationMessage(`Request failed: ${error}`)
|
|
})
|
|
}
|
|
}).then((result) => {
|
|
if (result.isConfirmed && result.value.success) {
|
|
Swal.fire('Success!', result.value.message, 'success')
|
|
.then(() => {
|
|
location.reload();
|
|
});
|
|
} else if (result.value && !result.value.success) {
|
|
Swal.fire('Error!', result.value.message, 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Edit Image
|
|
function editImage(id) {
|
|
fetch(`/admin/gallery/image/${id}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const image = data.data;
|
|
Swal.fire({
|
|
title: 'Edit Image',
|
|
html: `
|
|
<form id="editImageForm" class="space-y-4">
|
|
<input type="hidden" name="_method" value="PUT">
|
|
<div class="text-left">
|
|
<label class="block text-sm font-medium text-gray-700">Current Image</label>
|
|
<img src="/storage/${image.image}" class="w-full h-48 object-cover rounded-lg mb-2">
|
|
<label class="block text-sm font-medium text-gray-700">New Image (Optional)</label>
|
|
<input type="file" id="edit_image" name="image" accept="image/*" class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-maroon file:text-white hover:file:bg-red-800">
|
|
<p class="mt-1 text-sm text-gray-500">Leave empty to keep current image</p>
|
|
</div>
|
|
<div class="text-left">
|
|
<label class="block text-sm font-medium text-gray-700">Caption</label>
|
|
<input type="text" id="edit_caption" name="caption" value="${image.caption || ''}" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-maroon focus:ring-maroon">
|
|
</div>
|
|
</form>
|
|
`,
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Save Changes',
|
|
confirmButtonColor: '#800000',
|
|
preConfirm: () => {
|
|
const formData = new FormData(document.getElementById('editImageForm'));
|
|
return fetch(`/admin/gallery/image/${id}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
},
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.catch(error => {
|
|
Swal.showValidationMessage(`Request failed: ${error}`)
|
|
})
|
|
}
|
|
}).then((result) => {
|
|
if (result.isConfirmed && result.value.success) {
|
|
Swal.fire('Success!', result.value.message, 'success')
|
|
.then(() => {
|
|
location.reload();
|
|
});
|
|
} else if (result.value && !result.value.success) {
|
|
Swal.fire('Error!', result.value.message, 'error');
|
|
}
|
|
});
|
|
} else {
|
|
Swal.fire('Error!', 'Failed to fetch image data', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
Swal.fire('Error!', 'Failed to fetch image data', 'error');
|
|
});
|
|
}
|
|
|
|
// Delete Image
|
|
function deleteImage(id) {
|
|
Swal.fire({
|
|
title: 'Delete Image?',
|
|
text: "You won't be able to revert this!",
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#800000',
|
|
cancelButtonColor: '#3085d6',
|
|
confirmButtonText: 'Yes, delete it!',
|
|
cancelButtonText: 'Cancel'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
fetch(`/admin/gallery/image/${id}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
Swal.fire('Deleted!', data.message, 'success')
|
|
.then(() => {
|
|
location.reload();
|
|
});
|
|
} else {
|
|
Swal.fire('Error!', data.message, 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
Swal.fire('Error!', 'Something went wrong.', 'error');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
@endpush
|
|
@endsection
|