diff --git a/index.html b/index.html
index 50057ea..2c823ad 100644
--- a/index.html
+++ b/index.html
@@ -127,6 +127,17 @@
.product-item {
padding: 0.25rem 0;
border-bottom: 1px solid #eee;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ }
+
+ .product-image {
+ width: 32px;
+ height: 32px;
+ object-fit: cover;
+ border-radius: 4px;
+ background: #f0f0f0;
}
.product-item:last-child {
@@ -261,7 +272,21 @@
products.slice(0, 3).forEach(p => {
const li = document.createElement('li');
li.className = 'product-item';
- li.textContent = `📦 ${p.cName}`;
+
+ // Image
+ if (p.images && p.images.length > 0) {
+ const img = document.createElement('img');
+ img.className = 'product-image';
+ img.src = `/img/prod/${p.images[0]}.avif`;
+ img.alt = p.cName;
+ img.onerror = () => img.style.display = 'none';
+ li.appendChild(img);
+ }
+
+ const span = document.createElement('span');
+ span.textContent = p.cName;
+ li.appendChild(span);
+
ul.appendChild(li);
});
diff --git a/src/server/routes/images.js b/src/server/routes/images.js
index 873a69a..4d3c4f7 100644
--- a/src/server/routes/images.js
+++ b/src/server/routes/images.js
@@ -4,13 +4,36 @@ export function registerImages(app, cacheDir) {
app.get('/img/cat/:id.avif', (req, res) => {
const { id } = req.params;
const imagePath = path.join(cacheDir, 'img', 'categories', `${id}.avif`);
+ const resolvedPath = path.resolve(imagePath);
- // Cache images for 1 year (immutable content)
- res.set('Cache-Control', 'public, max-age=31536000, immutable');
-
- res.sendFile(path.resolve(imagePath), (err) => {
+ res.sendFile(resolvedPath, {
+ headers: {
+ 'Cache-Control': 'public, max-age=31536000, immutable'
+ }
+ }, (err) => {
if (err) {
- res.status(404).send('Image not found');
+ if (!res.headersSent) {
+ res.status(404).send('Image not found');
+ }
+ }
+ });
+ });
+
+ // Product images
+ app.get('/img/prod/:id.avif', (req, res) => {
+ const { id } = req.params;
+ const imagePath = path.join(cacheDir, 'img', 'products', `${id}.avif`);
+ const resolvedPath = path.resolve(imagePath);
+ res.sendFile(resolvedPath, {
+ headers: {
+ 'Cache-Control': 'public, max-age=31536000, immutable'
+ }
+ }, (err) => {
+ if (err) {
+ console.error(`❌ Error serving image ${resolvedPath}:`, err);
+ if (!res.headersSent) {
+ res.status(404).send('Image not found');
+ }
}
});
});
diff --git a/src/syncers/category-products-syncer.js b/src/syncers/category-products-syncer.js
index 1c65bb2..4bf192e 100644
--- a/src/syncers/category-products-syncer.js
+++ b/src/syncers/category-products-syncer.js
@@ -2,6 +2,7 @@ import fs from 'fs/promises';
import path from 'path';
import { EventEmitter } from 'events';
import { createConnection } from '../utils/database.js';
+import pictureSyncer from './picture-syncer.js';
class CategoryProductsSyncer extends EventEmitter {
constructor() {
@@ -90,6 +91,8 @@ class CategoryProductsSyncer extends EventEmitter {
async _fetchAndWriteProducts(ids, dir) {
let pool;
+ const globalImageIds = new Set();
+
try {
pool = await createConnection();
@@ -116,6 +119,31 @@ class CategoryProductsSyncer extends EventEmitter {
ORDER BY a.bRowversion DESC, ab.bRowversion DESC
`);
+ // Collect all kArtikel IDs to fetch images
+ const artikelIds = new Set();
+ result.recordset.forEach(r => artikelIds.add(r.kArtikel));
+
+ // Fetch images for these articles
+ let productImages = new Map(); // kArtikel -> kBild[]
+ if (artikelIds.size > 0) {
+ const artikelList = Array.from(artikelIds).join(',');
+ const imagesResult = await pool.request().query(`
+ SELECT kArtikel, kBild
+ FROM tArtikelbildPlattform
+ WHERE kShop = ${process.env.JTL_SHOP_ID}
+ AND kPlattform = ${process.env.JTL_PLATTFORM_ID}
+ AND kArtikel IN (${artikelList})
+ ORDER BY nNr ASC
+ `);
+
+ imagesResult.recordset.forEach(r => {
+ if (!productImages.has(r.kArtikel)) {
+ productImages.set(r.kArtikel, []);
+ }
+ productImages.get(r.kArtikel).push(r.kBild);
+ });
+ }
+
// Group results by kKategorie
const productsByCategory = {};
@@ -126,9 +154,13 @@ class CategoryProductsSyncer extends EventEmitter {
for (const record of result.recordset) {
if (productsByCategory[record.kKategorie]) {
+ const images = productImages.get(record.kArtikel) || [];
+ images.forEach(imgId => globalImageIds.add(imgId));
+
productsByCategory[record.kKategorie].push({
kArtikel: record.kArtikel,
- cName: record.cName
+ cName: record.cName,
+ images: images
});
}
}
@@ -160,6 +192,13 @@ class CategoryProductsSyncer extends EventEmitter {
console.log(`⏳ Processed products for ${processed}/${ids.length} categories...`);
}
}
+
+ // Sync all collected images at once
+ if (globalImageIds.size > 0) {
+ console.log(`🖼️ Syncing ${globalImageIds.size} product images...`);
+ await pictureSyncer.syncImages(Array.from(globalImageIds), 'products');
+ }
+
} catch (err) {
console.error('❌ Error fetching products:', err);
} finally {