classify admin
This commit is contained in:
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
|
||||
|
||||
Reference in New Issue
Block a user