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:
30
src/app.py
30
src/app.py
@@ -70,11 +70,12 @@ def tenant_index(tenant_id):
|
|||||||
if tenant is None:
|
if tenant is None:
|
||||||
# Reserved tenant ID or invalid
|
# Reserved tenant ID or invalid
|
||||||
return jsonify({"error": f"'{tenant_id}' is a reserved name and cannot be used as a tenant ID"}), 404
|
return jsonify({"error": f"'{tenant_id}' is a reserved name and cannot be used as a tenant ID"}), 404
|
||||||
|
|
||||||
# Load products for this tenant
|
# Load products for this tenant
|
||||||
products = load_products(tenant_id)
|
products = load_products(tenant_id)
|
||||||
|
custom_fields = database.get_custom_ar_fields(tenant_id)
|
||||||
return render_template('index.html', tenant=tenant, products=products)
|
|
||||||
|
return render_template('index.html', tenant=tenant, products=products, custom_fields=custom_fields)
|
||||||
|
|
||||||
@app.route('/<tenant:tenant_id>/settings')
|
@app.route('/<tenant:tenant_id>/settings')
|
||||||
def tenant_settings(tenant_id):
|
def tenant_settings(tenant_id):
|
||||||
@@ -292,21 +293,16 @@ def serve_barcode(tenant_id, filename):
|
|||||||
app.logger.error(f"Barcode generation error: {str(e)}")
|
app.logger.error(f"Barcode generation error: {str(e)}")
|
||||||
return jsonify({"error": "Failed to generate barcode"}), 500
|
return jsonify({"error": "Failed to generate barcode"}), 500
|
||||||
|
|
||||||
# Import the admin routes directly and register them with tenant support
|
# Import product management routes
|
||||||
from routes.admin import (index as admin_index, add_product, edit_product,
|
from routes.admin import (add_product, edit_product, delete_product,
|
||||||
delete_product, view_product, generate_barcode,
|
generate_barcode, generate_barcode_page)
|
||||||
generate_barcode_page, manage_credentials, manage_ar_fields)
|
|
||||||
|
|
||||||
# Register admin routes with tenant prefix
|
# Register product management routes (remove /admin/ from paths)
|
||||||
app.add_url_rule('/<tenant:tenant_id>/admin/', 'admin.index', admin_index)
|
app.add_url_rule('/<tenant:tenant_id>/add', 'admin.add_product', add_product, methods=['GET', 'POST'])
|
||||||
app.add_url_rule('/<tenant:tenant_id>/admin/add', 'admin.add_product', add_product, methods=['GET', 'POST'])
|
app.add_url_rule('/<tenant:tenant_id>/edit/<product_id>', 'admin.edit_product', edit_product, methods=['GET', 'POST'])
|
||||||
app.add_url_rule('/<tenant:tenant_id>/admin/edit/<product_id>', 'admin.edit_product', edit_product, methods=['GET', 'POST'])
|
app.add_url_rule('/<tenant:tenant_id>/delete/<product_id>', 'admin.delete_product', delete_product, methods=['POST'])
|
||||||
app.add_url_rule('/<tenant:tenant_id>/admin/delete/<product_id>', 'admin.delete_product', delete_product, methods=['POST'])
|
app.add_url_rule('/<tenant:tenant_id>/generate_barcode/<product_id>/<code_type>', 'admin.generate_barcode', generate_barcode)
|
||||||
app.add_url_rule('/<tenant:tenant_id>/admin/view/<product_id>', 'admin.view_product', view_product)
|
app.add_url_rule('/<tenant:tenant_id>/generate_barcode_page/<product_id>', 'admin.generate_barcode_page', generate_barcode_page)
|
||||||
app.add_url_rule('/<tenant:tenant_id>/admin/generate_barcode/<product_id>/<code_type>', 'admin.generate_barcode', generate_barcode)
|
|
||||||
app.add_url_rule('/<tenant:tenant_id>/admin/generate_barcode_page/<product_id>', 'admin.generate_barcode_page', generate_barcode_page)
|
|
||||||
app.add_url_rule('/<tenant:tenant_id>/admin/credentials', 'admin.manage_credentials', manage_credentials, methods=['GET', 'POST'])
|
|
||||||
app.add_url_rule('/<tenant:tenant_id>/admin/ar_fields', 'admin.manage_ar_fields', manage_ar_fields, methods=['GET', 'POST'])
|
|
||||||
|
|
||||||
# Register API routes with tenant prefix
|
# Register API routes with tenant prefix
|
||||||
from routes.api import api_index
|
from routes.api import api_index
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ def save_products(products):
|
|||||||
def index(tenant_id):
|
def index(tenant_id):
|
||||||
products = load_products(tenant_id)
|
products = load_products(tenant_id)
|
||||||
tenant = database.get_or_create_tenant(tenant_id)
|
tenant = database.get_or_create_tenant(tenant_id)
|
||||||
return render_template('admin/index.html', products=products, tenant=tenant)
|
custom_fields = database.get_custom_ar_fields(tenant_id)
|
||||||
|
return render_template('admin/index.html', products=products, tenant=tenant, custom_fields=custom_fields)
|
||||||
|
|
||||||
def add_product(tenant_id):
|
def add_product(tenant_id):
|
||||||
# Get custom AR fields for this tenant
|
# Get custom AR fields for this tenant
|
||||||
@@ -172,26 +173,26 @@ def add_product(tenant_id):
|
|||||||
database.save_product_image(product_id, tenant_id, img['field_name'], img['data'], img['mime_type'])
|
database.save_product_image(product_id, tenant_id, img['field_name'], img['data'], img['mime_type'])
|
||||||
|
|
||||||
flash('Product added successfully!')
|
flash('Product added successfully!')
|
||||||
return redirect(url_for('admin.index', tenant_id=tenant_id))
|
return redirect(f'/{tenant_id}/')
|
||||||
|
|
||||||
return render_template('admin/add_product.html', tenant_id=tenant_id, custom_fields=custom_fields)
|
return render_template('admin/add_product.html', tenant_id=tenant_id, custom_fields=custom_fields)
|
||||||
|
|
||||||
def edit_product(tenant_id, product_id):
|
def edit_product(tenant_id, product_id):
|
||||||
products = load_products(tenant_id)
|
products = load_products(tenant_id)
|
||||||
custom_fields = database.get_custom_ar_fields(tenant_id)
|
custom_fields = database.get_custom_ar_fields(tenant_id)
|
||||||
|
|
||||||
if product_id not in products:
|
if product_id not in products:
|
||||||
flash('Product not found.')
|
flash('Product not found.')
|
||||||
return redirect(url_for('admin.index', tenant_id=tenant_id))
|
return redirect(f'/{tenant_id}/')
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
# Handle backward compatibility image upload
|
# Handle backward compatibility image upload
|
||||||
image_data = None
|
image_data = None
|
||||||
image_mime_type = None
|
image_mime_type = None
|
||||||
|
|
||||||
# Process images after product is saved
|
# Process images after product is saved
|
||||||
images_to_save = []
|
images_to_save = []
|
||||||
|
|
||||||
# Update fields based on form data
|
# Update fields based on form data
|
||||||
updated_product = []
|
updated_product = []
|
||||||
|
|
||||||
@@ -295,7 +296,7 @@ def edit_product(tenant_id, product_id):
|
|||||||
database.save_product_image(product_id, tenant_id, img['field_name'], img['data'], img['mime_type'])
|
database.save_product_image(product_id, tenant_id, img['field_name'], img['data'], img['mime_type'])
|
||||||
|
|
||||||
flash('Product updated successfully!')
|
flash('Product updated successfully!')
|
||||||
return redirect(url_for('admin.index', tenant_id=tenant_id))
|
return redirect(f'/{tenant_id}/')
|
||||||
|
|
||||||
return render_template('admin/edit_product.html',
|
return render_template('admin/edit_product.html',
|
||||||
product_id=product_id,
|
product_id=product_id,
|
||||||
@@ -305,24 +306,24 @@ def edit_product(tenant_id, product_id):
|
|||||||
|
|
||||||
def delete_product(tenant_id, product_id):
|
def delete_product(tenant_id, product_id):
|
||||||
products = load_products(tenant_id)
|
products = load_products(tenant_id)
|
||||||
|
|
||||||
if product_id not in products:
|
if product_id not in products:
|
||||||
flash('Product not found.')
|
flash('Product not found.')
|
||||||
return redirect(url_for('admin.index', tenant_id=tenant_id))
|
return redirect(f'/{tenant_id}/')
|
||||||
|
|
||||||
# Delete product from database (image is stored in DB)
|
# Delete product from database (image is stored in DB)
|
||||||
database.delete_product(product_id, tenant_id)
|
database.delete_product(product_id, tenant_id)
|
||||||
|
|
||||||
flash('Product deleted successfully!')
|
flash('Product deleted successfully!')
|
||||||
return redirect(url_for('admin.index', tenant_id=tenant_id))
|
return redirect(f'/{tenant_id}/')
|
||||||
|
|
||||||
def view_product(tenant_id, product_id):
|
def view_product(tenant_id, product_id):
|
||||||
products = load_products(tenant_id)
|
products = load_products(tenant_id)
|
||||||
|
|
||||||
if product_id not in products:
|
if product_id not in products:
|
||||||
flash('Product not found.')
|
flash('Product not found.')
|
||||||
return redirect(url_for('admin.index', tenant_id=tenant_id))
|
return redirect(f'/{tenant_id}/')
|
||||||
|
|
||||||
return render_template('admin/view_product.html', product_id=product_id, product=products[product_id], tenant_id=tenant_id)
|
return render_template('admin/view_product.html', product_id=product_id, product=products[product_id], tenant_id=tenant_id)
|
||||||
|
|
||||||
def generate_barcode(tenant_id, product_id, code_type):
|
def generate_barcode(tenant_id, product_id, code_type):
|
||||||
@@ -346,10 +347,10 @@ def generate_barcode(tenant_id, product_id, code_type):
|
|||||||
def generate_barcode_page(tenant_id, product_id):
|
def generate_barcode_page(tenant_id, product_id):
|
||||||
"""Show a page with different barcode options for a product"""
|
"""Show a page with different barcode options for a product"""
|
||||||
products = load_products(tenant_id)
|
products = load_products(tenant_id)
|
||||||
|
|
||||||
if product_id not in products:
|
if product_id not in products:
|
||||||
flash('Product not found.')
|
flash('Product not found.')
|
||||||
return redirect(url_for('admin.index', tenant_id=tenant_id))
|
return redirect(f'/{tenant_id}/')
|
||||||
|
|
||||||
# Extract product data
|
# Extract product data
|
||||||
product_data = {}
|
product_data = {}
|
||||||
@@ -388,7 +389,7 @@ def manage_credentials(tenant_id):
|
|||||||
if username and password:
|
if username and password:
|
||||||
database.update_tenant_credentials(tenant_id, username, password)
|
database.update_tenant_credentials(tenant_id, username, password)
|
||||||
flash('Credentials updated successfully!')
|
flash('Credentials updated successfully!')
|
||||||
return redirect(url_for('admin.index', tenant_id=tenant_id))
|
return redirect(f'/{tenant_id}/')
|
||||||
else:
|
else:
|
||||||
flash('Username and password are required.')
|
flash('Username and password are required.')
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<h2>Add New Product</h2>
|
<h2>Add New Product</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="{{ url_for('admin.add_product', tenant_id=tenant_id) }}" method="POST" enctype="multipart/form-data">
|
<form action="/{{ tenant_id }}/add" method="POST" enctype="multipart/form-data">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="product_id" class="form-label">Product ID *</label>
|
<label for="product_id" class="form-label">Product ID *</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<a href="{{ url_for('admin.index', tenant_id=tenant_id) }}" class="btn btn-secondary">Cancel</a>
|
<a href="/{{ tenant_id }}/" class="btn btn-secondary">Cancel</a>
|
||||||
<button type="submit" class="btn btn-primary">Add Product</button>
|
<button type="submit" class="btn btn-primary">Add Product</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<h2>Edit Product</h2>
|
<h2>Edit Product</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="{{ url_for('admin.edit_product', tenant_id=tenant_id, product_id=product_id) }}" method="POST" enctype="multipart/form-data">
|
<form action="/{{ tenant_id }}/edit/{{ product_id }}" method="POST" enctype="multipart/form-data">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="product_id" class="form-label">Product ID</label>
|
<label for="product_id" class="form-label">Product ID</label>
|
||||||
<input type="text" class="form-control" id="product_id" value="{{ product_id }}" disabled>
|
<input type="text" class="form-control" id="product_id" value="{{ product_id }}" disabled>
|
||||||
@@ -21,14 +21,11 @@
|
|||||||
{% if field.fieldName != '_id' %}
|
{% if field.fieldName != '_id' %}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="field_{{ field.fieldName }}" class="form-label">{{ field.label }}</label>
|
<label for="field_{{ field.fieldName }}" class="form-label">{{ field.label }}</label>
|
||||||
|
|
||||||
{% set current_value = '' %}
|
{# Find current value using selectattr filter #}
|
||||||
{% for prod_field in product %}
|
{% set matching_fields = product|selectattr('fieldName', 'equalto', field.fieldName)|list %}
|
||||||
{% if prod_field.fieldName == field.fieldName %}
|
{% set current_value = matching_fields[0].value if matching_fields else '' %}
|
||||||
{% set current_value = prod_field.value %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if field.fieldType == 'IMAGE_URI' %}
|
{% if field.fieldType == 'IMAGE_URI' %}
|
||||||
<input type="file" class="form-control" id="image_{{ field.fieldName }}" name="image_{{ field.fieldName }}" accept="image/*" onchange="previewImage(this, 'preview_new_{{ field.fieldName }}')">
|
<input type="file" class="form-control" id="image_{{ field.fieldName }}" name="image_{{ field.fieldName }}" accept="image/*" onchange="previewImage(this, 'preview_new_{{ field.fieldName }}')">
|
||||||
<div class="form-text">Leave empty to keep the current image.</div>
|
<div class="form-text">Leave empty to keep the current image.</div>
|
||||||
@@ -93,7 +90,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<a href="{{ url_for('admin.index', tenant_id=tenant_id) }}" class="btn btn-secondary">Cancel</a>
|
<a href="/{{ tenant_id }}/" class="btn btn-secondary">Cancel</a>
|
||||||
<button type="submit" class="btn btn-primary">Update Product</button>
|
<button type="submit" class="btn btn-primary">Update Product</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h2>Generate Barcodes for Product: {{ product_id }}</h2>
|
<h2>Generate Barcodes for Product: {{ product_id }}</h2>
|
||||||
<a href="{{ url_for('admin.index', tenant_id=tenant_id) }}" class="btn btn-outline-secondary">Back to Products</a>
|
<a href="/{{ tenant_id }}/" class="btn btn-outline-secondary">Back to Products</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if not dependencies.qrcode or not dependencies.barcode or not dependencies.pillow %}
|
{% if not dependencies.qrcode or not dependencies.barcode or not dependencies.pillow %}
|
||||||
|
|||||||
@@ -22,46 +22,44 @@
|
|||||||
<table class="table table-striped table-bordered align-middle">
|
<table class="table table-striped table-bordered align-middle">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 15%">Product ID</th>
|
<th>Product ID</th>
|
||||||
<th style="width: 25%">Name</th>
|
{% for custom_field in custom_fields %}
|
||||||
<th style="width: 10%">Price</th>
|
{% if custom_field.fieldName != '_id' %}
|
||||||
<th style="width: 20%">Image</th>
|
<th>{{ custom_field.label }}</th>
|
||||||
<th style="width: 30%">Actions</th>
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for product_id, product_data in products.items() %}
|
{% for product_id, product_data in products.items() %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ product_id }}</td>
|
<td>{{ product_id }}</td>
|
||||||
<td>
|
{% for custom_field in custom_fields %}
|
||||||
{% for field in product_data %}
|
{% if custom_field.fieldName != '_id' %}
|
||||||
{% if field.fieldName == '_name' %}
|
<td>
|
||||||
{{ field.value }}
|
{% for field in product_data %}
|
||||||
{% endif %}
|
{% if field.fieldName == custom_field.fieldName %}
|
||||||
{% endfor %}
|
{% if custom_field.fieldType == 'IMAGE_URI' %}
|
||||||
</td>
|
{% if field.value %}
|
||||||
<td>
|
<div class="product-image-container">
|
||||||
{% for field in product_data %}
|
<img src="{{ field.value }}" alt="{{ custom_field.label }}" class="product-image">
|
||||||
{% if field.fieldName == '_price' %}
|
</div>
|
||||||
{{ field.value }}
|
{% endif %}
|
||||||
{% endif %}
|
{% else %}
|
||||||
{% endfor %}
|
{{ field.value }}
|
||||||
</td>
|
{% endif %}
|
||||||
<td>
|
{% endif %}
|
||||||
{% for field in product_data %}
|
{% endfor %}
|
||||||
{% if field.fieldName == '_image' %}
|
</td>
|
||||||
<div class="product-image-container">
|
{% endif %}
|
||||||
<img src="{{ field.value }}" alt="Product image" class="product-image">
|
{% endfor %}
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex justify-content-center gap-2">
|
<div class="d-flex justify-content-center gap-2">
|
||||||
<a href="{{ url_for('admin.edit_product', tenant_id=tenant.id, product_id=product_id) }}" class="btn btn-sm btn-outline-primary">Edit</a>
|
<a href="{{ url_for('admin.edit_product', tenant_id=tenant.id, product_id=product_id) }}" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||||
<a href="{{ url_for('admin.generate_barcode_page', tenant_id=tenant.id, product_id=product_id) }}" class="btn btn-sm btn-outline-success">Barcodes</a>
|
<a href="{{ url_for('admin.generate_barcode_page', tenant_id=tenant.id, product_id=product_id) }}" class="btn btn-sm btn-outline-success">Barcodes</a>
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger"
|
<button type="button" class="btn btn-sm btn-outline-danger"
|
||||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||||
data-product-id="{{ product_id }}"
|
data-product-id="{{ product_id }}"
|
||||||
data-product-name="{% for field in product_data %}{% if field.fieldName == '_name' %}{{ field.value }}{% endif %}{% endfor %}">
|
data-product-name="{% for field in product_data %}{% if field.fieldName == '_name' %}{{ field.value }}{% endif %}{% endfor %}">
|
||||||
Delete
|
Delete
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h2>Products</h2>
|
<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
|
<i class="bi bi-plus-circle"></i> Add New Product
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,8 +29,11 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Product ID</th>
|
<th>Product ID</th>
|
||||||
<th>Name</th>
|
{% for custom_field in custom_fields %}
|
||||||
<th>Price</th>
|
{% if custom_field.fieldName != '_id' and custom_field.fieldType != 'IMAGE_URI' %}
|
||||||
|
<th>{{ custom_field.label }}</th>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -38,45 +41,42 @@
|
|||||||
{% for product_id, product_data in products.items() %}
|
{% for product_id, product_data in products.items() %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ product_id }}</td>
|
<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>
|
<td>
|
||||||
{% for field in product_data %}
|
<a href="/{{ tenant.id }}/edit/{{ product_id }}" class="btn btn-sm btn-outline-primary">
|
||||||
{% 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">
|
|
||||||
<i class="bi bi-pencil"></i> Edit
|
<i class="bi bi-pencil"></i> Edit
|
||||||
</a>
|
</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
|
<i class="bi bi-upc-scan"></i> Barcodes
|
||||||
</a>
|
</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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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 %}
|
{% else %}
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<i class="bi bi-info-circle"></i> No products available yet. Click "Add New Product" to get started.
|
<i class="bi bi-info-circle"></i> No products available yet. Click "Add New Product" to get started.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<h2>Getting Started</h2>
|
<h2>Getting Started</h2>
|
||||||
<ol>
|
<ol>
|
||||||
@@ -89,4 +89,51 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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 %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user