Added Editing of plates, items, configurations

This commit is contained in:
2026-05-26 17:15:36 +02:00
parent 66543b75d4
commit be58c5941d
6 changed files with 341 additions and 40 deletions
+173 -39
View File
@@ -234,7 +234,7 @@
</section>
<section class="card wide">
<h2>Platte anlegen</h2>
<h2 x-text="plateEditingId ? 'Platte bearbeiten' : 'Platte anlegen'"></h2>
<form @submit.prevent="savePlate()">
<div class="form-grid">
<div>
@@ -266,9 +266,12 @@
<input type="number" step="0.1" min="0" x-model.number="plateForm.margin_left_mm">
</div>
</div>
<button type="submit" :disabled="plateSaving">
<span x-text="plateSaving ? 'Speichere…' : 'Platte anlegen'"></span>
</button>
<div class="btn-row">
<button type="submit" :disabled="plateSaving">
<span x-text="plateSaving ? 'Speichere…' : (plateEditingId ? 'Änderungen speichern' : 'Platte anlegen')"></span>
</button>
<button type="button" class="secondary" x-show="plateEditingId" @click="cancelPlateEdit()">Abbrechen</button>
</div>
<p class="msg-err" x-show="plateError" x-text="plateError"></p>
<p class="msg-ok" x-show="plateSuccess" x-text="plateSuccess"></p>
</form>
@@ -297,6 +300,7 @@
<td x-text="fmtMargins(p)"></td>
<td x-text="fmtPrintable(p)"></td>
<td class="row-actions">
<button type="button" class="secondary" @click="editPlate(p)">Bearbeiten</button>
<button type="button" class="danger" @click="deletePlate(p.id)">Löschen</button>
</td>
</tr>
@@ -306,13 +310,14 @@
</section>
<section class="card wide">
<h2>Item anlegen</h2>
<p class="muted">Erzeugt SVG-Maske in <code>data/svg_template/</code>.</p>
<h2 x-text="itemEditingId ? 'Item bearbeiten' : 'Item anlegen'"></h2>
<p class="muted" x-show="!itemEditingId">Erzeugt SVG-Maske in <code>data/svg_template/</code>.</p>
<p class="muted" x-show="itemEditingId">Aktualisiert SVG und Metadaten; Dateiname bleibt unverändert.</p>
<form @submit.prevent="saveItem()">
<div class="form-grid">
<div>
<label>Name</label>
<input type="text" x-model="itemForm.name" placeholder="z.B. sticker_80" required>
<input type="text" x-model="itemForm.name" placeholder="z.B. sticker_80" :required="!itemEditingId">
</div>
<div>
<label>Größe (mm)</label>
@@ -331,16 +336,19 @@
<input type="number" step="0.1" min="0" x-model.number="itemForm.padding_mm">
</div>
</div>
<button type="submit" :disabled="itemSaving">
<span x-text="itemSaving ? 'Erzeuge…' : 'Item anlegen'"></span>
</button>
<div class="btn-row">
<button type="submit" :disabled="itemSaving">
<span x-text="itemSaving ? 'Speichere…' : (itemEditingId ? 'Änderungen speichern' : 'Item anlegen')"></span>
</button>
<button type="button" class="secondary" x-show="itemEditingId" @click="cancelItemEdit()">Abbrechen</button>
</div>
<p class="msg-err" x-show="itemError" x-text="itemError"></p>
<p class="msg-ok" x-show="itemSuccess" x-text="itemSuccess"></p>
</form>
</section>
<section class="card wide">
<h2>Konfiguration — Layout-Vorschau</h2>
<h2 x-text="configEditingId ? 'Konfiguration bearbeiten' : 'Konfiguration — Layout-Vorschau'"></h2>
<p class="muted">Kombiniert Platte und Item; berechnet maximale Stückzahl unter Einhaltung aller Margins und Abstände.</p>
<form @submit.prevent="saveConfiguration()">
<div class="form-grid">
@@ -373,8 +381,9 @@
</div>
<div class="btn-row">
<button type="submit" :disabled="configSaving || !configForm.plate_id || !configForm.item_id">
<span x-text="configSaving ? 'Speichere…' : 'Konfiguration speichern'"></span>
<span x-text="configSaving ? 'Speichere…' : (configEditingId ? 'Änderungen speichern' : 'Konfiguration speichern')"></span>
</button>
<button type="button" class="secondary" x-show="configEditingId" @click="cancelConfigEdit()">Abbrechen</button>
<button type="button" class="secondary" @click="downloadLayoutPDF()"
:disabled="pdfGenerating || !layoutPreview || !configForm.plate_id || !configForm.item_id">
<span x-text="pdfGenerating ? 'PDF…' : 'PDF-Vorschau'"></span>
@@ -413,6 +422,7 @@
<p class="muted" x-text="configSummary(c)"></p>
</div>
<div class="row-actions">
<button type="button" class="secondary" @click="editConfiguration(c)">Bearbeiten</button>
<button type="button" class="secondary" @click="downloadConfigurationPDF(c.id)"
:disabled="pdfGenerating || !!c.preview_error" x-show="c.preview && !c.preview_error">
PDF
@@ -449,7 +459,8 @@
<span x-text="it.spec.size_mm + ' mm · bleed ' + it.spec.bleed_mm + ' · margin ' + it.spec.margin_mm"></span>
<span><code x-text="it.svg_template"></code></span>
</div>
<div class="item-actions">
<div class="item-actions row-actions">
<button type="button" class="secondary" @click="editItem(it)">Bearbeiten</button>
<button type="button" class="danger" @click="deleteItem(it.id)">Löschen</button>
</div>
</article>
@@ -470,6 +481,7 @@
plateSaving: false,
plateError: '',
plateSuccess: '',
plateEditingId: null,
plateForm: {
name: '',
width_mm: 300,
@@ -485,6 +497,7 @@
itemSaving: false,
itemError: '',
itemSuccess: '',
itemEditingId: null,
itemForm: {
name: '',
size_mm: 80,
@@ -494,6 +507,7 @@
},
configurations: [],
configEditingId: null,
configSaving: false,
configError: '',
configSuccess: '',
@@ -576,21 +590,62 @@
}
},
defaultPlateForm() {
return {
name: '',
width_mm: 300,
height_mm: 400,
margin_top_mm: 10,
margin_right_mm: 10,
margin_bottom_mm: 10,
margin_left_mm: 10,
};
},
editPlate(p) {
this.plateEditingId = p.id;
this.plateForm = {
name: p.name || '',
width_mm: p.width_mm,
height_mm: p.height_mm,
margin_top_mm: p.margin_top_mm,
margin_right_mm: p.margin_right_mm,
margin_bottom_mm: p.margin_bottom_mm,
margin_left_mm: p.margin_left_mm,
};
this.plateError = '';
this.plateSuccess = '';
},
cancelPlateEdit() {
this.plateEditingId = null;
this.plateForm = this.defaultPlateForm();
this.plateError = '';
this.plateSuccess = '';
},
async savePlate() {
this.plateSaving = true;
this.plateError = '';
this.plateSuccess = '';
const body = {
name: this.plateForm.name,
width_mm: Number(this.plateForm.width_mm),
height_mm: Number(this.plateForm.height_mm),
margin_top_mm: Number(this.plateForm.margin_top_mm) || 0,
margin_right_mm: Number(this.plateForm.margin_right_mm) || 0,
margin_bottom_mm: Number(this.plateForm.margin_bottom_mm) || 0,
margin_left_mm: Number(this.plateForm.margin_left_mm) || 0,
};
try {
await this.apiJSON('POST', '/plates', {
name: this.plateForm.name,
width_mm: Number(this.plateForm.width_mm),
height_mm: Number(this.plateForm.height_mm),
margin_top_mm: Number(this.plateForm.margin_top_mm) || 0,
margin_right_mm: Number(this.plateForm.margin_right_mm) || 0,
margin_bottom_mm: Number(this.plateForm.margin_bottom_mm) || 0,
margin_left_mm: Number(this.plateForm.margin_left_mm) || 0,
});
this.plateSuccess = 'Platte gespeichert.';
if (this.plateEditingId) {
await this.apiJSON('PUT', '/plates/' + this.plateEditingId, body);
this.plateSuccess = 'Platte aktualisiert.';
} else {
await this.apiJSON('POST', '/plates', body);
this.plateSuccess = 'Platte gespeichert.';
}
this.cancelPlateEdit();
await this.loadPlates();
this.refreshLayoutPreview();
} catch (e) {
@@ -644,6 +699,7 @@
throw new Error(text || res.statusText);
}
await Promise.all([this.loadPlates(), this.loadConfigurations()]);
if (this.plateEditingId === id) this.cancelPlateEdit();
if (this.configForm.plate_id === id) {
this.configForm.plate_id = '';
this.layoutPreview = null;
@@ -664,20 +720,59 @@
}
},
defaultItemForm() {
return {
name: '',
size_mm: 80,
bleed_mm: 2,
margin_mm: 5,
padding_mm: 3,
};
},
editItem(it) {
this.itemEditingId = it.id;
this.itemForm = {
name: it.name || '',
size_mm: it.spec.size_mm,
bleed_mm: it.spec.bleed_mm,
margin_mm: it.spec.margin_mm,
padding_mm: it.spec.padding_mm,
};
this.itemError = '';
this.itemSuccess = '';
},
cancelItemEdit() {
this.itemEditingId = null;
this.itemForm = this.defaultItemForm();
this.itemError = '';
this.itemSuccess = '';
},
async saveItem() {
this.itemSaving = true;
this.itemError = '';
this.itemSuccess = '';
const body = {
name: this.itemForm.name,
size_mm: Number(this.itemForm.size_mm),
bleed_mm: Number(this.itemForm.bleed_mm) || 0,
margin_mm: Number(this.itemForm.margin_mm) || 0,
padding_mm: Number(this.itemForm.padding_mm) || 0,
};
try {
await this.apiJSON('POST', '/items', {
name: this.itemForm.name,
size_mm: Number(this.itemForm.size_mm),
bleed_mm: Number(this.itemForm.bleed_mm) || 0,
margin_mm: Number(this.itemForm.margin_mm) || 0,
padding_mm: Number(this.itemForm.padding_mm) || 0,
});
this.itemSuccess = 'Item erzeugt.';
this.itemForm.name = '';
if (this.itemEditingId) {
await this.apiJSON('PUT', '/items/' + this.itemEditingId, body);
this.itemSuccess = 'Item aktualisiert.';
} else {
if (!body.name) {
throw new Error('Name ist erforderlich');
}
await this.apiJSON('POST', '/items', body);
this.itemSuccess = 'Item erzeugt.';
}
this.cancelItemEdit();
await this.loadItems();
this.refreshLayoutPreview();
} catch (e) {
@@ -697,6 +792,7 @@
throw new Error(text || res.statusText);
}
await Promise.all([this.loadItems(), this.loadConfigurations()]);
if (this.itemEditingId === id) this.cancelItemEdit();
if (this.configForm.item_id === id) {
this.configForm.item_id = '';
this.layoutPreview = null;
@@ -714,18 +810,55 @@
}
},
defaultConfigForm() {
return {
name: '',
plate_id: '',
item_id: '',
spacing_mm: 2,
};
},
editConfiguration(c) {
this.configEditingId = c.id;
this.configForm = {
name: c.name || '',
plate_id: c.plate_id,
item_id: c.item_id,
spacing_mm: c.spacing_mm,
};
this.configError = '';
this.configSuccess = '';
this.refreshLayoutPreview();
},
cancelConfigEdit() {
this.configEditingId = null;
this.configForm = this.defaultConfigForm();
this.layoutPreview = null;
this.configError = '';
this.configSuccess = '';
},
async saveConfiguration() {
this.configSaving = true;
this.configError = '';
this.configSuccess = '';
const body = {
name: this.configForm.name,
plate_id: this.configForm.plate_id,
item_id: this.configForm.item_id,
spacing_mm: Number(this.configForm.spacing_mm) || 0,
};
try {
await this.apiJSON('POST', '/configurations', {
name: this.configForm.name,
plate_id: this.configForm.plate_id,
item_id: this.configForm.item_id,
spacing_mm: Number(this.configForm.spacing_mm) || 0,
});
this.configSuccess = 'Konfiguration gespeichert.';
if (this.configEditingId) {
await this.apiJSON('PUT', '/configurations/' + this.configEditingId, body);
this.configSuccess = 'Konfiguration aktualisiert.';
} else {
await this.apiJSON('POST', '/configurations', body);
this.configSuccess = 'Konfiguration gespeichert.';
}
this.cancelConfigEdit();
await this.loadConfigurations();
} catch (e) {
this.configError = String(e.message || e);
@@ -784,6 +917,7 @@
throw new Error(text || res.statusText);
}
await this.loadConfigurations();
if (this.configEditingId === id) this.cancelConfigEdit();
} catch (e) {
this.configError = String(e.message || e);
}