fix: improved UI
This commit is contained in:
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user