Add dynamic custom field display and remove redundant admin routes

Major improvements to tenant management:
1. Dynamic field display - Product lists now automatically show all custom fields based on tenant configuration (e.g., inventory, custom attributes)
2. Fixed Jinja2 template scoping issue - Product field values now correctly display in edit forms using selectattr filter
3. Simplified URL structure - Removed redundant /tenant/admin/ routes, all product management now at /tenant/ root
4. Enhanced tenant landing page - Added delete functionality with confirmation modal, shows all custom fields except images
5. Updated all routes and redirects to use simplified paths (/tenant/add, /tenant/edit, etc.)

This consolidates the interface so /tenant/ serves as the main dashboard with full product management capabilities, while /tenant/settings handles configuration.
This commit is contained in:
2025-10-20 17:11:26 -04:00
parent 92eb45fc8c
commit f781cdf602
7 changed files with 142 additions and 103 deletions

View File

@@ -18,7 +18,7 @@
<div class="mt-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>Products</h2>
<a href="/{{ tenant.id }}/admin/add" class="btn btn-primary">
<a href="/{{ tenant.id }}/add" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Add New Product
</a>
</div>
@@ -29,8 +29,11 @@
<thead>
<tr>
<th>Product ID</th>
<th>Name</th>
<th>Price</th>
{% for custom_field in custom_fields %}
{% if custom_field.fieldName != '_id' and custom_field.fieldType != 'IMAGE_URI' %}
<th>{{ custom_field.label }}</th>
{% endif %}
{% endfor %}
<th>Actions</th>
</tr>
</thead>
@@ -38,45 +41,42 @@
{% for product_id, product_data in products.items() %}
<tr>
<td>{{ product_id }}</td>
{% for custom_field in custom_fields %}
{% if custom_field.fieldName != '_id' and custom_field.fieldType != 'IMAGE_URI' %}
<td>
{% set matching_fields = product_data|selectattr('fieldName', 'equalto', custom_field.fieldName)|list %}
{{ matching_fields[0].value if matching_fields else '' }}
</td>
{% endif %}
{% endfor %}
<td>
{% for field in product_data %}
{% if field.fieldName == '_name' %}
{{ field.value }}
{% endif %}
{% endfor %}
</td>
<td>
{% for field in product_data %}
{% if field.fieldName == '_price' %}
{{ field.value }}
{% endif %}
{% endfor %}
</td>
<td>
<a href="/{{ tenant.id }}/admin/edit/{{ product_id }}" class="btn btn-sm btn-outline-primary">
<a href="/{{ tenant.id }}/edit/{{ product_id }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil"></i> Edit
</a>
<a href="/{{ tenant.id }}/admin/generate_barcode_page/{{ product_id }}" class="btn btn-sm btn-outline-success">
<a href="/{{ tenant.id }}/generate_barcode_page/{{ product_id }}" class="btn btn-sm btn-outline-success">
<i class="bi bi-upc-scan"></i> Barcodes
</a>
<button type="button" class="btn btn-sm btn-outline-danger"
data-bs-toggle="modal" data-bs-target="#deleteModal"
data-product-id="{{ product_id }}"
data-product-name="{% set matching_fields = product_data|selectattr('fieldName', 'equalto', '_name')|list %}{{ matching_fields[0].value if matching_fields else product_id }}">
<i class="bi bi-trash"></i> Delete
</button>
<form id="delete-form-{{ product_id }}" action="/{{ tenant.id }}/delete/{{ product_id }}" method="POST" style="display: none;">
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="mt-3">
<a href="/{{ tenant.id }}/admin/" class="btn btn-secondary">
<i class="bi bi-list-task"></i> Manage All Products
</a>
</div>
{% else %}
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> No products available yet. Click "Add New Product" to get started.
</div>
{% endif %}
</div>
<div class="mt-4">
<h2>Getting Started</h2>
<ol>
@@ -89,4 +89,51 @@
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="deleteModalLabel">Confirm Deletion</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete the product <strong id="delete-product-name"></strong>?</p>
<p class="text-danger"><i class="bi bi-exclamation-triangle-fill"></i> This action cannot be undone.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirm-delete-btn">Delete Product</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// Delete confirmation modal functionality
const deleteModal = document.getElementById('deleteModal');
if (deleteModal) {
deleteModal.addEventListener('show.bs.modal', function (event) {
// Button that triggered the modal
const button = event.relatedTarget;
// Extract product info from data attributes
const productId = button.getAttribute('data-product-id');
const productName = button.getAttribute('data-product-name');
// Update modal content
const modalProductName = document.getElementById('delete-product-name');
modalProductName.textContent = productName + " (ID: " + productId + ")";
// Setup the confirm button action
const confirmDeleteBtn = document.getElementById('confirm-delete-btn');
confirmDeleteBtn.onclick = function() {
document.getElementById('delete-form-' + productId).submit();
};
});
}
</script>
{% endblock %}