classify admin
This commit is contained in:
627
resources/views/custom-fields/bulk-update.blade.php
Normal file
627
resources/views/custom-fields/bulk-update.blade.php
Normal file
@@ -0,0 +1,627 @@
|
||||
@extends('layouts.main')
|
||||
|
||||
@section('title')
|
||||
{{__("Bulk Update Custom Fields")}}
|
||||
@endsection
|
||||
|
||||
@section('page-title')
|
||||
<div class="page-title">
|
||||
<div class="row d-flex align-items-center">
|
||||
<div class="col-12 col-md-6">
|
||||
<h4 class="mb-0">@yield('title')</h4>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 text-end">
|
||||
<a href="{{ route('custom-fields.index') }}" class="btn btn-secondary mb-0">
|
||||
<i class="fas fa-arrow-left"></i> {{__("Back")}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@section('content')
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-2">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h5 class="mb-0 fw-bold">
|
||||
<i class="fas fa-info-circle me-2"></i> {{__("Instructions & Reference Guide")}}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<div class="row g-4">
|
||||
{{-- Section 1: Important Instructions --}}
|
||||
<div class="col-md-4">
|
||||
<div class="h-100 p-3 border rounded">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-warning text-dark rounded-circle d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
</div>
|
||||
<h6 class="mb-0 ms-3 fw-bold">{{__("Important Instructions")}}</h6>
|
||||
</div>
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-check-circle text-success me-2 mt-1"></i>
|
||||
<span>{{__("Download current custom fields to get the existing data")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-check-circle text-success me-2 mt-1"></i>
|
||||
<span>{{__("Do NOT modify the ID column - it's required for updates")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-check-circle text-success me-2 mt-1"></i>
|
||||
<span>{{__("Edit the fields you want to update in the downloaded file")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-check-circle text-success me-2 mt-1"></i>
|
||||
<span>{{__("For image paths, use the gallery section to upload new images")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-check-circle text-success me-2 mt-1"></i>
|
||||
<span>{{__("Category IDs should be comma-separated if multiple categories")}}</span>
|
||||
</li>
|
||||
<li class="mb-0 d-flex align-items-start">
|
||||
<i class="fas fa-check-circle text-success me-2 mt-1"></i>
|
||||
<span>{{__("Values for radio, dropdown, and checkbox types should be pipe-separated (|)")}}</span>
|
||||
</li>
|
||||
<li class="mb-0 d-flex align-items-start mt-3">
|
||||
<i class="fas fa-info-circle text-info me-2 mt-1"></i>
|
||||
<span><strong>{{__("Note:")}}</strong> {{__("You can open the CSV file in Excel, edit it, and save it. Excel will maintain the CSV format.")}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="h-100 p-3 border rounded">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-success text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
|
||||
<i class="fas fa-book"></i>
|
||||
</div>
|
||||
<h6 class="mb-0 ms-3 fw-bold">{{__("Quick Reference")}}</h6>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<strong class="d-block mb-2">{{__("Field Types:")}}</strong>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span class="badge bg-primary">Number</span>
|
||||
<span class="badge bg-secondary">Textbox</span>
|
||||
<span class="badge bg-info text-dark">Fileinput</span>
|
||||
<span class="badge bg-warning text-dark">Radio</span>
|
||||
<span class="badge bg-success">Dropdown</span>
|
||||
<span class="badge bg-danger">Checkbox</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong class="d-block mb-2">{{__("Data Format:")}}</strong>
|
||||
<ul class="list-unstyled mb-0 small">
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-dot-circle text-primary me-2"></i>
|
||||
<strong>{{__("Required:")}}</strong> 0 = {{__("Optional")}}, 1 = {{__("Required")}}
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-dot-circle text-primary me-2"></i>
|
||||
<strong>{{__("Status:")}}</strong> 0 = {{__("Inactive")}}, 1 = {{__("Active")}}
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-dot-circle text-primary me-2"></i>
|
||||
<strong>{{__("Values:")}}</strong> {{__("Use pipe (|) to separate options")}}
|
||||
</li>
|
||||
<li class="mb-0">
|
||||
<i class="fas fa-dot-circle text-primary me-2"></i>
|
||||
<strong>{{__("Categories:")}}</strong> {{__("Use comma (,) to separate IDs")}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{-- Section 3: Gallery Instructions --}}
|
||||
<div class="col-md-4">
|
||||
<div class="h-100 p-3 border rounded">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-info text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
|
||||
<i class="fas fa-images"></i>
|
||||
</div>
|
||||
<h6 class="mb-0 ms-3 fw-bold">{{__("Image Gallery Guide")}}</h6>
|
||||
</div>
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-arrow-right text-info me-2 mt-1"></i>
|
||||
<span>{{__("Click 'Open Image Gallery' button to upload images")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-arrow-right text-info me-2 mt-1"></i>
|
||||
<span>{{__("Upload multiple images at once (JPG, PNG, SVG)")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-arrow-right text-info me-2 mt-1"></i>
|
||||
<span>{{__("Click on image or 'Copy Path' button to copy image path")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-arrow-right text-info me-2 mt-1"></i>
|
||||
<span>{{__("Paste the copied path in CSV file's Image column")}}</span>
|
||||
</li>
|
||||
<li class="mb-0 d-flex align-items-start">
|
||||
<i class="fas fa-arrow-right text-info me-2 mt-1"></i>
|
||||
<span><strong>{{__("Format:")}}</strong> custom-fields/filename.jpg</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h5 class="mb-0 fw-bold">
|
||||
<i class="fas fa-download me-2"></i> {{__("Download Current Custom Fields & Image Gallery")}}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="p-3 border rounded h-100">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="fas fa-file-download text-warning me-2 fs-5"></i>
|
||||
<h6 class="mb-0 fw-bold">{{__("Download Current Data")}}</h6>
|
||||
</div>
|
||||
<p class="text-muted small mb-3">{{__("Download all existing custom fields to edit and update them")}}</p>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{{ route('custom-fields.bulk-update.download') }}" class="btn btn-warning">
|
||||
<i class="fas fa-file-csv me-2"></i> <span class="d-none d-sm-inline">{{__("Download Current Custom Fields")}}</span><span class="d-sm-none">{{__("Download CSV")}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="p-3 border rounded h-100">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="fas fa-images text-info me-2 fs-5"></i>
|
||||
<h6 class="mb-0 fw-bold">{{__("Image Gallery")}}</h6>
|
||||
</div>
|
||||
<p class="text-muted small mb-3">{{__("Upload images and copy their paths to use in your CSV file. The gallery allows you to manage all custom field images in one place.")}}</p>
|
||||
<button type="button" class="btn btn-info w-100" data-bs-toggle="modal" data-bs-target="#editModal">
|
||||
<i class="fas fa-images me-2"></i> <span class="d-none d-sm-inline">{{__("Open Image Gallery")}}</span><span class="d-sm-none">{{__("Gallery")}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0 fw-bold text-white">
|
||||
<i class="fas fa-file-upload me-2"></i> {{__("Upload & Process Updated CSV File")}}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<form action="{{ route('custom-fields.bulk-update.process') }}" method="POST" enctype="multipart/form-data" id="bulkUpdateForm">
|
||||
@csrf
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group mb-3">
|
||||
<label for="excel_file" class="form-label fw-bold">
|
||||
{{__("SelectFile")}} <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input type="file" name="excel_file" id="excel_file" class="form-control form-control-lg" accept=".xlsx,.xls,.csv" required style="touch-action: manipulation; -webkit-touch-callout: none;">
|
||||
<div class="form-text">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
{{__("Supported formats: CSV, XLSX, XLS")}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-light border d-flex align-items-start mb-3">
|
||||
<i class="fas fa-lightbulb text-warning me-2 mt-1"></i>
|
||||
<div>
|
||||
<strong class="d-block mb-1">{{__("Important:")}}</strong>
|
||||
<p class="mb-0 small">{{__("Make sure your CSV file includes the ID column and follows the exact format from the downloaded file. You can open CSV in Excel, edit it, and save. Invalid data will be skipped during processing.")}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary w-100 w-md-auto">
|
||||
<i class="fas fa-upload"></i> <span class="d-none d-sm-inline">{{__("Upload and Update")}}</span><span class="d-sm-none">{{__("Upload")}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="editModal" tabindex="-1" role="dialog" aria-labelledby="galleryModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content shadow-lg">
|
||||
<div class="modal-header bg-info text-white">
|
||||
<h5 class="modal-title fw-bold" id="galleryModalLabel">
|
||||
<i class="fas fa-images me-2"></i> {{__("Image Gallery")}}
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h6 class="fw-bold mb-3">
|
||||
<i class="fas fa-upload text-primary me-2"></i> {{__("Upload New Images")}}
|
||||
</h6>
|
||||
<form id="galleryUploadForm" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="d-flex flex-column flex-sm-row gap-2">
|
||||
|
||||
<div class="flex-grow-1">
|
||||
<input type="file" name="image" id="galleryImageInput"
|
||||
class="form-control form-control-lg"
|
||||
accept=".jpg,.jpeg,.png,.svg" multiple
|
||||
style="min-height: 48px;">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-lg" id="galleryUploadBtn">
|
||||
<i class="fas fa-upload me-2"></i>
|
||||
<span class="d-none d-sm-inline">{{__("Upload Images")}}</span>
|
||||
<span class="d-sm-none">{{__("Upload")}}</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-text mt-2">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
{{__("You can select multiple images at once. Supported formats: JPG, JPEG, PNG, SVG (Maximum size: 5MB per image)")}}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<h6 class="fw-bold mb-3">
|
||||
<i class="fas fa-images text-success me-2"></i> {{__("Uploaded Images")}}
|
||||
</h6>
|
||||
</div>
|
||||
<div id="galleryImagesList" class="row g-3" style="max-height: 500px; overflow-y: auto; padding: 10px;">
|
||||
{{-- Gallery images will be loaded here via AJAX --}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-2"></i> {{__("Close")}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Error Details Modal --}}
|
||||
<div class="modal fade" id="errorDetailsModal" tabindex="-1" role="dialog" aria-labelledby="errorModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content shadow-lg">
|
||||
<div class="modal-header bg-danger text-white">
|
||||
<h5 class="modal-title fw-bold" id="errorModalLabel">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i> {{__("Bulk Update Errors")}}
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<div class="alert alert-info d-flex align-items-center mb-3">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<div>
|
||||
<strong>{{__("Summary:")}}</strong>
|
||||
<span id="errorSummary"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive" style="max-height: 500px;">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark sticky-top">
|
||||
<tr>
|
||||
<th style="width: 100px;">{{__("Row #")}}</th>
|
||||
<th>{{__("Error Message")}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="errorTableBody">
|
||||
{{-- Errors will be populated here --}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="moreErrorsNotice" class="alert alert-warning mt-3" style="display: none;">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<strong>{{__("Note:")}}</strong> {{__("Only the first 100 errors are displayed. There may be more errors in your file.")}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-2"></i> {{__("Close")}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
|
||||
@section('js')
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
let isUploading = false; // Flag to prevent multiple simultaneous uploads
|
||||
|
||||
// Handle gallery image upload
|
||||
$('#galleryUploadForm').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Prevent multiple simultaneous uploads
|
||||
if (isUploading) {
|
||||
showErrorToast('{{__("Please wait for the current upload to complete")}}');
|
||||
return false;
|
||||
}
|
||||
|
||||
const formData = new FormData(this);
|
||||
const files = $('#galleryImageInput')[0].files;
|
||||
const submitBtn = $('#galleryUploadBtn');
|
||||
const originalBtnHtml = submitBtn.html();
|
||||
|
||||
if (files.length === 0) {
|
||||
showErrorToast('{{__("Please select at least one image")}}');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set uploading flag and disable button
|
||||
isUploading = true;
|
||||
submitBtn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-2"></i> <span class="d-none d-sm-inline">{{__("Uploading...")}}</span><span class="d-sm-none">{{__("Uploading")}}</span>');
|
||||
|
||||
// Add all files to FormData
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
formData.append('images[]', files[i]);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '{{ route("custom-fields.bulk-upload.gallery.upload") }}',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(response) {
|
||||
if (response.status === 'success') {
|
||||
showSuccessToast(response.message || '{{__("Images uploaded successfully")}}');
|
||||
// Clear file input to prevent duplicate uploads
|
||||
$('#galleryImageInput').val('');
|
||||
// Reset form to clear any cached file data
|
||||
$('#galleryUploadForm')[0].reset();
|
||||
loadGalleryImages();
|
||||
} else {
|
||||
showErrorToast(response.message || '{{__("Error uploading images")}}');
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
const errorMsg = xhr.responseJSON?.message || '{{__("Error uploading images")}}';
|
||||
showErrorToast(errorMsg);
|
||||
},
|
||||
complete: function() {
|
||||
// Reset uploading flag and re-enable button
|
||||
isUploading = false;
|
||||
submitBtn.prop('disabled', false).html(originalBtnHtml);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
function loadGalleryImages() {
|
||||
$.ajax({
|
||||
url: '{{ route("custom-fields.bulk-upload.gallery.list") }}',
|
||||
type: 'GET',
|
||||
success: function(response) {
|
||||
if (response.status === 'success' && response.images) {
|
||||
let html = '';
|
||||
if (response.images.length === 0) {
|
||||
html = `
|
||||
<div class="col-12">
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-images text-muted" style="font-size: 48px;"></i>
|
||||
<p class="text-muted mt-3 mb-0">{{__("No images uploaded yet. Upload images using the form above.")}}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
response.images.forEach(function(image) {
|
||||
html += `
|
||||
<div class="col-6 col-md-4 col-lg-3 mb-3">
|
||||
<div class="card h-100 shadow-sm border">
|
||||
<div class="position-relative">
|
||||
<img src="${image.url}" class="card-img-top" alt="Gallery Image" style="height: 200px; object-fit: cover; cursor: pointer; width: 100%;" onclick="copyImagePath('${image.path}')">
|
||||
<div class="position-absolute top-0 end-0 m-2">
|
||||
<span class="badge bg-dark opacity-75">
|
||||
<i class="fas fa-image"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2 p-md-3">
|
||||
<label class="form-label small fw-bold mb-2 d-block">{{__("Image Path:")}}</label>
|
||||
<input type="text" class="form-control form-control-sm mb-2 image-path-input" value="${image.path}" readonly onclick="this.select();" style="font-size: 11px;">
|
||||
<button type="button" class="btn btn-sm btn-primary w-100 copy-path-btn" data-path="${image.path}">
|
||||
<i class="fas fa-copy me-1"></i> <span class="d-none d-sm-inline">{{__("Copy Path")}}</span><span class="d-sm-none">{{__("Copy")}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
$('#galleryImagesList').html(html);
|
||||
} else {
|
||||
$('#galleryImagesList').html(`
|
||||
<div class="col-12">
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-images text-muted" style="font-size: 48px;"></i>
|
||||
<p class="text-muted mt-3 mb-0">{{__("No images uploaded yet. Upload images using the form above.")}}</p>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$('#galleryImagesList').html(`
|
||||
<div class="col-12">
|
||||
<div class="alert alert-danger text-center">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
{{__("Error loading images. Please try again.")}}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to copy image path
|
||||
async function copyImagePath(path) {
|
||||
try {
|
||||
// Try modern Clipboard API first
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(path);
|
||||
} else {
|
||||
// Fallback for older browsers
|
||||
const input = document.createElement('input');
|
||||
input.value = path;
|
||||
input.style.position = 'fixed';
|
||||
input.style.opacity = '0';
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999); // For mobile devices
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(input);
|
||||
}
|
||||
|
||||
// Show feedback
|
||||
const btn = $(`.copy-path-btn[data-path="${path}"]`);
|
||||
const originalHtml = btn.html();
|
||||
btn.html('<i class="fas fa-check me-1"></i> {{__("Copied!")}}').removeClass('btn-primary').addClass('btn-success');
|
||||
setTimeout(() => {
|
||||
btn.html(originalHtml).removeClass('btn-success').addClass('btn-primary');
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy:', err);
|
||||
showErrorToast('{{__("Failed to copy path to clipboard")}}');
|
||||
}
|
||||
}
|
||||
|
||||
// Copy path to clipboard
|
||||
$(document).on('click', '.copy-path-btn', function() {
|
||||
const path = $(this).data('path');
|
||||
copyImagePath(path);
|
||||
});
|
||||
|
||||
// Also handle click on image to copy path
|
||||
$(document).on('click', '.card-img-top', function() {
|
||||
const pathInput = $(this).closest('.card').find('.image-path-input');
|
||||
if (pathInput.length) {
|
||||
const path = pathInput.val();
|
||||
copyImagePath(path);
|
||||
}
|
||||
});
|
||||
|
||||
// Load gallery when modal is opened
|
||||
$('#editModal').on('show.bs.modal', function() {
|
||||
loadGalleryImages();
|
||||
});
|
||||
|
||||
// Unbind default create-form handler to prevent duplicate messages
|
||||
$('#bulkUpdateForm').off('submit');
|
||||
|
||||
// Handle bulk update form submission
|
||||
$('#bulkUpdateForm').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
const submitBtn = $(this).find('button[type="submit"]');
|
||||
const originalBtnText = submitBtn.html();
|
||||
|
||||
submitBtn.html('<i class="fas fa-spinner fa-spin me-2"></i> {{__("Processing...")}}').attr('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
url: $(this).attr('action'),
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(response) {
|
||||
submitBtn.html(originalBtnText).attr('disabled', false);
|
||||
|
||||
if (response.error) {
|
||||
showErrorToast(response.message || '{{__("Error processing file")}}');
|
||||
if (response.data && response.data.errors && response.data.errors.length > 0) {
|
||||
displayErrors(response.data);
|
||||
}
|
||||
} else {
|
||||
if (response.warning) {
|
||||
showWarningToast(response.message || '{{__("Update completed with errors")}}');
|
||||
} else {
|
||||
showSuccessToast(response.message || '{{__("Update completed successfully")}}');
|
||||
}
|
||||
|
||||
// Show errors if any
|
||||
if (response.data && response.data.errors && response.data.errors.length > 0) {
|
||||
displayErrors(response.data);
|
||||
}
|
||||
|
||||
// Reset form on complete success
|
||||
if (response.data && response.data.error_count === 0) {
|
||||
$('#bulkUpdateForm')[0].reset();
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
submitBtn.html(originalBtnText).attr('disabled', false);
|
||||
const errorMsg = xhr.responseJSON?.message || '{{__("Error uploading file")}}';
|
||||
showErrorToast(errorMsg);
|
||||
|
||||
if (xhr.responseJSON?.data && xhr.responseJSON.data.errors && xhr.responseJSON.data.errors.length > 0) {
|
||||
displayErrors(xhr.responseJSON.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function displayErrors(data) {
|
||||
const errorCount = data.error_count || 0;
|
||||
const successCount = data.success_count || 0;
|
||||
const totalProcessed = data.total_processed || 0;
|
||||
const errors = data.errors || [];
|
||||
const hasMoreErrors = data.has_more_errors || false;
|
||||
|
||||
// Update summary
|
||||
let summaryText = '';
|
||||
if (successCount > 0 && errorCount > 0) {
|
||||
summaryText = `{{__("Successfully updated")}} ${successCount} {{__("row(s)")}}, ${errorCount} {{__("error(s) found")}}`;
|
||||
} else if (errorCount > 0) {
|
||||
summaryText = `${errorCount} {{__("error(s) found")}}`;
|
||||
}
|
||||
$('#errorSummary').text(summaryText);
|
||||
|
||||
// Populate error table
|
||||
const tbody = $('#errorTableBody');
|
||||
tbody.empty();
|
||||
|
||||
if (errors.length === 0) {
|
||||
tbody.html('<tr><td colspan="2" class="text-center text-muted">{{__("No errors to display")}}</td></tr>');
|
||||
} else {
|
||||
errors.forEach(function(error) {
|
||||
const row = $('<tr>');
|
||||
row.append($('<td>').text(error.row || '-'));
|
||||
row.append($('<td>').text(error.message || '-'));
|
||||
tbody.append(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Show/hide more errors notice
|
||||
if (hasMoreErrors) {
|
||||
$('#moreErrorsNotice').show();
|
||||
} else {
|
||||
$('#moreErrorsNotice').hide();
|
||||
}
|
||||
|
||||
// Show modal
|
||||
$('#errorDetailsModal').modal('show');
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
488
resources/views/custom-fields/bulk-upload.blade.php
Normal file
488
resources/views/custom-fields/bulk-upload.blade.php
Normal file
@@ -0,0 +1,488 @@
|
||||
@extends('layouts.main')
|
||||
|
||||
@section('title')
|
||||
{{__("Bulk Upload Custom Fields")}}
|
||||
@endsection
|
||||
|
||||
@section('page-title')
|
||||
<div class="page-title">
|
||||
<div class="row d-flex align-items-center">
|
||||
<div class="col-12 col-md-6">
|
||||
<h4 class="mb-0">@yield('title')</h4>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 text-end">
|
||||
<a href="{{ route('custom-fields.index') }}" class="btn btn-secondary mb-0">
|
||||
<i class="fas fa-arrow-left"></i> {{__("Back")}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@section('content')
|
||||
<section class="section">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-2">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0 fw-bold">
|
||||
<i class="fas fa-info-circle me-2"></i> {{__("Instructions & Reference Guide")}}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<div class="row g-4">
|
||||
{{-- Section 1: Important Instructions --}}
|
||||
<div class="col-md-4">
|
||||
<div class="h-100 p-3 border rounded">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
</div>
|
||||
<h6 class="mb-0 ms-3 fw-bold">{{__("Important Instructions")}}</h6>
|
||||
</div>
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-check-circle text-success me-2 mt-1"></i>
|
||||
<span>{{__("Download the detailed instructions PDF for complete guide")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-check-circle text-success me-2 mt-1"></i>
|
||||
<span>{{__("Download the example CSV file to see the correct format")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-check-circle text-success me-2 mt-1"></i>
|
||||
<span>{{__("Fill in all required fields according to the field type")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-check-circle text-success me-2 mt-1"></i>
|
||||
<span>{{__("For image paths, use the gallery section to upload images")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-check-circle text-success me-2 mt-1"></i>
|
||||
<span>{{__("Category IDs should be comma-separated if multiple categories")}}</span>
|
||||
</li>
|
||||
<li class="mb-0 d-flex align-items-start">
|
||||
<i class="fas fa-check-circle text-success me-2 mt-1"></i>
|
||||
<span>{{__("Values for radio, dropdown, and checkbox types should be pipe-separated (|)")}}</span>
|
||||
</li>
|
||||
<li class="mb-0 d-flex align-items-start mt-3">
|
||||
<i class="fas fa-info-circle text-info me-2 mt-1"></i>
|
||||
<span><strong>{{__("Note:")}}</strong> {{__("You can open the CSV file in Excel, edit it, and save it. Excel will maintain the CSV format.")}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="h-100 p-3 border rounded">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-success text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
|
||||
<i class="fas fa-book"></i>
|
||||
</div>
|
||||
<h6 class="mb-0 ms-3 fw-bold">{{__("Quick Reference")}}</h6>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<strong class="d-block mb-2">{{__("Field Types:")}}</strong>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span class="badge bg-primary">Number</span>
|
||||
<span class="badge bg-secondary">Textbox</span>
|
||||
<span class="badge bg-info text-dark">Fileinput</span>
|
||||
<span class="badge bg-warning text-dark">Radio</span>
|
||||
<span class="badge bg-success">Dropdown</span>
|
||||
<span class="badge bg-danger">Checkbox</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong class="d-block mb-2">{{__("Data Format:")}}</strong>
|
||||
<ul class="list-unstyled mb-0 small">
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-dot-circle text-primary me-2"></i>
|
||||
<strong>{{__("Required:")}}</strong> 0 = {{__("Optional")}}, 1 = {{__("Required")}}
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-dot-circle text-primary me-2"></i>
|
||||
<strong>{{__("Status:")}}</strong> 0 = {{__("Inactive")}}, 1 = {{__("Active")}}
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-dot-circle text-primary me-2"></i>
|
||||
<strong>{{__("Values:")}}</strong> {{__("Use pipe (|) to separate options")}}
|
||||
</li>
|
||||
<li class="mb-0">
|
||||
<i class="fas fa-dot-circle text-primary me-2"></i>
|
||||
<strong>{{__("Categories:")}}</strong> {{__("Use comma (,) to separate IDs")}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{-- Section 3: Gallery Instructions --}}
|
||||
<div class="col-md-4">
|
||||
<div class="h-100 p-3 border rounded">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-info text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
|
||||
<i class="fas fa-images"></i>
|
||||
</div>
|
||||
<h6 class="mb-0 ms-3 fw-bold">{{__("Image Gallery Guide")}}</h6>
|
||||
</div>
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-arrow-right text-info me-2 mt-1"></i>
|
||||
<span>{{__("Click 'Open Image Gallery' button to upload images")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-arrow-right text-info me-2 mt-1"></i>
|
||||
<span>{{__("Upload multiple images at once (JPG, PNG, SVG)")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-arrow-right text-info me-2 mt-1"></i>
|
||||
<span>{{__("Click on image or 'Copy Path' button to copy image path")}}</span>
|
||||
</li>
|
||||
<li class="mb-3 d-flex align-items-start">
|
||||
<i class="fas fa-arrow-right text-info me-2 mt-1"></i>
|
||||
<span>{{__("Paste the copied path in CSV file's Image column")}}</span>
|
||||
</li>
|
||||
<li class="mb-0 d-flex align-items-start">
|
||||
<i class="fas fa-arrow-right text-info me-2 mt-1"></i>
|
||||
<span><strong>{{__("Format:")}}</strong> custom-fields/filename.jpg</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0 fw-bold text-white">
|
||||
<i class="fas fa-download me-2"></i> {{__("Download Files & Image Gallery")}}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="p-3 border rounded h-100">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="fas fa-file-download text-success me-2 fs-5"></i>
|
||||
<h6 class="mb-0 fw-bold">{{__("Download Required Files")}}</h6>
|
||||
</div>
|
||||
<p class="text-muted small mb-3">{{__("Download these files before starting the bulk upload process")}}</p>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{{ route('custom-fields.bulk-upload.instructions-pdf') }}" class="btn btn-primary">
|
||||
<i class="fas fa-file-pdf me-2"></i> <span class="d-none d-sm-inline">{{__("Download Instructions PDF")}}</span><span class="d-sm-none">{{__("PDF Guide")}}</span>
|
||||
</a>
|
||||
<a href="{{ route('custom-fields.bulk-upload.example') }}" class="btn btn-success">
|
||||
<i class="fas fa-file-csv me-2"></i> <span class="d-none d-sm-inline">{{__("Download Example CSV File")}}</span><span class="d-sm-none">{{__("Example CSV")}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="p-3 border rounded h-100">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="fas fa-images text-info me-2 fs-5"></i>
|
||||
<h6 class="mb-0 fw-bold">{{__("Image Gallery")}}</h6>
|
||||
</div>
|
||||
<p class="text-muted small mb-3">{{__("Upload images and copy their paths to use in your CSV file. The gallery allows you to manage all custom field images in one place.")}}</p>
|
||||
<button type="button" class="btn btn-info w-100" data-bs-toggle="modal" data-bs-target="#editModal">
|
||||
<i class="fas fa-images me-2"></i> <span class="d-none d-sm-inline">{{__("Open Image Gallery")}}</span><span class="d-sm-none">{{__("Gallery")}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0 fw-bold text-white">
|
||||
<i class="fas fa-file-upload me-2"></i> {{__("Upload & Process CSV File")}}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<form action="{{ route('custom-fields.bulk-upload.process') }}" method="POST" class="create-form" enctype="multipart/form-data" id="bulkUploadForm">
|
||||
@csrf
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group mb-3">
|
||||
<label for="excel_file" class="form-label fw-bold">
|
||||
{{__("SelectFile")}} <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input type="file" name="excel_file" id="excel_file" class="form-control form-control-lg" accept=".xlsx,.xls,.csv" required style="touch-action: manipulation; -webkit-touch-callout: none;">
|
||||
<div class="form-text">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
{{__("Supported formats: CSV, XLSX, XLS")}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-light border d-flex align-items-start mb-3">
|
||||
<i class="fas fa-lightbulb text-warning me-2 mt-1"></i>
|
||||
<div>
|
||||
<strong class="d-block mb-1">{{__("Important:")}}</strong>
|
||||
<p class="mb-0 small">{{__("Make sure your CSV file follows the exact format from the example file. You can open CSV in Excel, edit it, and save. Invalid data will be skipped during processing.")}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary w-100 w-md-auto">
|
||||
<i class="fas fa-upload"></i> <span class="d-none d-sm-inline">{{__("Upload and Process")}}</span><span class="d-sm-none">{{__("Upload")}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{-- <div id="editModal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel1" aria-hidden="true"> --}}
|
||||
<div class="modal fade" id="editModal" tabindex="-1" role="dialog" aria-labelledby="galleryModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content shadow-lg">
|
||||
<div class="modal-header bg-info text-white">
|
||||
<h5 class="modal-title fw-bold" id="galleryModalLabel">
|
||||
<i class="fas fa-images me-2"></i> {{__("Image Gallery")}}
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h6 class="fw-bold mb-3">
|
||||
<i class="fas fa-upload text-primary me-2"></i> {{__("Upload New Images")}}
|
||||
</h6>
|
||||
<form id="galleryUploadForm" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="d-flex flex-column flex-sm-row gap-2">
|
||||
|
||||
<div class="flex-grow-1">
|
||||
<input type="file" name="image" id="galleryImageInput"
|
||||
class="form-control form-control-lg"
|
||||
accept=".jpg,.jpeg,.png,.svg" multiple
|
||||
style="min-height: 48px;">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-lg" id="galleryUploadBtn">
|
||||
<i class="fas fa-upload me-2"></i>
|
||||
<span class="d-none d-sm-inline">{{__("Upload Images")}}</span>
|
||||
<span class="d-sm-none">{{__("Upload")}}</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-text mt-2">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
{{__("You can select multiple images at once. Supported formats: JPG, JPEG, PNG, SVG (Max: 5MB)")}}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<h6 class="fw-bold mb-3">
|
||||
<i class="fas fa-images text-success me-2"></i> {{__("Uploaded Images")}}
|
||||
</h6>
|
||||
</div>
|
||||
<div id="galleryImagesList" class="row g-3" style="max-height: 500px; overflow-y: auto; padding: 10px;">
|
||||
{{-- Gallery images will be loaded here via AJAX --}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-2"></i> {{__("Close")}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
|
||||
@section('js')
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
let isUploading = false; // Flag to prevent multiple simultaneous uploads
|
||||
|
||||
// Handle gallery image upload
|
||||
$('#galleryUploadForm').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Prevent multiple simultaneous uploads
|
||||
if (isUploading) {
|
||||
showErrorToast('{{__("Please wait for the current upload to complete")}}');
|
||||
return false;
|
||||
}
|
||||
|
||||
const formData = new FormData(this);
|
||||
const files = $('#galleryImageInput')[0].files;
|
||||
const submitBtn = $('#galleryUploadBtn');
|
||||
const originalBtnHtml = submitBtn.html();
|
||||
|
||||
if (files.length === 0) {
|
||||
showErrorToast('{{__("Please select at least one image")}}');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set uploading flag and disable button
|
||||
isUploading = true;
|
||||
submitBtn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin me-2"></i> <span class="d-none d-sm-inline">{{__("Uploading...")}}</span><span class="d-sm-none">{{__("Uploading")}}</span>');
|
||||
|
||||
// Add all files to FormData
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
formData.append('images[]', files[i]);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '{{ route("custom-fields.bulk-upload.gallery.upload") }}',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(response) {
|
||||
if (response.status === 'success') {
|
||||
showSuccessToast(response.message || '{{__("Images uploaded successfully")}}');
|
||||
// Clear file input to prevent duplicate uploads
|
||||
$('#galleryImageInput').val('');
|
||||
// Reset form to clear any cached file data
|
||||
$('#galleryUploadForm')[0].reset();
|
||||
loadGalleryImages();
|
||||
} else {
|
||||
showErrorToast(response.message || '{{__("Error uploading images")}}');
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
const errorMsg = xhr.responseJSON?.message || '{{__("Error uploading images")}}';
|
||||
showErrorToast(errorMsg);
|
||||
},
|
||||
complete: function() {
|
||||
// Reset uploading flag and re-enable button
|
||||
isUploading = false;
|
||||
submitBtn.prop('disabled', false).html(originalBtnHtml);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
function loadGalleryImages() {
|
||||
$.ajax({
|
||||
url: '{{ route("custom-fields.bulk-upload.gallery.list") }}',
|
||||
type: 'GET',
|
||||
success: function(response) {
|
||||
if (response.status === 'success' && response.images) {
|
||||
let html = '';
|
||||
if (response.images.length === 0) {
|
||||
html = `
|
||||
<div class="col-12">
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-images text-muted" style="font-size: 48px;"></i>
|
||||
<p class="text-muted mt-3 mb-0">{{__("No images uploaded yet. Upload images using the form above.")}}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
response.images.forEach(function(image) {
|
||||
html += `
|
||||
<div class="col-6 col-md-4 col-lg-3 mb-3">
|
||||
<div class="card h-100 shadow-sm border">
|
||||
<div class="position-relative">
|
||||
<img src="${image.url}" class="card-img-top" alt="Gallery Image" style="height: 200px; object-fit: cover; cursor: pointer; width: 100%;" onclick="copyImagePath('${image.path}')">
|
||||
<div class="position-absolute top-0 end-0 m-2">
|
||||
<span class="badge bg-dark opacity-75">
|
||||
<i class="fas fa-image"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2 p-md-3">
|
||||
<label class="form-label small fw-bold mb-2 d-block">{{__("Image Path:")}}</label>
|
||||
<input type="text" class="form-control form-control-sm mb-2 image-path-input" value="${image.path}" readonly onclick="this.select();" style="font-size: 11px;">
|
||||
<button type="button" class="btn btn-sm btn-primary w-100 copy-path-btn" data-path="${image.path}">
|
||||
<i class="fas fa-copy me-1"></i> <span class="d-none d-sm-inline">{{__("Copy Path")}}</span><span class="d-sm-none">{{__("Copy")}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
$('#galleryImagesList').html(html);
|
||||
} else {
|
||||
$('#galleryImagesList').html(`
|
||||
<div class="col-12">
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-images text-muted" style="font-size: 48px;"></i>
|
||||
<p class="text-muted mt-3 mb-0">{{__("No images uploaded yet. Upload images using the form above.")}}</p>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$('#galleryImagesList').html(`
|
||||
<div class="col-12">
|
||||
<div class="alert alert-danger text-center">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
{{__("Error loading images. Please try again.")}}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to copy image path
|
||||
async function copyImagePath(path) {
|
||||
try {
|
||||
// Try modern Clipboard API first
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(path);
|
||||
} else {
|
||||
// Fallback for older browsers
|
||||
const input = document.createElement('input');
|
||||
input.value = path;
|
||||
input.style.position = 'fixed';
|
||||
input.style.opacity = '0';
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999); // For mobile devices
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(input);
|
||||
}
|
||||
|
||||
// Show feedback
|
||||
const btn = $(`.copy-path-btn[data-path="${path}"]`);
|
||||
const originalHtml = btn.html();
|
||||
btn.html('<i class="fas fa-check me-1"></i> {{__("Copied!")}}').removeClass('btn-primary').addClass('btn-success');
|
||||
setTimeout(() => {
|
||||
btn.html(originalHtml).removeClass('btn-success').addClass('btn-primary');
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy:', err);
|
||||
showErrorToast('{{__("Failed to copy path to clipboard")}}');
|
||||
}
|
||||
}
|
||||
|
||||
// Copy path to clipboard
|
||||
$(document).on('click', '.copy-path-btn', function() {
|
||||
const path = $(this).data('path');
|
||||
copyImagePath(path);
|
||||
});
|
||||
|
||||
// Also handle click on image to copy path
|
||||
$(document).on('click', '.card-img-top', function() {
|
||||
const pathInput = $(this).closest('.card').find('.image-path-input');
|
||||
if (pathInput.length) {
|
||||
const path = pathInput.val();
|
||||
copyImagePath(path);
|
||||
}
|
||||
});
|
||||
|
||||
// Load gallery when modal is opened
|
||||
$('#editModal').on('show.bs.modal', function() {
|
||||
loadGalleryImages();
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
216
resources/views/custom-fields/create.blade.php
Normal file
216
resources/views/custom-fields/create.blade.php
Normal file
@@ -0,0 +1,216 @@
|
||||
@extends('layouts.main')
|
||||
|
||||
@section('title')
|
||||
{{__("Custom Fields")}}
|
||||
@endsection
|
||||
|
||||
@section('page-title')
|
||||
<div class="page-title">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 order-md-1 order-last">
|
||||
<h4>@yield('title')</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<section class="section">
|
||||
<form action="{{ route('custom-fields.store') }}" method="POST" class="create-form" data-success-function="afterCustomFieldCreationSuccess" data-parsley-validate enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-header">{{__("Create Custom Field")}}</div>
|
||||
<div class="card-body mt-2">
|
||||
|
||||
<ul class="nav nav-tabs" id="langTabs" role="tablist">
|
||||
@foreach($languages as $key => $lang)
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @if($key == 0) active @endif" id="tab-{{ $lang->id }}" data-bs-toggle="tab" data-bs-target="#lang-{{ $lang->id }}" type="button" role="tab">
|
||||
{{ $lang->name }}
|
||||
</button>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
|
||||
<div class="tab-content mt-3">
|
||||
@foreach($languages as $key => $lang)
|
||||
<div class="tab-pane fade @if($key == 0) show active @endif" id="lang-{{ $lang->id }}" role="tabpanel">
|
||||
<input type="hidden" name="languages[]" value="{{ $lang->id }}">
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ __('Field Name') }} ({{ $lang->name }})</label>
|
||||
<input type="text" name="name[{{ $lang->id }}]" class="form-control" @if($lang->id != 1)@endif>
|
||||
</div>
|
||||
|
||||
@if($lang->id == 1)
|
||||
{{-- Type (Only in English) --}}
|
||||
<div class="form-group">
|
||||
<label>{{ __('Field Type') }}</label>
|
||||
<select name="type" class="form-control" required>
|
||||
<option value="number">{{ __("Number Input") }}</option>
|
||||
<option value="textbox">{{ __("Text Input") }}</option>
|
||||
<option value="fileinput">{{ __("File Input") }}</option>
|
||||
<option value="radio">{{ __("Radio") }}</option>
|
||||
<option value="dropdown">{{ __("Dropdown") }}</option>
|
||||
<option value="checkbox">{{ __("Checkboxes") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 form-group min-max-fields">
|
||||
<label>{{ __('Field Length (Min)') }}</label>
|
||||
<input type="number" name="min_length" class="form-control" min="0">
|
||||
</div>
|
||||
<div class="col-md-6 form-group min-max-fields">
|
||||
<label>{{ __('Field Length (Max)') }}</label>
|
||||
<input type="number" name="max_length" class="form-control" min="0">
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-info mt-2">
|
||||
{{ __('Field type, min/max length, and status can only be set in English.') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ __('Field Values') }} ({{ $lang->name }})</label>
|
||||
<select name="values[{{ $lang->id }}][]" data-tags="true" data-placeholder="{{ __("Select an option") }}" data-allow-clear="true" data-token-separators="[',']" class="select2 w-100 full-width-select2" multiple="multiple" @if($lang->id == 1) required @endif></select>
|
||||
@if($lang->id != 1)
|
||||
<small class="text-muted">{{ __('Used for translatable field types.') }}</small>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-12 form-group mandatory">
|
||||
<label for="image" class="form-label">{{ __('Icon ') }}</label>
|
||||
<input type="file" name="image" id="image" class="form-control" data-parsley-required="true" accept=" .jpg, .jpeg, .png, .svg">
|
||||
{{__("(use 256 x 256 size for better view)")}}
|
||||
<div class="img_error" style="color:#DC3545;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 form-group mandatory">
|
||||
<div class="form-check form-switch ">
|
||||
<input type="hidden" name="required" id="required" value="0">
|
||||
<input class="form-check-input status-switch" type="checkbox" role="switch" aria-label="required">{{ __('Required') }}
|
||||
<label class="form-check-label" for="required"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 form-group mandatory">
|
||||
<div class="form-check form-switch ">
|
||||
<input type="hidden" name="status" id="status" value="0">
|
||||
<input class="form-check-input status-switch" type="checkbox" role="switch" aria-label="status">{{ __('Active') }}
|
||||
<label class="form-check-label" for="status"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if ($cat_id == 0)
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-header">{{__("Category")}}</div>
|
||||
<div class="card-body mt-2">
|
||||
<div class="sub_category_lit">
|
||||
@foreach ($categories as $category)
|
||||
<div class="category">
|
||||
<div class="category-header">
|
||||
<label>
|
||||
<input type="checkbox" name="selected_categories[]" value="{{ $category->id }}"> {{ $category->name }}
|
||||
</label>
|
||||
@if (!empty($category->subcategories))
|
||||
@php
|
||||
// Get current language from Session (not from foreach loop)
|
||||
$currentLang = Session::get('language');
|
||||
// Check RTL: use accessor which returns boolean (rtl != 0)
|
||||
$isRtl = false;
|
||||
if (!empty($currentLang)) {
|
||||
try {
|
||||
// Try to get raw attribute first, fallback to accessor
|
||||
$rtlRaw = method_exists($currentLang, 'getRawOriginal') ? $currentLang->getRawOriginal('rtl') : null;
|
||||
if ($rtlRaw !== null) {
|
||||
$isRtl = ($rtlRaw == 1 || $rtlRaw === true);
|
||||
} else {
|
||||
$isRtl = ($currentLang->rtl == true || $currentLang->rtl === 1);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$isRtl = ($currentLang->rtl == true || $currentLang->rtl === 1);
|
||||
}
|
||||
}
|
||||
$arrowIcon = $isRtl ? '' : ''; // fa-caret-left for RTL, fa-caret-right for LTR
|
||||
@endphp
|
||||
<i style='font-size:24px' class='fas toggle-button'>{!! $arrowIcon !!}</i>
|
||||
@endif
|
||||
</div>
|
||||
<div class="subcategories" style="display: none;">
|
||||
@if (!empty($category->subcategories))
|
||||
@include('category.treeview', ['categories' => $category->subcategories])
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<input type="hidden" name="selected_categories[]" value="{{ $cat_id }}">
|
||||
@endif
|
||||
<div class="col-md-12 text-end">
|
||||
<input type="submit" class="btn btn-primary" value="{{__("Save and Back")}}">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@endsection
|
||||
@section('js')
|
||||
<script>
|
||||
function updateCustomFieldUI() {
|
||||
const type = $('select[name="type"]').val();
|
||||
const valuesTypes = ['radio', 'dropdown', 'checkbox'];
|
||||
|
||||
$('.tab-pane').each(function () {
|
||||
const $tab = $(this);
|
||||
|
||||
const $fieldValues = $tab.find('select[name^="values"]')
|
||||
.closest('.form-group');
|
||||
|
||||
const $minMaxGroup = $tab.find('.min-max-fields');
|
||||
|
||||
if (valuesTypes.includes(type)) {
|
||||
$fieldValues.show();
|
||||
$minMaxGroup.hide();
|
||||
} else if (type === 'fileinput') {
|
||||
$fieldValues.hide();
|
||||
$minMaxGroup.hide();
|
||||
} else {
|
||||
$fieldValues.hide();
|
||||
$minMaxGroup.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
updateCustomFieldUI();
|
||||
|
||||
$(document).on('change', 'select[name="type"]', function () {
|
||||
updateCustomFieldUI();
|
||||
});
|
||||
});
|
||||
|
||||
function afterCustomFieldCreationSuccess() {
|
||||
setTimeout(function () {
|
||||
window.location.href = "{{ route('custom-fields.index') }}";
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
519
resources/views/custom-fields/edit.blade.php
Normal file
519
resources/views/custom-fields/edit.blade.php
Normal file
@@ -0,0 +1,519 @@
|
||||
@extends('layouts.main')
|
||||
|
||||
@section('title')
|
||||
{{ __('Custom Fields') }}
|
||||
@endsection
|
||||
|
||||
@section('page-title')
|
||||
<div class="page-title">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 order-md-1 order-last">
|
||||
<h4>@yield('title')</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<section class="section">
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary" href="{{ url('custom-fields') }}">
|
||||
< {{ __('Back to Custom Fields') }} </a>
|
||||
@if (in_array($custom_field->type, ['radio', 'checkbox', 'dropdown']))
|
||||
<a class="btn btn-primary" data-bs-toggle="modal" data-bs-target='#addModal'>+
|
||||
{{ __('Add Options') }}</a>
|
||||
@endif
|
||||
</div>
|
||||
<form action="{{ route('custom-fields.update', $custom_field->id) }}" class="edit-form"
|
||||
data-success-function="afterCustomFieldUpdate" method="POST" data-parsley-validate
|
||||
enctype="multipart/form-data">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ __('Edit Custom Field') }}</div>
|
||||
<div class="card-body mt-2">
|
||||
|
||||
<ul class="nav nav-tabs" id="langTabs" role="tablist">
|
||||
@foreach ($languages as $key => $lang)
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @if ($key == 0) active @endif"
|
||||
id="tab-{{ $lang->id }}" data-bs-toggle="tab"
|
||||
data-bs-target="#lang-{{ $lang->id }}" type="button" role="tab">
|
||||
{{ $lang->name }}
|
||||
</button>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
|
||||
<div class="tab-content mt-3">
|
||||
@foreach ($languages as $key => $lang)
|
||||
<div class="tab-pane fade @if ($key == 0) show active @endif"
|
||||
id="lang-{{ $lang->id }}" role="tabpanel">
|
||||
<input type="hidden" name="languages[]" value="{{ $lang->id }}">
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ __('Field Name') }} ({{ $lang->name }})</label>
|
||||
<input type="text" name="name[{{ $lang->id }}]" class="form-control"
|
||||
value="{{ $translations[$lang->id]['name'] ?? '' }}"
|
||||
@if ($lang->id == 1) @endif>
|
||||
</div>
|
||||
|
||||
@if ($lang->id == 1)
|
||||
{{-- Type (Only in English) - Read Only --}}
|
||||
<div class="form-group">
|
||||
<label>{{ __('Field Type') }}</label>
|
||||
<select name="type" id="type" class="form-control" required
|
||||
disabled>
|
||||
<option value="number"
|
||||
{{ $custom_field->type == 'number' ? 'selected' : '' }}>
|
||||
{{ __('Number Input') }}</option>
|
||||
<option value="textbox"
|
||||
{{ $custom_field->type == 'textbox' ? 'selected' : '' }}>
|
||||
{{ __('Text Input') }}</option>
|
||||
<option value="fileinput"
|
||||
{{ $custom_field->type == 'fileinput' ? 'selected' : '' }}>
|
||||
{{ __('File Input') }}</option>
|
||||
<option value="radio"
|
||||
{{ $custom_field->type == 'radio' ? 'selected' : '' }}>
|
||||
{{ __('Radio') }}</option>
|
||||
<option value="dropdown"
|
||||
{{ $custom_field->type == 'dropdown' ? 'selected' : '' }}>
|
||||
{{ __('Dropdown') }}</option>
|
||||
<option value="checkbox"
|
||||
{{ $custom_field->type == 'checkbox' ? 'selected' : '' }}>
|
||||
{{ __('Checkboxes') }}</option>
|
||||
</select>
|
||||
<input type="hidden" name="type" value="{{ $custom_field->type }}">
|
||||
<small
|
||||
class="text-muted">{{ __('Field type cannot be changed after creation.') }}</small>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 form-group min-max-fields">
|
||||
<label>{{ __('Field Length (Min)') }}</label>
|
||||
<input type="number" name="min_length" class="form-control"
|
||||
value="{{ $custom_field->min_length !== null ? $custom_field->min_length : '' }}" min="0">
|
||||
</div>
|
||||
<div class="col-md-6 form-group min-max-fields">
|
||||
<label>{{ __('Field Length (Max)') }}</label>
|
||||
<input type="number" name="max_length" class="form-control"
|
||||
value="{{ $custom_field->max_length !== null ? $custom_field->max_length : '' }}" min="0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-12 form-group">
|
||||
<label for="image"
|
||||
class="form-label">{{ __('Icon ') }}</label>
|
||||
<input type="file" name="image" id="image"
|
||||
class="form-control" accept=" .jpg, .jpeg, .png, .svg">
|
||||
{{ __('(use 256 x 256 size for better view)') }}
|
||||
<div class="field_img mt-2">
|
||||
<img src="{{ empty($custom_field->image) ? asset('assets/img_placeholder.jpeg') : $custom_field->image }}"
|
||||
alt="" id="blah"
|
||||
class="preview-image img w-25">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 form-group mandatory">
|
||||
<div class="form-check form-switch ">
|
||||
<input type="hidden" name="required" id="required"
|
||||
value="{{ $custom_field->required ? '1' : '0' }}">
|
||||
<input class="form-check-input status-switch" type="checkbox"
|
||||
role="switch" aria-label="required"
|
||||
{{ $custom_field->required ? 'checked' : '' }}>{{ __('Required') }}
|
||||
<label class="form-check-label" for="required"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 form-group mandatory">
|
||||
<div class="form-check form-switch ">
|
||||
<input type="hidden" name="status" id="status"
|
||||
value="{{ $custom_field->status ? '1' : '0' }}">
|
||||
<input class="form-check-input status-switch" type="checkbox"
|
||||
role="switch" aria-label="status"
|
||||
{{ $custom_field->status ? 'checked' : '' }}>{{ __('Active') }}
|
||||
<label class="form-check-label" for="status"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ __('Field Values') }} ({{ $lang->name }})</label>
|
||||
{{-- <select name="values[{{ $lang->id }}][]" data-tags="true"
|
||||
data-placeholder="{{ __('Select an option') }}" data-allow-clear="true"
|
||||
data-token-separators="[',']" class="select2 w-100 full-width-select2"
|
||||
multiple="multiple" @if ($lang->id == 1) @endif>
|
||||
@php
|
||||
$fieldValues = [];
|
||||
if ($lang->id == 1) {
|
||||
$fieldValues = is_array($custom_field->values)
|
||||
? $custom_field->values
|
||||
: [];
|
||||
} elseif (
|
||||
isset($translations[$lang->id]['value']) &&
|
||||
is_array($translations[$lang->id]['value'])
|
||||
) {
|
||||
$fieldValues = $translations[$lang->id]['value'];
|
||||
}
|
||||
@endphp
|
||||
@foreach ($fieldValues as $value)
|
||||
<option value="{{ $value }}" selected>{{ $value }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select> --}}
|
||||
@php
|
||||
$fieldValues = [];
|
||||
|
||||
if ($lang->id == 1) {
|
||||
$fieldValues = is_array($custom_field->values)
|
||||
? $custom_field->values
|
||||
: [];
|
||||
} elseif (
|
||||
isset($translations[$lang->id]['value']) &&
|
||||
is_array($translations[$lang->id]['value'])
|
||||
) {
|
||||
$fieldValues = $translations[$lang->id]['value'];
|
||||
}
|
||||
|
||||
if (is_string($fieldValues)) {
|
||||
$fieldValues = json_decode($fieldValues, true);
|
||||
}
|
||||
|
||||
if (!is_array($fieldValues)) {
|
||||
$fieldValues = [];
|
||||
}
|
||||
|
||||
// Ensure all values including 0 are preserved as strings for Tagify
|
||||
$fieldValues = array_map(function($v) {
|
||||
return $v !== null && $v !== '' ? (string)$v : '';
|
||||
}, $fieldValues);
|
||||
@endphp
|
||||
|
||||
|
||||
<input
|
||||
type="text"
|
||||
name="values[{{ $lang->id }}]"
|
||||
class="tagify-input w-100"
|
||||
value='@json($fieldValues ?? [])'
|
||||
data-field-values='@json($fieldValues ?? [])'
|
||||
>
|
||||
|
||||
|
||||
|
||||
@if ($lang->id != 1)
|
||||
<small
|
||||
class="text-muted">{{ __('Used for translatable field types.') }}</small>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ __('Category') }}</div>
|
||||
<div class="card-body mt-2">
|
||||
<div class="sub_category_lit">
|
||||
@foreach ($categories as $category)
|
||||
<div class="category">
|
||||
<div class="category-header">
|
||||
<label>
|
||||
<input type="checkbox" name="selected_categories[]"
|
||||
value="{{ $category->id }}"
|
||||
{{ in_array($category->id, $selected_categories) ? 'checked' : '' }}>
|
||||
{{ $category->name }}
|
||||
</label>
|
||||
@if (!empty($category->subcategories))
|
||||
@php
|
||||
// Get current language from Session (not from foreach loop)
|
||||
$currentLang = Session::get('language');
|
||||
// Check RTL: use accessor which returns boolean (rtl != 0)
|
||||
$isRtl = false;
|
||||
if (!empty($currentLang)) {
|
||||
try {
|
||||
// Try to get raw attribute first, fallback to accessor
|
||||
$rtlRaw = method_exists($currentLang, 'getRawOriginal') ? $currentLang->getRawOriginal('rtl') : null;
|
||||
if ($rtlRaw !== null) {
|
||||
$isRtl = ($rtlRaw == 1 || $rtlRaw === true);
|
||||
} else {
|
||||
$isRtl = ($currentLang->rtl == true || $currentLang->rtl === 1);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$isRtl = ($currentLang->rtl == true || $currentLang->rtl === 1);
|
||||
}
|
||||
}
|
||||
$arrowIcon = $isRtl ? '' : ''; // fa-caret-left for RTL, fa-caret-right for LTR
|
||||
@endphp
|
||||
<i style="font-size:24px"
|
||||
class="fas toggle-button {{ in_array($category->id, $selected_all_categories) ? 'open' : '' }}">
|
||||
{!! $arrowIcon !!}
|
||||
</i>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- ✅ Show children open if parent or child is selected --}}
|
||||
<div class="subcategories"
|
||||
style="display: {{ in_array($category->id, $selected_all_categories) ? 'block' : 'none' }};">
|
||||
@if (!empty($category->subcategories))
|
||||
@include('category.treeview', [
|
||||
'categories' => $category->subcategories,
|
||||
'selected_categories' => $selected_categories,
|
||||
'selected_all_categories' => $selected_all_categories,
|
||||
])
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 text-end mb-3">
|
||||
<input type="submit" class="btn btn-primary" value="{{ __('Save and Back') }}">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<!-- @if (in_array($custom_field->type, ['radio', 'checkbox', 'dropdown']))
|
||||
<div class="col-md-12 col-sm-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<table class="table table-borderless table-striped" id="table_list"
|
||||
data-toggle="table" data-url="{{ route('custom-fields.value.show', $custom_field->id) }}"
|
||||
data-click-to-select="true"
|
||||
data-page-list="[5, 10, 20, 50, 100, 200]" data-search="true" data-search-align="right"
|
||||
data-toolbar="#toolbar" data-show-columns="true" data-show-refresh="true"
|
||||
data-trim-on-search="false" data-responsive="true" data-sort-name="id"
|
||||
data-escape="true"
|
||||
data-sort-order="desc" data-query-params="queryParams"
|
||||
data-table="custom_fields" data-use-row-attr-func="true" data-mobile-responsive="true">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col" data-field="id" data-align="center" data-sortable="true">{{ __('ID') }}</th>
|
||||
<th scope="col" data-field="value" data-align="center" data-sortable="true">{{ __('Value') }}</th>
|
||||
<th scope="col" data-field="operate"data-escape="false" data-align="center" data-sortable="false" data-events="customFieldValueEvents">{{ __('Action') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif -->
|
||||
{{-- add modal --}}
|
||||
@if (in_array($custom_field->type, ['radio', 'checkbox', 'dropdown']))
|
||||
<div id="addModal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel1"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="myModalLabel1">{{ __('Add Values') }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="{{ route('custom-fields.value.add', $custom_field->id) }}"
|
||||
class="create-form form-horizontal" enctype="multipart/form-data" method="POST"
|
||||
data-parsley-validate>
|
||||
@csrf
|
||||
<div class="col-md-12 form-group mandatory">
|
||||
<label for="values" class="mandatory form-label">{{ __('Field Values') }}</label>
|
||||
<input type="text" name="values" id="values" class="form-control"
|
||||
value="{{ old('values') }}" data-parsley-required="true">
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="field_id" id="field_id" value="{{ $custom_field->id }}">
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary waves-effect"
|
||||
data-bs-dismiss="modal">{{ __('Close') }}</button>
|
||||
<button type="submit"
|
||||
class="btn btn-primary waves-effect waves-light">{{ __('Save') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
{{-- edit modal --}}
|
||||
<div id="editModal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel1"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="myModalLabel1">{{ __('Edit Custome Field Values') }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="{{ route('custom-fields.value.update', $custom_field->id) }}"
|
||||
class="edit-form form-horizontal" enctype="multipart/form-data" method="POST"
|
||||
data-parsley-validate>
|
||||
@csrf
|
||||
<input type="hidden" name="old_custom_field_value" id="old_custom_field_value" />
|
||||
<div class="col-md-12 form-group mandatory">
|
||||
<label for="new_custom_field_value"
|
||||
class="mandatory form-label">{{ __('Name') }}</label>
|
||||
<input type="text" name="new_custom_field_value" id="new_custom_field_value"
|
||||
class="form-control" value="" data-parsley-required="true">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary waves-effect"
|
||||
data-bs-dismiss="modal">{{ __('Close') }}</button>
|
||||
<button type="submit"
|
||||
class="btn btn-primary waves-effect waves-light">{{ __('Save') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
@endif
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
@section('js')
|
||||
<script>
|
||||
window.existingTranslations = @json($translations ?? []);
|
||||
// console.log('Loaded Translations:', window.existingTranslations);
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function updateCustomFieldUI() {
|
||||
const type = $('select[name="type"]').val();
|
||||
const valuesTypes = ['radio', 'dropdown', 'checkbox'];
|
||||
|
||||
$('.tab-pane').each(function() {
|
||||
const $tab = $(this);
|
||||
|
||||
const $fieldValues = $tab.find('select[name^="values"]')
|
||||
.closest('.form-group');
|
||||
|
||||
const $minMaxGroup = $tab.find('.min-max-fields');
|
||||
|
||||
if (valuesTypes.includes(type)) {
|
||||
$fieldValues.show();
|
||||
$minMaxGroup.hide();
|
||||
} else if (type === 'fileinput') {
|
||||
$fieldValues.hide();
|
||||
$minMaxGroup.hide();
|
||||
} else {
|
||||
$fieldValues.hide();
|
||||
$minMaxGroup.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
updateCustomFieldUI();
|
||||
|
||||
$(document).on('change', 'select[name="type"]', function() {
|
||||
updateCustomFieldUI();
|
||||
});
|
||||
|
||||
// Initialize select2 for all value selects
|
||||
$('select[name^="values"]').each(function() {
|
||||
if (!$(this).hasClass('select2-hidden-accessible')) {
|
||||
$(this).select2({
|
||||
tags: true,
|
||||
tokenSeparators: [','],
|
||||
placeholder: "{{ __('Select an option') }}",
|
||||
allowClear: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Image preview functionality
|
||||
$('#image').on('change', function() {
|
||||
const [file] = this.files;
|
||||
if (file) {
|
||||
$('#blah').attr('src', URL.createObjectURL(file));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function afterCustomFieldUpdate() {
|
||||
setTimeout(function() {
|
||||
window.location.href = "{{ route('custom-fields.index') }}"
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
// const input = document.querySelector('.tagify-input');
|
||||
|
||||
// new Tagify(input, {
|
||||
// delimiters: ",",
|
||||
// editTags: true,
|
||||
// duplicate: false,
|
||||
// dropdown: {
|
||||
// enabled: 0
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('.tagify-input').forEach((input) => {
|
||||
if (!input.tagify) {
|
||||
// Get initial values from data attribute or value attribute
|
||||
let initialValues = [];
|
||||
try {
|
||||
const dataValues = input.getAttribute('data-field-values');
|
||||
if (dataValues) {
|
||||
initialValues = JSON.parse(dataValues);
|
||||
} else if (input.value) {
|
||||
initialValues = JSON.parse(input.value);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error parsing initial values:', e);
|
||||
}
|
||||
|
||||
// Ensure all values including 0 are converted to strings
|
||||
initialValues = initialValues.map(v => {
|
||||
// Preserve 0, false, empty string, etc. as strings
|
||||
return v !== null && v !== undefined ? String(v) : '';
|
||||
}).filter(v => v !== ''); // Only filter out truly empty strings
|
||||
|
||||
// Initialize Tagify with proper configuration
|
||||
const tagify = new Tagify(input, {
|
||||
delimiters: ",",
|
||||
editTags: true,
|
||||
duplicate: false,
|
||||
dropdown: {
|
||||
enabled: 0
|
||||
},
|
||||
// Ensure 0 and other falsy values are preserved
|
||||
transformTag: function(tagData) {
|
||||
// Convert tag value to string to preserve 0
|
||||
if (tagData.value !== null && tagData.value !== undefined) {
|
||||
tagData.value = String(tagData.value);
|
||||
}
|
||||
return tagData;
|
||||
},
|
||||
// Ensure all values including 0 are kept
|
||||
whitelist: [],
|
||||
enforceWhitelist: false,
|
||||
// Don't trim values to preserve spaces if needed
|
||||
trim: false
|
||||
});
|
||||
|
||||
// Set initial values after initialization
|
||||
if (initialValues.length > 0) {
|
||||
tagify.addTags(initialValues);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
@endsection
|
||||
92
resources/views/custom-fields/index.blade.php
Normal file
92
resources/views/custom-fields/index.blade.php
Normal file
@@ -0,0 +1,92 @@
|
||||
@extends('layouts.main')
|
||||
@section('title')
|
||||
{{__("Custom Fields")}}
|
||||
@endsection
|
||||
|
||||
@section('page-title')
|
||||
<div class="page-title">
|
||||
<div class="row d-flex align-items-center">
|
||||
<div class="col-12 col-md-6">
|
||||
<h4 class="mb-0">@yield('title')</h4>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 text-end mt-2 mt-md-0">
|
||||
<div class="d-flex flex-wrap gap-2 justify-content-end">
|
||||
@can('custom-field-create')
|
||||
<a href="{{ route('custom-fields.create', ['id' => 0]) }}" class="btn btn-primary mb-0">
|
||||
<span class="d-none d-sm-inline">+ {{__("Create Custom Field")}}</span>
|
||||
<span class="d-sm-none">+ {{__("Create")}}</span>
|
||||
</a>
|
||||
<a href="{{ route('custom-fields.bulk-upload') }}" class="btn btn-success mb-0">
|
||||
<i class="fas fa-upload"></i> <span class="d-none d-sm-inline">{{__("Bulk Upload")}}</span><span class="d-sm-none">{{__("Upload")}}</span>
|
||||
</a>
|
||||
@endcan
|
||||
@can('custom-field-update')
|
||||
<a href="{{ route('custom-fields.bulk-update') }}" class="btn btn-warning mb-0">
|
||||
<i class="fas fa-edit"></i> <span class="d-none d-sm-inline">{{__("Bulk Update")}}</span><span class="d-sm-none">{{__("Update")}}</span>
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<section class="section">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div id="filters">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="filter">{{ __("Category") }}</label>
|
||||
<select name="category" class="form-control bootstrap-table-filter-control-category_names" aria-label="category">
|
||||
<option value="">{{ __("All") }}</option>
|
||||
@include('category.dropdowntree', ['categories' => $categories])
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="type">{{ __("Type") }}</label>
|
||||
<select name="type" class="form-select form-control bootstrap-table-filter-control-type" id="type">
|
||||
<option value="">{{ __("All") }}</option>
|
||||
<option value="number">{{ __("Number Input") }}</option>
|
||||
<option value="textbox">{{ __("Text Input") }}</option>
|
||||
<option value="fileinput">{{ __("File Input") }}</option>
|
||||
<option value="radio">{{ __("Radio") }}</option>
|
||||
<option value="dropdown">{{ __("Dropdown") }}</option>
|
||||
<option value="checkbox">{{ __("Checkboxes") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="stable-borderless table-striped" aria-describedby="mydesc" id="table_list"
|
||||
data-toggle="table" data-url="{{ route('custom-fields.show',1) }}" data-click-to-select="true"
|
||||
data-side-pagination="server" data-pagination="true" data-page-list="[5, 10, 20, 50, 100, 200]"
|
||||
data-search="true" data-search-align="right" data-toolbar="#filters" data-show-columns="true"
|
||||
data-show-refresh="true" data-fixed-columns="true" data-fixed-number="1" data-fixed-right-number="1"
|
||||
data-trim-on-search="false" data-responsive="true" data-sort-name="id" data-sort-order="desc"
|
||||
data-pagination-successively-size="3"
|
||||
data-escape="true"
|
||||
data-show-export="true" data-export-options='{"fileName": "custom-field-list","ignoreColumn": ["operate"]}' data-export-types="['pdf','json', 'xml', 'csv', 'txt', 'sql', 'doc', 'excel']"
|
||||
data-mobile-responsive="true" data-filter-control="true" data-filter-control-container="#filters">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col" data-field="state" data-checkbox="true"></th>
|
||||
<th scope="col" data-field="id" data-align="center" data-sortable="true">{{ __('ID') }}</th>
|
||||
<th scope="col" data-field="image" data-align="center" data-formatter="imageFormatter">{{ __('Image') }}</th>
|
||||
<th scope="col" data-field="name" data-align="center" data-escape="true" data-sortable="true">{{ __('Name') }}</th>
|
||||
<th scope="col" data-field="category_names" data-align="center" data-filter-name="category_id" data-filter-control="select" data-filter-data="">{{ __('Category') }}</th>
|
||||
<th scope="col" data-field="type" data-align="center" data-sortable="true" data-filter-name="type" data-filter-control="select" data-filter-data="">{{ __('Type') }}</th>
|
||||
@canany(['custom-field-update','custom-field-delete'])
|
||||
<th scope="col" data-field="operate" data-escape="false" data-sortable="false">{{ __('Action') }}</th>
|
||||
@endcanany
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user