Added Meta Data to Orders and made the frontend english
This commit is contained in:
@@ -331,6 +331,33 @@
|
||||
padding: 0.5rem;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
.form-check-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem 1.5rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
.form-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
margin-top: 0;
|
||||
}
|
||||
.form-check input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
.ref-code {
|
||||
margin-top: 0.35rem;
|
||||
font-size: 0.82rem;
|
||||
font-family: ui-monospace, monospace;
|
||||
color: var(--muted);
|
||||
word-break: break-all;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.app { flex-direction: column; }
|
||||
.sidebar {
|
||||
@@ -430,7 +457,7 @@
|
||||
</button>
|
||||
<div class="nav-children" x-show="expanded.orders">
|
||||
<template x-if="orders.length === 0 && !ordersLoading">
|
||||
<p class="nav-empty">Keine Orders</p>
|
||||
<p class="nav-empty">No orders</p>
|
||||
</template>
|
||||
<template x-for="o in orders" :key="o.id">
|
||||
<button type="button" class="nav-item"
|
||||
@@ -440,7 +467,7 @@
|
||||
</template>
|
||||
<button type="button" class="nav-item add"
|
||||
:class="{ selected: section === 'orders' && !orderEditingId }"
|
||||
@click="newOrder()">+ Neu</button>
|
||||
@click="newOrder()">+ New</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -630,57 +657,72 @@
|
||||
<!-- Orders -->
|
||||
<div x-show="section === 'orders'">
|
||||
<div class="main-header">
|
||||
<h2 x-text="orderEditingId ? 'Order bearbeiten' : 'Neue Order'"></h2>
|
||||
<p class="muted" x-show="!orderEditingId">Order anlegen, danach Bilder hochladen.</p>
|
||||
<p class="muted" x-show="orderEditingId">Beliebig viele Bilder pro Order (PNG, JPEG, WebP, GIF).</p>
|
||||
<h2 x-text="orderEditingId ? 'Edit order' : 'New order'"></h2>
|
||||
<p class="muted" x-show="!orderEditingId">Create an order, then upload images.</p>
|
||||
<p class="muted" x-show="orderEditingId">Any number of images per order (PNG, JPEG, WebP, GIF).</p>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<form @submit.prevent="saveOrder()">
|
||||
<div class="form-grid">
|
||||
<div>
|
||||
<label>Name (optional)</label>
|
||||
<input type="text" x-model="orderForm.name" placeholder="z.B. Kunde Müller">
|
||||
<input type="text" x-model="orderForm.name" placeholder="e.g. Customer Smith">
|
||||
</div>
|
||||
<div>
|
||||
<label>Reference code</label>
|
||||
<input type="text" x-model="orderForm.ref" placeholder="e.g. SHOP-10482" autocomplete="off" spellcheck="false">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check-row">
|
||||
<label class="form-check">
|
||||
<input type="checkbox" x-model="orderForm.printed">
|
||||
Printed
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input type="checkbox" x-model="orderForm.shipped">
|
||||
Shipped
|
||||
</label>
|
||||
</div>
|
||||
<p class="ref-code" x-show="orderForm.ref" x-text="'Ref: ' + orderForm.ref"></p>
|
||||
<div class="btn-row">
|
||||
<button type="submit" :disabled="orderSaving">
|
||||
<span x-text="orderSaving ? 'Speichere…' : (orderEditingId ? 'Speichern' : 'Anlegen')"></span>
|
||||
<span x-text="orderSaving ? 'Saving…' : (orderEditingId ? 'Save' : 'Create')"></span>
|
||||
</button>
|
||||
<button type="button" class="danger" x-show="orderEditingId" @click="deleteOrder(orderEditingId)">Löschen</button>
|
||||
<button type="button" class="danger" x-show="orderEditingId" @click="deleteOrder(orderEditingId)">Delete</button>
|
||||
</div>
|
||||
<p class="msg-err" x-show="orderError" x-text="orderError"></p>
|
||||
<p class="msg-ok" x-show="orderSuccess" x-text="orderSuccess"></p>
|
||||
</form>
|
||||
|
||||
<div x-show="orderEditingId" style="margin-top: 1.5rem;">
|
||||
<label>Bilder hochladen</label>
|
||||
<label>Upload images</label>
|
||||
<input type="file" class="file-input" accept="image/*" multiple
|
||||
:disabled="orderImageUploading || orderLoading"
|
||||
@change="uploadOrderImages($event)">
|
||||
<p class="muted" x-show="orderImageUploading">Lade hoch…</p>
|
||||
<p class="muted" x-show="orderLoading">Lade Bilder…</p>
|
||||
<p class="muted" x-show="orderImageUploading">Uploading…</p>
|
||||
<p class="muted" x-show="orderLoading">Loading images…</p>
|
||||
|
||||
<div class="order-images" x-show="orderImages.length > 0">
|
||||
<template x-for="img in orderImages" :key="img.id">
|
||||
<div class="order-image-card">
|
||||
<button type="button" class="order-image-thumb"
|
||||
@click="openOrderImage(img)"
|
||||
:title="(img.original_name || img.filename) + ' — Klicken für Vollbild'">
|
||||
:title="(img.original_name || img.filename) + ' — click for full size'">
|
||||
<img :src="orderImageUrl(orderEditingId, img.id)"
|
||||
:alt="img.original_name || 'Bild'"
|
||||
:alt="img.original_name || 'Image'"
|
||||
loading="lazy"
|
||||
@error="onOrderImageError($event, img)">
|
||||
</button>
|
||||
<p class="order-image-meta" x-text="img.original_name || img.filename"></p>
|
||||
<div class="order-image-actions">
|
||||
<button type="button" class="secondary" @click="openOrderImage(img)">Vergrößern</button>
|
||||
<button type="button" class="danger" @click="deleteOrderImage(img.id)">Entfernen</button>
|
||||
<button type="button" class="secondary" @click="openOrderImage(img)">Enlarge</button>
|
||||
<button type="button" class="danger" @click="deleteOrderImage(img.id)">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<p class="muted" x-show="orderImages.length === 0 && !orderImageUploading && !orderLoading" style="margin-top: 1rem;">
|
||||
Noch keine Bilder — Dateien oben auswählen.
|
||||
No images yet — choose files above.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -694,7 +736,7 @@
|
||||
<div @click.stop style="display: contents;">
|
||||
<img :src="orderImageUrl(orderLightbox.orderId, orderLightbox.imageId)" :alt="orderLightbox.label">
|
||||
<p class="order-lightbox-caption" x-text="orderLightbox.label"></p>
|
||||
<p class="order-lightbox-hint">Klicken oder Esc zum Schließen</p>
|
||||
<p class="order-lightbox-hint">Click or press Esc to close</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -765,7 +807,7 @@
|
||||
orderSuccess: '',
|
||||
orderEditingId: null,
|
||||
orderImages: [],
|
||||
orderForm: { name: '' },
|
||||
orderForm: { name: '', printed: false, shipped: false, ref: '' },
|
||||
orderLoading: false,
|
||||
orderImageVersion: 0,
|
||||
orderLightbox: null,
|
||||
@@ -837,9 +879,23 @@
|
||||
},
|
||||
|
||||
sidebarOrderLabel(o) {
|
||||
const n = o.name || ('Order ' + o.id.slice(0, 8));
|
||||
const n = o.name || o.ref || ('Order ' + o.id.slice(0, 8));
|
||||
const parts = [];
|
||||
if (o.name && o.ref) parts.push(o.ref);
|
||||
if (o.printed) parts.push('printed');
|
||||
if (o.shipped) parts.push('shipped');
|
||||
const count = (o.images || []).length;
|
||||
return n + (count ? ' · ' + count + ' Bild' + (count === 1 ? '' : 'er') : '');
|
||||
if (count) parts.push(count + ' img' + (count === 1 ? '' : 's'));
|
||||
return parts.length ? n + ' · ' + parts.join(' · ') : n;
|
||||
},
|
||||
|
||||
orderFormFrom(o) {
|
||||
return {
|
||||
name: o.name || '',
|
||||
printed: !!o.printed,
|
||||
shipped: !!o.shipped,
|
||||
ref: o.ref || o.ref_link || '',
|
||||
};
|
||||
},
|
||||
|
||||
apiUrl(path) {
|
||||
@@ -859,7 +915,7 @@
|
||||
this.orderLightbox = {
|
||||
orderId: this.orderEditingId,
|
||||
imageId: img.id,
|
||||
label: img.original_name || img.filename || 'Bild',
|
||||
label: img.original_name || img.filename || 'Image',
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1326,14 +1382,14 @@
|
||||
|
||||
async editOrder(o) {
|
||||
this.orderEditingId = o.id;
|
||||
this.orderForm = { name: o.name || '' };
|
||||
this.orderForm = this.orderFormFrom(o);
|
||||
this.orderImages = o.images ? [...o.images] : [];
|
||||
this.orderError = '';
|
||||
this.orderSuccess = '';
|
||||
this.orderLoading = true;
|
||||
try {
|
||||
const fresh = await this.apiJSON('GET', '/orders/' + o.id);
|
||||
this.orderForm = { name: fresh.name || '' };
|
||||
this.orderForm = this.orderFormFrom(fresh);
|
||||
this.orderImages = fresh.images ? [...fresh.images] : [];
|
||||
this.orderImageVersion++;
|
||||
} catch (e) {
|
||||
@@ -1345,7 +1401,7 @@
|
||||
|
||||
cancelOrderEdit() {
|
||||
this.orderEditingId = null;
|
||||
this.orderForm = { name: '' };
|
||||
this.orderForm = { name: '', printed: false, shipped: false, ref: '' };
|
||||
this.orderImages = [];
|
||||
this.orderLightbox = null;
|
||||
this.orderError = '';
|
||||
@@ -1356,15 +1412,20 @@
|
||||
this.orderSaving = true;
|
||||
this.orderError = '';
|
||||
this.orderSuccess = '';
|
||||
const body = { name: this.orderForm.name };
|
||||
const body = {
|
||||
name: this.orderForm.name,
|
||||
printed: !!this.orderForm.printed,
|
||||
shipped: !!this.orderForm.shipped,
|
||||
ref: (this.orderForm.ref || '').trim(),
|
||||
};
|
||||
try {
|
||||
let saved;
|
||||
if (this.orderEditingId) {
|
||||
saved = await this.apiJSON('PUT', '/orders/' + this.orderEditingId, body);
|
||||
this.orderSuccess = 'Order aktualisiert.';
|
||||
this.orderSuccess = 'Order updated.';
|
||||
} else {
|
||||
saved = await this.apiJSON('POST', '/orders', body);
|
||||
this.orderSuccess = 'Order angelegt.';
|
||||
this.orderSuccess = 'Order created.';
|
||||
}
|
||||
const keepId = saved?.id || this.orderEditingId;
|
||||
await this.loadOrders();
|
||||
@@ -1419,7 +1480,7 @@
|
||||
if (o) await this.editOrder(o);
|
||||
}
|
||||
const n = (result?.added || []).length;
|
||||
this.orderSuccess = n === 1 ? '1 Bild hochgeladen.' : n + ' Bilder hochgeladen.';
|
||||
this.orderSuccess = n === 1 ? '1 image uploaded.' : n + ' images uploaded.';
|
||||
} catch (e) {
|
||||
this.orderError = String(e.message || e);
|
||||
} finally {
|
||||
@@ -1430,7 +1491,7 @@
|
||||
|
||||
async deleteOrderImage(imageId) {
|
||||
if (!this.orderEditingId) return;
|
||||
if (!confirm('Bild wirklich entfernen?')) return;
|
||||
if (!confirm('Remove this image?')) return;
|
||||
this.orderError = '';
|
||||
try {
|
||||
const res = await fetch(
|
||||
@@ -1445,14 +1506,14 @@
|
||||
await this.loadOrders();
|
||||
const o = this.orders.find(x => x.id === this.orderEditingId);
|
||||
if (o) await this.editOrder(o);
|
||||
this.orderSuccess = 'Bild entfernt.';
|
||||
this.orderSuccess = 'Image removed.';
|
||||
} catch (e) {
|
||||
this.orderError = String(e.message || e);
|
||||
}
|
||||
},
|
||||
|
||||
async deleteOrder(id) {
|
||||
if (!confirm('Order inkl. aller Bilder wirklich löschen?')) return;
|
||||
if (!confirm('Delete this order and all images?')) return;
|
||||
this.orderError = '';
|
||||
try {
|
||||
const res = await fetch(this.apiUrl('/orders/' + id), { method: 'DELETE' });
|
||||
|
||||
Reference in New Issue
Block a user