Added Editing of plates, items, configurations
This commit is contained in:
parent
66543b75d4
commit
be58c5941d
@ -234,7 +234,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card wide">
|
<section class="card wide">
|
||||||
<h2>Platte anlegen</h2>
|
<h2 x-text="plateEditingId ? 'Platte bearbeiten' : 'Platte anlegen'"></h2>
|
||||||
<form @submit.prevent="savePlate()">
|
<form @submit.prevent="savePlate()">
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div>
|
<div>
|
||||||
@ -266,9 +266,12 @@
|
|||||||
<input type="number" step="0.1" min="0" x-model.number="plateForm.margin_left_mm">
|
<input type="number" step="0.1" min="0" x-model.number="plateForm.margin_left_mm">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="btn-row">
|
||||||
<button type="submit" :disabled="plateSaving">
|
<button type="submit" :disabled="plateSaving">
|
||||||
<span x-text="plateSaving ? 'Speichere…' : 'Platte anlegen'"></span>
|
<span x-text="plateSaving ? 'Speichere…' : (plateEditingId ? 'Änderungen speichern' : 'Platte anlegen')"></span>
|
||||||
</button>
|
</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-err" x-show="plateError" x-text="plateError"></p>
|
||||||
<p class="msg-ok" x-show="plateSuccess" x-text="plateSuccess"></p>
|
<p class="msg-ok" x-show="plateSuccess" x-text="plateSuccess"></p>
|
||||||
</form>
|
</form>
|
||||||
@ -297,6 +300,7 @@
|
|||||||
<td x-text="fmtMargins(p)"></td>
|
<td x-text="fmtMargins(p)"></td>
|
||||||
<td x-text="fmtPrintable(p)"></td>
|
<td x-text="fmtPrintable(p)"></td>
|
||||||
<td class="row-actions">
|
<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>
|
<button type="button" class="danger" @click="deletePlate(p.id)">Löschen</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -306,13 +310,14 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card wide">
|
<section class="card wide">
|
||||||
<h2>Item anlegen</h2>
|
<h2 x-text="itemEditingId ? 'Item bearbeiten' : 'Item anlegen'"></h2>
|
||||||
<p class="muted">Erzeugt SVG-Maske in <code>data/svg_template/</code>.</p>
|
<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()">
|
<form @submit.prevent="saveItem()">
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div>
|
<div>
|
||||||
<label>Name</label>
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<label>Größe (mm)</label>
|
<label>Größe (mm)</label>
|
||||||
@ -331,16 +336,19 @@
|
|||||||
<input type="number" step="0.1" min="0" x-model.number="itemForm.padding_mm">
|
<input type="number" step="0.1" min="0" x-model.number="itemForm.padding_mm">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="btn-row">
|
||||||
<button type="submit" :disabled="itemSaving">
|
<button type="submit" :disabled="itemSaving">
|
||||||
<span x-text="itemSaving ? 'Erzeuge…' : 'Item anlegen'"></span>
|
<span x-text="itemSaving ? 'Speichere…' : (itemEditingId ? 'Änderungen speichern' : 'Item anlegen')"></span>
|
||||||
</button>
|
</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-err" x-show="itemError" x-text="itemError"></p>
|
||||||
<p class="msg-ok" x-show="itemSuccess" x-text="itemSuccess"></p>
|
<p class="msg-ok" x-show="itemSuccess" x-text="itemSuccess"></p>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card wide">
|
<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>
|
<p class="muted">Kombiniert Platte und Item; berechnet maximale Stückzahl unter Einhaltung aller Margins und Abstände.</p>
|
||||||
<form @submit.prevent="saveConfiguration()">
|
<form @submit.prevent="saveConfiguration()">
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
@ -373,8 +381,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button type="submit" :disabled="configSaving || !configForm.plate_id || !configForm.item_id">
|
<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>
|
||||||
|
<button type="button" class="secondary" x-show="configEditingId" @click="cancelConfigEdit()">Abbrechen</button>
|
||||||
<button type="button" class="secondary" @click="downloadLayoutPDF()"
|
<button type="button" class="secondary" @click="downloadLayoutPDF()"
|
||||||
:disabled="pdfGenerating || !layoutPreview || !configForm.plate_id || !configForm.item_id">
|
:disabled="pdfGenerating || !layoutPreview || !configForm.plate_id || !configForm.item_id">
|
||||||
<span x-text="pdfGenerating ? 'PDF…' : 'PDF-Vorschau'"></span>
|
<span x-text="pdfGenerating ? 'PDF…' : 'PDF-Vorschau'"></span>
|
||||||
@ -413,6 +422,7 @@
|
|||||||
<p class="muted" x-text="configSummary(c)"></p>
|
<p class="muted" x-text="configSummary(c)"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-actions">
|
<div class="row-actions">
|
||||||
|
<button type="button" class="secondary" @click="editConfiguration(c)">Bearbeiten</button>
|
||||||
<button type="button" class="secondary" @click="downloadConfigurationPDF(c.id)"
|
<button type="button" class="secondary" @click="downloadConfigurationPDF(c.id)"
|
||||||
:disabled="pdfGenerating || !!c.preview_error" x-show="c.preview && !c.preview_error">
|
:disabled="pdfGenerating || !!c.preview_error" x-show="c.preview && !c.preview_error">
|
||||||
PDF
|
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 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>
|
<span><code x-text="it.svg_template"></code></span>
|
||||||
</div>
|
</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>
|
<button type="button" class="danger" @click="deleteItem(it.id)">Löschen</button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@ -470,6 +481,7 @@
|
|||||||
plateSaving: false,
|
plateSaving: false,
|
||||||
plateError: '',
|
plateError: '',
|
||||||
plateSuccess: '',
|
plateSuccess: '',
|
||||||
|
plateEditingId: null,
|
||||||
plateForm: {
|
plateForm: {
|
||||||
name: '',
|
name: '',
|
||||||
width_mm: 300,
|
width_mm: 300,
|
||||||
@ -485,6 +497,7 @@
|
|||||||
itemSaving: false,
|
itemSaving: false,
|
||||||
itemError: '',
|
itemError: '',
|
||||||
itemSuccess: '',
|
itemSuccess: '',
|
||||||
|
itemEditingId: null,
|
||||||
itemForm: {
|
itemForm: {
|
||||||
name: '',
|
name: '',
|
||||||
size_mm: 80,
|
size_mm: 80,
|
||||||
@ -494,6 +507,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
configurations: [],
|
configurations: [],
|
||||||
|
configEditingId: null,
|
||||||
configSaving: false,
|
configSaving: false,
|
||||||
configError: '',
|
configError: '',
|
||||||
configSuccess: '',
|
configSuccess: '',
|
||||||
@ -576,12 +590,45 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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() {
|
async savePlate() {
|
||||||
this.plateSaving = true;
|
this.plateSaving = true;
|
||||||
this.plateError = '';
|
this.plateError = '';
|
||||||
this.plateSuccess = '';
|
this.plateSuccess = '';
|
||||||
try {
|
const body = {
|
||||||
await this.apiJSON('POST', '/plates', {
|
|
||||||
name: this.plateForm.name,
|
name: this.plateForm.name,
|
||||||
width_mm: Number(this.plateForm.width_mm),
|
width_mm: Number(this.plateForm.width_mm),
|
||||||
height_mm: Number(this.plateForm.height_mm),
|
height_mm: Number(this.plateForm.height_mm),
|
||||||
@ -589,8 +636,16 @@
|
|||||||
margin_right_mm: Number(this.plateForm.margin_right_mm) || 0,
|
margin_right_mm: Number(this.plateForm.margin_right_mm) || 0,
|
||||||
margin_bottom_mm: Number(this.plateForm.margin_bottom_mm) || 0,
|
margin_bottom_mm: Number(this.plateForm.margin_bottom_mm) || 0,
|
||||||
margin_left_mm: Number(this.plateForm.margin_left_mm) || 0,
|
margin_left_mm: Number(this.plateForm.margin_left_mm) || 0,
|
||||||
});
|
};
|
||||||
|
try {
|
||||||
|
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.plateSuccess = 'Platte gespeichert.';
|
||||||
|
}
|
||||||
|
this.cancelPlateEdit();
|
||||||
await this.loadPlates();
|
await this.loadPlates();
|
||||||
this.refreshLayoutPreview();
|
this.refreshLayoutPreview();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -644,6 +699,7 @@
|
|||||||
throw new Error(text || res.statusText);
|
throw new Error(text || res.statusText);
|
||||||
}
|
}
|
||||||
await Promise.all([this.loadPlates(), this.loadConfigurations()]);
|
await Promise.all([this.loadPlates(), this.loadConfigurations()]);
|
||||||
|
if (this.plateEditingId === id) this.cancelPlateEdit();
|
||||||
if (this.configForm.plate_id === id) {
|
if (this.configForm.plate_id === id) {
|
||||||
this.configForm.plate_id = '';
|
this.configForm.plate_id = '';
|
||||||
this.layoutPreview = null;
|
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() {
|
async saveItem() {
|
||||||
this.itemSaving = true;
|
this.itemSaving = true;
|
||||||
this.itemError = '';
|
this.itemError = '';
|
||||||
this.itemSuccess = '';
|
this.itemSuccess = '';
|
||||||
try {
|
const body = {
|
||||||
await this.apiJSON('POST', '/items', {
|
|
||||||
name: this.itemForm.name,
|
name: this.itemForm.name,
|
||||||
size_mm: Number(this.itemForm.size_mm),
|
size_mm: Number(this.itemForm.size_mm),
|
||||||
bleed_mm: Number(this.itemForm.bleed_mm) || 0,
|
bleed_mm: Number(this.itemForm.bleed_mm) || 0,
|
||||||
margin_mm: Number(this.itemForm.margin_mm) || 0,
|
margin_mm: Number(this.itemForm.margin_mm) || 0,
|
||||||
padding_mm: Number(this.itemForm.padding_mm) || 0,
|
padding_mm: Number(this.itemForm.padding_mm) || 0,
|
||||||
});
|
};
|
||||||
|
try {
|
||||||
|
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.itemSuccess = 'Item erzeugt.';
|
||||||
this.itemForm.name = '';
|
}
|
||||||
|
this.cancelItemEdit();
|
||||||
await this.loadItems();
|
await this.loadItems();
|
||||||
this.refreshLayoutPreview();
|
this.refreshLayoutPreview();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -697,6 +792,7 @@
|
|||||||
throw new Error(text || res.statusText);
|
throw new Error(text || res.statusText);
|
||||||
}
|
}
|
||||||
await Promise.all([this.loadItems(), this.loadConfigurations()]);
|
await Promise.all([this.loadItems(), this.loadConfigurations()]);
|
||||||
|
if (this.itemEditingId === id) this.cancelItemEdit();
|
||||||
if (this.configForm.item_id === id) {
|
if (this.configForm.item_id === id) {
|
||||||
this.configForm.item_id = '';
|
this.configForm.item_id = '';
|
||||||
this.layoutPreview = null;
|
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() {
|
async saveConfiguration() {
|
||||||
this.configSaving = true;
|
this.configSaving = true;
|
||||||
this.configError = '';
|
this.configError = '';
|
||||||
this.configSuccess = '';
|
this.configSuccess = '';
|
||||||
try {
|
const body = {
|
||||||
await this.apiJSON('POST', '/configurations', {
|
|
||||||
name: this.configForm.name,
|
name: this.configForm.name,
|
||||||
plate_id: this.configForm.plate_id,
|
plate_id: this.configForm.plate_id,
|
||||||
item_id: this.configForm.item_id,
|
item_id: this.configForm.item_id,
|
||||||
spacing_mm: Number(this.configForm.spacing_mm) || 0,
|
spacing_mm: Number(this.configForm.spacing_mm) || 0,
|
||||||
});
|
};
|
||||||
|
try {
|
||||||
|
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.configSuccess = 'Konfiguration gespeichert.';
|
||||||
|
}
|
||||||
|
this.cancelConfigEdit();
|
||||||
await this.loadConfigurations();
|
await this.loadConfigurations();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.configError = String(e.message || e);
|
this.configError = String(e.message || e);
|
||||||
@ -784,6 +917,7 @@
|
|||||||
throw new Error(text || res.statusText);
|
throw new Error(text || res.statusText);
|
||||||
}
|
}
|
||||||
await this.loadConfigurations();
|
await this.loadConfigurations();
|
||||||
|
if (this.configEditingId === id) this.cancelConfigEdit();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.configError = String(e.message || e);
|
this.configError = String(e.message || e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,9 +22,11 @@ func NewHandler() http.Handler {
|
|||||||
mux.HandleFunc("GET /", root)
|
mux.HandleFunc("GET /", root)
|
||||||
mux.HandleFunc("GET /plates", listPlates(plates))
|
mux.HandleFunc("GET /plates", listPlates(plates))
|
||||||
mux.HandleFunc("POST /plates", savePlate(plates))
|
mux.HandleFunc("POST /plates", savePlate(plates))
|
||||||
|
mux.HandleFunc("PUT /plates/{id}", updatePlate(plates))
|
||||||
mux.HandleFunc("DELETE /plates/{id}", deletePlate(plates))
|
mux.HandleFunc("DELETE /plates/{id}", deletePlate(plates))
|
||||||
mux.HandleFunc("GET /items", listItems(items))
|
mux.HandleFunc("GET /items", listItems(items))
|
||||||
mux.HandleFunc("POST /items", saveItem(items))
|
mux.HandleFunc("POST /items", saveItem(items))
|
||||||
|
mux.HandleFunc("PUT /items/{id}", updateItem(items))
|
||||||
mux.HandleFunc("DELETE /items/{id}", deleteItem(items))
|
mux.HandleFunc("DELETE /items/{id}", deleteItem(items))
|
||||||
mux.HandleFunc("GET /items/{id}/svg", serveItemSVG(items))
|
mux.HandleFunc("GET /items/{id}/svg", serveItemSVG(items))
|
||||||
registerConfigurationRoutes(mux, configs, plates, items)
|
registerConfigurationRoutes(mux, configs, plates, items)
|
||||||
@ -34,7 +36,7 @@ func NewHandler() http.Handler {
|
|||||||
func withCORS(next http.Handler) http.Handler {
|
func withCORS(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||||
if r.Method == http.MethodOptions {
|
if r.Method == http.MethodOptions {
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
@ -111,6 +113,40 @@ func savePlate(s *store.PlateStore) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updatePlate(s *store.PlateStore) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id := r.PathValue("id")
|
||||||
|
if id == "" {
|
||||||
|
writeError(w, http.StatusBadRequest, errors.New("id required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req savePlateRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.WidthMM <= 0 || req.HeightMM <= 0 {
|
||||||
|
writeError(w, http.StatusBadRequest, errors.New("width_mm and height_mm must be positive"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p := model.Plate{
|
||||||
|
Name: req.Name,
|
||||||
|
WidthMM: req.WidthMM,
|
||||||
|
HeightMM: req.HeightMM,
|
||||||
|
MarginTop: req.MarginTop,
|
||||||
|
MarginRight: req.MarginRight,
|
||||||
|
MarginBottom: req.MarginBottom,
|
||||||
|
MarginLeft: req.MarginLeft,
|
||||||
|
}
|
||||||
|
saved, err := s.Update(id, p)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, http.StatusOK, saved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func deletePlate(s *store.PlateStore) http.HandlerFunc {
|
func deletePlate(s *store.PlateStore) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
id := r.PathValue("id")
|
id := r.PathValue("id")
|
||||||
@ -170,6 +206,32 @@ func saveItem(s *store.ItemStore) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateItem(s *store.ItemStore) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id := r.PathValue("id")
|
||||||
|
if id == "" {
|
||||||
|
writeError(w, http.StatusBadRequest, errors.New("id required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req saveItemRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saved, err := s.Update(id, req.Name, model.ItemSpec{
|
||||||
|
SizeMM: req.SizeMM,
|
||||||
|
BleedMM: req.BleedMM,
|
||||||
|
MarginMM: req.MarginMM,
|
||||||
|
PaddingMM: req.PaddingMM,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, http.StatusOK, saved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func deleteItem(s *store.ItemStore) http.HandlerFunc {
|
func deleteItem(s *store.ItemStore) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
id := r.PathValue("id")
|
id := r.PathValue("id")
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import (
|
|||||||
func registerConfigurationRoutes(mux *http.ServeMux, configs *store.ConfigurationStore, plates *store.PlateStore, items *store.ItemStore) {
|
func registerConfigurationRoutes(mux *http.ServeMux, configs *store.ConfigurationStore, plates *store.PlateStore, items *store.ItemStore) {
|
||||||
mux.HandleFunc("GET /configurations", listConfigurations(configs, plates, items))
|
mux.HandleFunc("GET /configurations", listConfigurations(configs, plates, items))
|
||||||
mux.HandleFunc("POST /configurations", saveConfiguration(configs, plates, items))
|
mux.HandleFunc("POST /configurations", saveConfiguration(configs, plates, items))
|
||||||
|
mux.HandleFunc("PUT /configurations/{id}", updateConfiguration(configs, plates, items))
|
||||||
mux.HandleFunc("DELETE /configurations/{id}", deleteConfiguration(configs))
|
mux.HandleFunc("DELETE /configurations/{id}", deleteConfiguration(configs))
|
||||||
mux.HandleFunc("GET /configurations/{id}/preview", previewConfiguration(configs, plates, items))
|
mux.HandleFunc("GET /configurations/{id}/preview", previewConfiguration(configs, plates, items))
|
||||||
mux.HandleFunc("GET /layout/preview", layoutPreview(plates, items))
|
mux.HandleFunc("GET /layout/preview", layoutPreview(plates, items))
|
||||||
@ -99,6 +100,56 @@ func saveConfiguration(configs *store.ConfigurationStore, plates *store.PlateSto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateConfiguration(configs *store.ConfigurationStore, plates *store.PlateStore, items *store.ItemStore) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id := r.PathValue("id")
|
||||||
|
if id == "" {
|
||||||
|
writeError(w, http.StatusBadRequest, errors.New("id required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req saveConfigurationRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.PlateID == "" || req.ItemID == "" {
|
||||||
|
writeError(w, http.StatusBadRequest, errors.New("plate_id and item_id are required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := findPlate(plates, req.PlateID); err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := items.Get(req.ItemID); err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.SpacingMM < 0 {
|
||||||
|
writeError(w, http.StatusBadRequest, errors.New("spacing_mm must be non-negative"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c := model.Configuration{
|
||||||
|
Name: req.Name,
|
||||||
|
PlateID: req.PlateID,
|
||||||
|
ItemID: req.ItemID,
|
||||||
|
SpacingMM: req.SpacingMM,
|
||||||
|
}
|
||||||
|
saved, err := configs.Update(id, c)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := configurationResponseFor(saved, plates, items)
|
||||||
|
if resp.PreviewError != "" {
|
||||||
|
writeError(w, http.StatusInternalServerError, errors.New(resp.PreviewError))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func deleteConfiguration(configs *store.ConfigurationStore) http.HandlerFunc {
|
func deleteConfiguration(configs *store.ConfigurationStore) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
id := r.PathValue("id")
|
id := r.PathValue("id")
|
||||||
|
|||||||
@ -58,6 +58,21 @@ func (s *ConfigurationStore) Save(c model.Configuration) (model.Configuration, e
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update replaces an existing configuration by ID (preserves created_at).
|
||||||
|
func (s *ConfigurationStore) Update(id string, c model.Configuration) (model.Configuration, error) {
|
||||||
|
path := filepath.Join(s.dir, id+".json")
|
||||||
|
existing, err := readJSON[model.Configuration](path)
|
||||||
|
if err != nil {
|
||||||
|
return model.Configuration{}, fmt.Errorf("configuration not found: %s", id)
|
||||||
|
}
|
||||||
|
c.ID = existing.ID
|
||||||
|
c.CreatedAt = existing.CreatedAt
|
||||||
|
if err := writeJSON(path, c); err != nil {
|
||||||
|
return model.Configuration{}, fmt.Errorf("write configuration: %w", err)
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Delete removes a configuration by ID.
|
// Delete removes a configuration by ID.
|
||||||
func (s *ConfigurationStore) Delete(id string) error {
|
func (s *ConfigurationStore) Delete(id string) error {
|
||||||
path := filepath.Join(s.dir, id+".json")
|
path := filepath.Join(s.dir, id+".json")
|
||||||
|
|||||||
@ -65,6 +65,30 @@ func (s *ItemStore) Create(name string, spec model.ItemSpec) (model.Item, error)
|
|||||||
return svgtemplate.WriteMeta(basename, spec, displayName)
|
return svgtemplate.WriteMeta(basename, spec, displayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update regenerates the SVG and metadata for an existing item (preserves id, svg filename, created_at).
|
||||||
|
func (s *ItemStore) Update(id string, name string, spec model.ItemSpec) (model.Item, error) {
|
||||||
|
if spec.SizeMM <= 0 {
|
||||||
|
return model.Item{}, fmt.Errorf("size_mm must be positive")
|
||||||
|
}
|
||||||
|
item, err := s.Get(id)
|
||||||
|
if err != nil {
|
||||||
|
return model.Item{}, err
|
||||||
|
}
|
||||||
|
data := svgtemplate.Build(spec.SizeMM, spec.BleedMM, spec.MarginMM, spec.PaddingMM)
|
||||||
|
if err := svgtemplate.WriteFile(item.SVGTemplate, data); err != nil {
|
||||||
|
return model.Item{}, fmt.Errorf("write svg: %w", err)
|
||||||
|
}
|
||||||
|
if name != "" {
|
||||||
|
item.Name = name
|
||||||
|
}
|
||||||
|
item.Spec = spec
|
||||||
|
metaPath := filepath.Join(s.dir, model.MetaFilename(item.SVGTemplate))
|
||||||
|
if err := writeJSON(metaPath, item); err != nil {
|
||||||
|
return model.Item{}, fmt.Errorf("write meta: %w", err)
|
||||||
|
}
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Delete removes an item's SVG and metadata by ID.
|
// Delete removes an item's SVG and metadata by ID.
|
||||||
func (s *ItemStore) Delete(id string) error {
|
func (s *ItemStore) Delete(id string) error {
|
||||||
item, err := s.Get(id)
|
item, err := s.Get(id)
|
||||||
|
|||||||
@ -49,6 +49,21 @@ func (s *PlateStore) Save(p model.Plate) (model.Plate, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update replaces an existing plate by ID (preserves created_at).
|
||||||
|
func (s *PlateStore) Update(id string, p model.Plate) (model.Plate, error) {
|
||||||
|
path := filepath.Join(s.dir, id+".json")
|
||||||
|
existing, err := readJSON[model.Plate](path)
|
||||||
|
if err != nil {
|
||||||
|
return model.Plate{}, fmt.Errorf("plate not found: %s", id)
|
||||||
|
}
|
||||||
|
p.ID = existing.ID
|
||||||
|
p.CreatedAt = existing.CreatedAt
|
||||||
|
if err := writeJSON(path, p); err != nil {
|
||||||
|
return model.Plate{}, fmt.Errorf("write plate: %w", err)
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Delete removes a plate by ID.
|
// Delete removes a plate by ID.
|
||||||
func (s *PlateStore) Delete(id string) error {
|
func (s *PlateStore) Delete(id string) error {
|
||||||
path := filepath.Join(s.dir, id+".json")
|
path := filepath.Join(s.dir, id+".json")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user