154 lines
5.9 KiB
HTML
154 lines
5.9 KiB
HTML
|
|
{% extends "base.html" %}
|
|||
|
|
{% block title %}Packaging Studio · Foodlinkk{% endblock %}
|
|||
|
|
{% block extra_head %}
|
|||
|
|
<style>
|
|||
|
|
.pack-grid { display: grid; grid-template-columns: 1fr 340px; gap: 1rem; }
|
|||
|
|
@media (max-width: 1024px) { .pack-grid { grid-template-columns: 1fr; } }
|
|||
|
|
.pack-preview {
|
|||
|
|
min-height: 460px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
background: radial-gradient(circle at top, rgba(0,229,255,.08), rgba(0,0,0,.35));
|
|||
|
|
border: 1px solid rgba(0,229,255,.2);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
overflow: auto;
|
|||
|
|
padding: 1rem;
|
|||
|
|
}
|
|||
|
|
.pack-preview svg { width: 100%; max-height: 72vh; }
|
|||
|
|
.pack-tools { display:flex; gap:.5rem; flex-wrap:wrap; margin-top:.75rem; }
|
|||
|
|
.elements-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.5rem; }
|
|||
|
|
.project-list { max-height: 260px; overflow: auto; display:flex; flex-direction:column; gap:.5rem; }
|
|||
|
|
.project-item { border:1px solid rgba(148,163,184,.15); border-radius:10px; padding:.6rem; background:rgba(0,0,0,.25); }
|
|||
|
|
.project-item small { color: var(--text-secondary); display:block; margin-top:.25rem; }
|
|||
|
|
</style>
|
|||
|
|
{% endblock %}
|
|||
|
|
{% block content %}
|
|||
|
|
<div x-data="packagingStudio()" x-init="init()">
|
|||
|
|
<header class="page-header">
|
|||
|
|
<h1>Packaging Studio</h1>
|
|||
|
|
<p class="subtitle page-tip">Foodlinkk branded box, wrap en labels met live SVG preview.</p>
|
|||
|
|
</header>
|
|||
|
|
|
|||
|
|
<div class="pack-grid">
|
|||
|
|
<section class="panel">
|
|||
|
|
<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.75rem;">
|
|||
|
|
<label>Type
|
|||
|
|
<select class="form-input" x-model="form.type">
|
|||
|
|
<option value="folding_box">Folding box</option>
|
|||
|
|
<option value="wrap">Wrap</option>
|
|||
|
|
<option value="round_label">Round label</option>
|
|||
|
|
</select>
|
|||
|
|
</label>
|
|||
|
|
<label>Barcode value
|
|||
|
|
<input class="form-input" x-model="form.barcode_value" placeholder="8710000000012" />
|
|||
|
|
</label>
|
|||
|
|
<label>Breedte (mm)<input type="number" class="form-input" x-model.number="form.width_mm" min="10" /></label>
|
|||
|
|
<label>Hoogte (mm)<input type="number" class="form-input" x-model.number="form.height_mm" min="10" /></label>
|
|||
|
|
<label>Diepte (mm)<input type="number" class="form-input" x-model.number="form.depth_mm" min="0" /></label>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<h2 style="font-size:1rem;margin:1rem 0 .5rem;">Elementen</h2>
|
|||
|
|
<div class="elements-grid">
|
|||
|
|
<label><input type="checkbox" x-model="form.elements.barcode"> Barcode</label>
|
|||
|
|
<label><input type="checkbox" x-model="form.elements.logo_area"> Logo area</label>
|
|||
|
|
<label><input type="checkbox" x-model="form.elements.fold_lines"> Fold lines</label>
|
|||
|
|
<label><input type="checkbox" x-model="form.elements.cut_lines"> Cut lines</label>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="pack-tools">
|
|||
|
|
<button class="btn btn-primary" @click="generate()" :disabled="loading">
|
|||
|
|
<span x-show="!loading">Genereer verpakking</span>
|
|||
|
|
<span x-show="loading">Genereren…</span>
|
|||
|
|
</button>
|
|||
|
|
<a class="btn btn-ghost" :href="downloadUrl('svg')" x-show="projectId">Download SVG</a>
|
|||
|
|
<a class="btn btn-ghost" :href="downloadUrl('png')" x-show="projectId">Download PNG</a>
|
|||
|
|
<a class="btn btn-ghost" :href="downloadUrl('pdf')" x-show="projectId">Download PDF</a>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<aside class="panel">
|
|||
|
|
<h2 style="font-size:1rem;margin:0 0 .75rem;">Recente projecten</h2>
|
|||
|
|
<div class="project-list">
|
|||
|
|
<template x-for="p in projects" :key="p.id">
|
|||
|
|
<div class="project-item">
|
|||
|
|
<strong x-text="p.spec.type"></strong>
|
|||
|
|
<small x-text="p.spec.width_mm + '×' + p.spec.height_mm + '×' + p.spec.depth_mm + ' mm'"></small>
|
|||
|
|
<small x-text="(p.created_at || '').replace('T',' ').slice(0,19)"></small>
|
|||
|
|
<button class="btn btn-ghost" style="margin-top:.4rem;" @click="loadProject(p.id)">Open</button>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
<p class="page-tip" x-show="!projects.length">Nog geen packaging projecten.</p>
|
|||
|
|
</div>
|
|||
|
|
</aside>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="panel" style="margin-top:1rem;">
|
|||
|
|
<div class="pack-preview">
|
|||
|
|
<div x-show="svg" x-html="svg"></div>
|
|||
|
|
<p class="page-tip" x-show="!svg && !loading">Start met Genereren om de preview te zien.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
{% endblock %}
|
|||
|
|
{% block scripts %}
|
|||
|
|
<script>
|
|||
|
|
function packagingStudio() {
|
|||
|
|
return {
|
|||
|
|
loading: false,
|
|||
|
|
svg: '',
|
|||
|
|
projectId: '',
|
|||
|
|
projects: [],
|
|||
|
|
form: {
|
|||
|
|
type: 'folding_box',
|
|||
|
|
width_mm: 120,
|
|||
|
|
height_mm: 80,
|
|||
|
|
depth_mm: 40,
|
|||
|
|
barcode_value: '8710000000012',
|
|||
|
|
brand: {},
|
|||
|
|
elements: { barcode: true, logo_area: true, fold_lines: true, cut_lines: true }
|
|||
|
|
},
|
|||
|
|
async init() { await this.refreshProjects(); },
|
|||
|
|
downloadUrl(format) {
|
|||
|
|
if (!this.projectId) return '#';
|
|||
|
|
return '/api/packaging/download/' + this.projectId + '?format=' + format;
|
|||
|
|
},
|
|||
|
|
async refreshProjects() {
|
|||
|
|
try {
|
|||
|
|
const r = await Cockpit.api('/api/packaging/projects?limit=20');
|
|||
|
|
this.projects = r.items || [];
|
|||
|
|
} catch (e) {
|
|||
|
|
this.projects = [];
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
async loadProject(id) {
|
|||
|
|
this.projectId = id;
|
|||
|
|
try {
|
|||
|
|
const data = await fetch('/api/packaging/download/' + id + '?format=svg').then(x => x.text());
|
|||
|
|
this.svg = data;
|
|||
|
|
} catch (e) {
|
|||
|
|
Cockpit.toast('Kon project niet laden', 'error');
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
async generate() {
|
|||
|
|
if (this.loading) return;
|
|||
|
|
this.loading = true;
|
|||
|
|
try {
|
|||
|
|
const r = await Cockpit.api('/api/packaging/generate', {
|
|||
|
|
method: 'POST',
|
|||
|
|
body: JSON.stringify(this.form)
|
|||
|
|
});
|
|||
|
|
this.svg = r.svg || '';
|
|||
|
|
this.projectId = r.id || '';
|
|||
|
|
await this.refreshProjects();
|
|||
|
|
Cockpit.toast('Packaging gegenereerd', 'success');
|
|||
|
|
} catch (e) {
|
|||
|
|
Cockpit.toast(e.message || 'Genereren mislukt', 'error');
|
|||
|
|
}
|
|||
|
|
this.loading = false;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
{% endblock %}
|