fix: improved UI
CI / test (push) Failing after 1m8s
CI / build (push) Has been skipped

This commit is contained in:
2026-07-23 16:53:16 +02:00
parent d0a4cabb3b
commit 332e0fc5f4
6 changed files with 264 additions and 6 deletions
@@ -127,8 +127,27 @@
</table>
</div>
<div class="mb-6">
<button class="btn btn-sm" hx-get="{% url 'backoffice:product_price_create' product.pk %}" hx-target="#backoffice-modal-box" hx-swap="innerHTML">Añadir precio al producto</button>
<div class="flex justify-between items-center mb-2">
<h2 class="text-xl font-semibold">Precios</h2>
<button class="btn btn-sm btn-primary" hx-get="{% url 'backoffice:product_price_create' product.pk %}" hx-target="#backoffice-modal-box" hx-swap="innerHTML">Añadir precio</button>
</div>
<div class="overflow-x-auto bg-base-100 rounded-box border border-base-300 mb-6">
<table class="table">
<thead><tr><th>Fecha</th><th>Precio</th><th>Impuesto</th><th>Con impuestos</th><th></th></tr></thead>
<tbody>
{% for price in prices %}
<tr class="hover">
<td>{{ price.date|date:'d/m/Y H:i' }}</td>
<td>{{ price.price }} €</td>
<td>{{ price.tax.code }}</td>
<td>{{ price.price_with_tax }} €</td>
<td>{% if price.current %}<span class="badge badge-success">Actual</span>{% endif %}</td>
</tr>
{% empty %}
<tr><td colspan="5" class="text-center py-6">Este producto no tiene precios.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="flex justify-between items-center mb-2">
@@ -55,9 +55,105 @@
</label>
</div>
<div class="mb-6 max-w-md">
<h3 class="text-sm font-medium opacity-70 mb-1">Precio inicial (opcional)</h3>
<div class="grid grid-cols-2 gap-4">
<div>
{{ price_form.price }}
{{ price_form.price.errors }}
</div>
<div>
{{ price_form.tax }}
{{ price_form.tax.errors }}
</div>
</div>
{{ price_form.non_field_errors }}
</div>
<div class="mb-6">
<div class="flex justify-between items-center mb-2">
<h3 class="text-sm font-medium opacity-70">Imágenes</h3>
<button type="button" class="btn btn-sm btn-primary" onclick="document.getElementById('id_images_input').click()">Añadir imagen</button>
</div>
<input type="file" id="id_images_input" name="images" multiple accept="image/*" class="hidden" onchange="productCreateAddImages(this.files)">
<div id="images-preview" class="flex flex-wrap gap-4">
<p class="opacity-70">Este producto no tiene imágenes.</p>
</div>
{% if image_errors %}
<ul class="text-error text-sm mt-1">
{% for error in image_errors %}<li>{{ error }}</li>{% endfor %}
</ul>
{% endif %}
</div>
<div class="flex gap-2">
<button class="btn btn-primary">Crear producto</button>
<a class="btn" href="{% url 'backoffice:product_list' %}">Cancelar</a>
</div>
</form>
{% endblock %}
{% block extra_js %}
<script>
let productCreateImageFiles = [];
function productCreateAddImages(fileList) {
for (const file of fileList) {
productCreateImageFiles.push(file);
}
renderImagePreviews();
syncImagesInput();
}
function productCreateRemoveImage(index) {
productCreateImageFiles.splice(index, 1);
renderImagePreviews();
syncImagesInput();
}
function renderImagePreviews() {
const container = document.getElementById('images-preview');
container.innerHTML = '';
if (productCreateImageFiles.length === 0) {
const empty = document.createElement('p');
empty.className = 'opacity-70';
empty.textContent = 'Este producto no tiene imágenes.';
container.appendChild(empty);
return;
}
productCreateImageFiles.forEach(function (file, index) {
const wrapper = document.createElement('div');
wrapper.className = 'relative';
const img = document.createElement('img');
img.className = 'w-24 h-24 object-cover rounded-box border ' + (index === 0 ? 'border-primary border-2' : 'border-base-300');
img.src = URL.createObjectURL(file);
wrapper.appendChild(img);
if (index === 0) {
const badge = document.createElement('span');
badge.className = 'badge badge-primary badge-xs absolute -top-2 -left-2';
badge.textContent = 'Principal';
wrapper.appendChild(badge);
}
const removeButton = document.createElement('button');
removeButton.type = 'button';
removeButton.className = 'btn btn-xs btn-error absolute -top-2 -right-2';
removeButton.textContent = '✕';
removeButton.onclick = function () { productCreateRemoveImage(index); };
wrapper.appendChild(removeButton);
container.appendChild(wrapper);
});
}
function syncImagesInput() {
const dataTransfer = new DataTransfer();
productCreateImageFiles.forEach(function (file) { dataTransfer.items.add(file); });
document.getElementById('id_images_input').files = dataTransfer.files;
}
</script>
{% endblock %}