Add core admin dashboard files (HTML, CSS, JS)
This commit is contained in:
parent
889578f180
commit
4d590fd822
9 changed files with 4663 additions and 0 deletions
338
admin-dashboard/src/js/imageManager.js
Normal file
338
admin-dashboard/src/js/imageManager.js
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
// Image Management Class
|
||||
class ImageManager {
|
||||
constructor(dashboard) {
|
||||
this.dashboard = dashboard;
|
||||
this.images = [];
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Upload images button
|
||||
document
|
||||
.getElementById("upload-images-btn")
|
||||
.addEventListener("click", () => {
|
||||
this.showUploadModal();
|
||||
});
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
try {
|
||||
// In a real implementation, this would fetch from a server
|
||||
// For now, we'll collect images from products
|
||||
this.collectImagesFromProducts();
|
||||
} catch (error) {
|
||||
console.error("Error loading image data:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
collectImagesFromProducts() {
|
||||
this.images = [];
|
||||
const products = this.dashboard.productManager.products;
|
||||
|
||||
products.forEach((product) => {
|
||||
if (product.images && Array.isArray(product.images)) {
|
||||
product.images.forEach((imagePath) => {
|
||||
if (!this.images.find((img) => img.path === imagePath)) {
|
||||
this.images.push({
|
||||
path: imagePath,
|
||||
name: this.extractFileName(imagePath),
|
||||
productId: product.id,
|
||||
productName: product.name,
|
||||
size: "Unknown", // Would be fetched from server
|
||||
uploadDate: "Unknown", // Would be fetched from server
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
extractFileName(path) {
|
||||
return path.split("/").pop();
|
||||
}
|
||||
|
||||
renderImages() {
|
||||
const container = document.getElementById("image-manager");
|
||||
|
||||
if (this.images.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="no-images">
|
||||
<h3>No images found</h3>
|
||||
<p>Upload images to get started!</p>
|
||||
<button class="btn btn-primary" onclick="imageManager.showUploadModal()">Upload Images</button>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = "";
|
||||
this.images.forEach((image) => {
|
||||
const imageItem = this.createImageItem(image);
|
||||
container.appendChild(imageItem);
|
||||
});
|
||||
}
|
||||
|
||||
createImageItem(image) {
|
||||
const item = document.createElement("div");
|
||||
item.className = "image-item";
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="image-preview">
|
||||
<img src="${image.path}" alt="${image.name}" onerror="this.src='data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"200\" height=\"150\" viewBox=\"0 0 200 150\"><rect width=\"200\" height=\"150\" fill=\"%23f0f0f0\"/><text x=\"50%\" y=\"50%\" text-anchor=\"middle\" dy=\".3em\" fill=\"%23999\">Image Not Found</text></svg>'">
|
||||
</div>
|
||||
<div class="image-item-info">
|
||||
<div class="image-item-name" title="${image.name}">${image.name}</div>
|
||||
<div class="image-meta">
|
||||
<span class="image-product">Product: ${image.productName}</span>
|
||||
<span class="image-size">Size: ${image.size}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-item-actions">
|
||||
<button class="btn btn-sm btn-primary" onclick="imageManager.viewImage('${image.path}')">
|
||||
👁️ View
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="imageManager.renameImage('${image.path}')">
|
||||
✏️ Rename
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="imageManager.deleteImage('${image.path}')">
|
||||
🗑️ Delete
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
return item;
|
||||
}
|
||||
|
||||
showUploadModal() {
|
||||
// Create upload modal if it doesn't exist
|
||||
let uploadModal = document.getElementById("upload-modal");
|
||||
if (!uploadModal) {
|
||||
uploadModal = this.createUploadModal();
|
||||
document.body.appendChild(uploadModal);
|
||||
}
|
||||
|
||||
uploadModal.style.display = "block";
|
||||
}
|
||||
|
||||
createUploadModal() {
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "upload-modal";
|
||||
modal.className = "modal";
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>Upload Images</h3>
|
||||
<span class="close" onclick="this.closest('.modal').style.display='none'">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="upload-area" id="upload-area">
|
||||
<div class="upload-content">
|
||||
<div class="upload-icon">📤</div>
|
||||
<h4>Drag & Drop images here</h4>
|
||||
<p>or click to select files</p>
|
||||
<input type="file" id="file-input" multiple accept="image/*" style="display: none;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload-preview" id="upload-preview"></div>
|
||||
<div class="upload-actions">
|
||||
<button class="btn btn-secondary" onclick="this.closest('.modal').style.display='none'">Cancel</button>
|
||||
<button class="btn btn-primary" onclick="imageManager.processUpload()">Upload Images</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Setup drag and drop
|
||||
const uploadArea = modal.querySelector("#upload-area");
|
||||
const fileInput = modal.querySelector("#file-input");
|
||||
|
||||
uploadArea.addEventListener("click", () => fileInput.click());
|
||||
uploadArea.addEventListener("dragover", (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.classList.add("drag-over");
|
||||
});
|
||||
uploadArea.addEventListener("dragleave", () => {
|
||||
uploadArea.classList.remove("drag-over");
|
||||
});
|
||||
uploadArea.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.classList.remove("drag-over");
|
||||
this.handleFileSelect(e.dataTransfer.files);
|
||||
});
|
||||
|
||||
fileInput.addEventListener("change", (e) => {
|
||||
this.handleFileSelect(e.target.files);
|
||||
});
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
handleFileSelect(files) {
|
||||
const preview = document.getElementById("upload-preview");
|
||||
preview.innerHTML = "";
|
||||
|
||||
Array.from(files).forEach((file) => {
|
||||
if (file.type.startsWith("image/")) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const imageDiv = document.createElement("div");
|
||||
imageDiv.className = "upload-preview-item";
|
||||
imageDiv.innerHTML = `
|
||||
<img src="${e.target.result}" alt="${file.name}">
|
||||
<div class="upload-preview-info">
|
||||
<span class="upload-filename">${file.name}</span>
|
||||
<span class="upload-filesize">${this.formatFileSize(
|
||||
file.size
|
||||
)}</span>
|
||||
</div>
|
||||
<button class="remove-upload-btn" onclick="this.parentElement.remove()">×</button>
|
||||
`;
|
||||
preview.appendChild(imageDiv);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
formatFileSize(bytes) {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
const k = 1024;
|
||||
const sizes = ["Bytes", "KB", "MB", "GB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
||||
}
|
||||
|
||||
async processUpload() {
|
||||
const fileInput = document.getElementById("file-input");
|
||||
const files = fileInput.files;
|
||||
|
||||
if (files.length === 0) {
|
||||
this.dashboard.showMessage("Please select files to upload", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.dashboard.showMessage("Uploading images...", "info");
|
||||
|
||||
const uploadedImages = await this.uploadImages(files);
|
||||
|
||||
// Add to images list
|
||||
uploadedImages.forEach((imagePath) => {
|
||||
const fileName = this.extractFileName(imagePath);
|
||||
this.images.push({
|
||||
path: imagePath,
|
||||
name: fileName,
|
||||
productId: null,
|
||||
productName: "Unassigned",
|
||||
size: "Unknown",
|
||||
uploadDate: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
this.renderImages();
|
||||
document.getElementById("upload-modal").style.display = "none";
|
||||
this.dashboard.showMessage(
|
||||
`Successfully uploaded ${uploadedImages.length} image(s)!`,
|
||||
"success"
|
||||
);
|
||||
} catch (error) {
|
||||
this.dashboard.showMessage(
|
||||
"Error uploading images: " + error.message,
|
||||
"error"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async uploadImages(files) {
|
||||
const uploadedImages = [];
|
||||
|
||||
for (const file of files) {
|
||||
if (file.type.startsWith("image/")) {
|
||||
// In a real implementation, you'd upload to a server
|
||||
const filename = `uploaded_${Date.now()}_${file.name}`;
|
||||
const imagePath = `assets/images/products/${filename}`;
|
||||
uploadedImages.push(imagePath);
|
||||
|
||||
console.log(`Would upload: ${file.name} to ${imagePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
return uploadedImages;
|
||||
}
|
||||
|
||||
viewImage(imagePath) {
|
||||
// Create image viewer modal
|
||||
const viewer = document.createElement("div");
|
||||
viewer.className = "modal image-viewer";
|
||||
viewer.innerHTML = `
|
||||
<div class="modal-content image-viewer-content">
|
||||
<div class="modal-header">
|
||||
<h3>Image Viewer</h3>
|
||||
<span class="close" onclick="this.closest('.modal').remove()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<img src="${imagePath}" alt="Full size image" style="max-width: 100%; max-height: 70vh; object-fit: contain;">
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(viewer);
|
||||
viewer.style.display = "block";
|
||||
}
|
||||
|
||||
renameImage(imagePath) {
|
||||
const currentName = this.extractFileName(imagePath);
|
||||
const newName = prompt("Enter new name for the image:", currentName);
|
||||
|
||||
if (newName && newName !== currentName) {
|
||||
// In a real implementation, you'd rename the file on the server
|
||||
console.log(`Would rename ${imagePath} to ${newName}`);
|
||||
this.dashboard.showMessage(
|
||||
"Image rename functionality would be implemented here",
|
||||
"info"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteImage(imagePath) {
|
||||
if (
|
||||
confirm(
|
||||
"Are you sure you want to delete this image? This action cannot be undone."
|
||||
)
|
||||
) {
|
||||
try {
|
||||
// Check if image is used by any products
|
||||
const productsUsingImage =
|
||||
this.dashboard.productManager.products.filter(
|
||||
(product) => product.images && product.images.includes(imagePath)
|
||||
);
|
||||
|
||||
if (productsUsingImage.length > 0) {
|
||||
this.dashboard.showMessage(
|
||||
`Cannot delete image because it's used by ${productsUsingImage.length} product(s). Please remove it from the products first.`,
|
||||
"error"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from images list
|
||||
this.images = this.images.filter((img) => img.path !== imagePath);
|
||||
|
||||
// In a real implementation, you'd delete from server
|
||||
console.log(`Would delete image: ${imagePath}`);
|
||||
|
||||
this.renderImages();
|
||||
this.dashboard.showMessage("Image deleted successfully!", "success");
|
||||
} catch (error) {
|
||||
this.dashboard.showMessage(
|
||||
"Error deleting image: " + error.message,
|
||||
"error"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make ImageManager available globally
|
||||
window.ImageManager = ImageManager;
|
||||
Loading…
Add table
Add a link
Reference in a new issue