Refactor worklog handling and rendering; enhance active worklog display and add settings page with brand management functionality
This commit is contained in:
parent
3915b97231
commit
e2b8579362
9 changed files with 145 additions and 42 deletions
42
routes.py
42
routes.py
|
@ -61,7 +61,7 @@ worklog_headers = {
|
||||||
"Start Time": lambda i: {"text": i.start_time.strftime("%Y-%m-%d")},
|
"Start Time": lambda i: {"text": i.start_time.strftime("%Y-%m-%d")},
|
||||||
"End Time": lambda i: {"text": i.end_time.strftime("%Y-%m-%d")} if i.end_time else {"text": None},
|
"End Time": lambda i: {"text": i.end_time.strftime("%Y-%m-%d")} if i.end_time else {"text": None},
|
||||||
"Complete?": lambda i: {"text": i.complete, "type": "bool", "html": checked_box if i.complete else unchecked_box},
|
"Complete?": lambda i: {"text": i.complete, "type": "bool", "html": checked_box if i.complete else unchecked_box},
|
||||||
"Follow Up?": lambda i: {"text": i.followup, "type": "bool", "html": checked_box if i.followup else unchecked_box},
|
"Follow Up?": lambda i: {"text": i.followup, "type": "bool", "html": checked_box if i.followup else unchecked_box, "highlight": i.followup},
|
||||||
"Quick Analysis?": lambda i: {"text": i.analysis, "type": "bool", "html": checked_box if i.analysis else unchecked_box},
|
"Quick Analysis?": lambda i: {"text": i.analysis, "type": "bool", "html": checked_box if i.analysis else unchecked_box},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,13 +136,13 @@ def index():
|
||||||
worklog_query = eager_load_worklog_relationships(
|
worklog_query = eager_load_worklog_relationships(
|
||||||
db.session.query(WorkLog)
|
db.session.query(WorkLog)
|
||||||
).filter(
|
).filter(
|
||||||
(WorkLog.start_time < cutoff) & (WorkLog.complete == False)
|
(WorkLog.complete == False)
|
||||||
)
|
)
|
||||||
|
|
||||||
stale_worklogs = worklog_query.all()
|
active_worklogs = worklog_query.all()
|
||||||
|
|
||||||
stale_count = len(stale_worklogs)
|
active_count = len(active_worklogs)
|
||||||
stale_worklog_headers = {
|
active_worklog_headers = {
|
||||||
k: v for k, v in worklog_headers.items()
|
k: v for k, v in worklog_headers.items()
|
||||||
if k not in ['End Time', 'Quick Analysis?', 'Complete?', 'Follow Up?']
|
if k not in ['End Time', 'Quick Analysis?', 'Complete?', 'Follow Up?']
|
||||||
}
|
}
|
||||||
|
@ -179,15 +179,28 @@ def index():
|
||||||
'name': 'Inventory Conditions'
|
'name': 'Inventory Conditions'
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
active_worklog_rows = []
|
||||||
|
for log in active_worklogs:
|
||||||
|
# Create a dictionary of {column name: cell dict}
|
||||||
|
cells_by_key = {k: fn(log) for k, fn in worklog_headers.items()}
|
||||||
|
|
||||||
|
# Use original, full header set for logic
|
||||||
|
highlight = cells_by_key.get("Follow Up?", {}).get("highlight", False)
|
||||||
|
|
||||||
|
# Use only filtered headers — and in exact order
|
||||||
|
cells = [cells_by_key[k] for k in active_worklog_headers]
|
||||||
|
|
||||||
|
active_worklog_rows.append({
|
||||||
|
"id": log.id,
|
||||||
|
"cells": cells,
|
||||||
|
"highlight": highlight
|
||||||
|
})
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"index.html",
|
"index.html",
|
||||||
title="Inventory Manager",
|
active_count=active_count,
|
||||||
stale_count=stale_count,
|
active_worklog_headers=active_worklog_headers,
|
||||||
stale_worklog_headers=stale_worklog_headers,
|
active_worklog_rows=active_worklog_rows,
|
||||||
stale_worklog_rows=[{
|
|
||||||
"id": log.id,
|
|
||||||
"cells": [fn(log) for fn in stale_worklog_headers.values()]
|
|
||||||
} for log in stale_worklogs],
|
|
||||||
labels=labels,
|
labels=labels,
|
||||||
datasets=datasets
|
datasets=datasets
|
||||||
)
|
)
|
||||||
|
@ -416,3 +429,8 @@ def search():
|
||||||
}
|
}
|
||||||
|
|
||||||
return render_template('search.html', title=f"Database Search ({query})" if query else "Database Search", results=results, query=query)
|
return render_template('search.html', title=f"Database Search ({query})" if query else "Database Search", results=results, query=query)
|
||||||
|
|
||||||
|
@main.route('/settings')
|
||||||
|
def settings():
|
||||||
|
brands = db.session.query(Brand).order_by(Brand.name).all()
|
||||||
|
return render_template('settings.html', title="Settings", brands=brands)
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
<!-- templates/worklog.html -->
|
|
||||||
{% extends "layout.html" %}
|
|
||||||
|
|
||||||
{% block title %}{{ title }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -1,3 +1,13 @@
|
||||||
|
{% macro gear(size=24) %}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" fill="currentColor"
|
||||||
|
class="bi bi-gear align-self-center" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492M5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0" />
|
||||||
|
<path
|
||||||
|
d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115z" />
|
||||||
|
</svg>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro home(size=24) %}
|
{% macro home(size=24) %}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" fill="currentColor"
|
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" fill="currentColor"
|
||||||
class="bi bi-house align-self-center" viewBox="0 0 16 16">
|
class="bi bi-house align-self-center" viewBox="0 0 16 16">
|
||||||
|
@ -23,8 +33,8 @@
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro link(size=24) %}
|
{% macro link(size=24) %}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" fill="currentColor" class="bi bi-box-arrow-up-right align-self-center"
|
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" fill="currentColor"
|
||||||
viewBox="0 0 16 16">
|
class="bi bi-box-arrow-up-right align-self-center" viewBox="0 0 16 16">
|
||||||
<path fill-rule="evenodd"
|
<path fill-rule="evenodd"
|
||||||
d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5" />
|
d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5" />
|
||||||
<path fill-rule="evenodd"
|
<path fill-rule="evenodd"
|
||||||
|
@ -50,6 +60,13 @@
|
||||||
</svg>
|
</svg>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro minus(size=24) %}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" fill="currentColor"
|
||||||
|
class="bi bi-dash-lg align-self-center" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M2 8a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11A.5.5 0 0 1 2 8" />
|
||||||
|
</svg>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro motherboard(size=24) %}
|
{% macro motherboard(size=24) %}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" fill="currentColor"
|
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" fill="currentColor"
|
||||||
class="bi bi-motherboard align-self-center" viewBox="0 0 16 16">
|
class="bi bi-motherboard align-self-center" viewBox="0 0 16 16">
|
||||||
|
@ -60,6 +77,14 @@
|
||||||
</svg>
|
</svg>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro plus(size=24) %}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" fill="currentColor"
|
||||||
|
class="bi bi-plus-lg align-self-center" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2" />
|
||||||
|
</svg>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro search(size=24) %}
|
{% macro search(size=24) %}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" fill="currentColor"
|
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" fill="currentColor"
|
||||||
class="bi bi-search align-self-center" viewBox="0 0 16 16">
|
class="bi bi-search align-self-center" viewBox="0 0 16 16">
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in rows %}
|
{% for row in rows %}
|
||||||
<tr {% if entry_route %}onclick="window.location='{{ url_for('main.' + entry_route, id=row.id) }}'"
|
<tr {% if entry_route %}onclick="window.location='{{ url_for('main.' + entry_route, id=row.id) }}'"
|
||||||
style="cursor: pointer;" {% endif %}>
|
style="cursor: pointer;"{% endif %}{% if row['highlight'] %} class="table-info"{% endif %}>
|
||||||
{% for cell in row.cells %}
|
{% for cell in row.cells %}
|
||||||
<td class="text-nowrap{% if cell.type=='bool' %} text-center{% endif %}">
|
<td class="text-nowrap{% if cell.type=='bool' %} text-center{% endif %}">
|
||||||
{% if cell.type == 'bool' %}
|
{% if cell.type == 'bool' %}
|
||||||
|
|
|
@ -8,17 +8,16 @@
|
||||||
<h1 class="display-4">Welcome to Inventory Manager</h1>
|
<h1 class="display-4">Welcome to Inventory Manager</h1>
|
||||||
<p class="lead">Find out about all of your assets.</p>
|
<p class="lead">Find out about all of your assets.</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% if stale_worklog_rows %}
|
{% if active_worklog_rows %}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">Stale Worklogs</h5>
|
<h5 class="card-title">Active Worklogs</h5>
|
||||||
<h6 class="card-subtitle mb-2 text-body-secondary">You have {{ stale_count }} worklogs
|
<h6 class="card-subtitle mb-2 text-body-secondary">You have {{ active_count }} active worklogs.</h6>
|
||||||
that need attention!</h6>
|
|
||||||
{{ tables.render_table(
|
{{ tables.render_table(
|
||||||
headers = stale_worklog_headers,
|
headers = active_worklog_headers,
|
||||||
rows = stale_worklog_rows,
|
rows = active_worklog_rows,
|
||||||
id = 'Stale Worklog',
|
id = 'Active Worklog',
|
||||||
entry_route = 'worklog_entry',
|
entry_route = 'worklog_entry',
|
||||||
per_page = 10
|
per_page = 10
|
||||||
)}}
|
)}}
|
||||||
|
|
|
@ -96,9 +96,10 @@ submit_button=True) }}
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<label for="condition" class="form-label">Condition</label>
|
<label for="condition" class="form-label">Condition</label>
|
||||||
<select name="condition" id="condition" class="form-select">
|
<select name="condition" id="condition" class="form-select">
|
||||||
|
<option>-</option>
|
||||||
{% for condition in ["Working", "Deployed", "Partially Inoperable", "Inoperable", "Unverified",
|
{% for condition in ["Working", "Deployed", "Partially Inoperable", "Inoperable", "Unverified",
|
||||||
"Removed", "Disposed"] %}
|
"Removed", "Disposed"] %}
|
||||||
<option value="{{ condition }}">{{ condition }}</option>
|
<option value="{{ condition }}"{% if item.condition == condition %} selected{% endif %}>{{ condition }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,21 +10,13 @@ title=title
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% if not category %}
|
{% if not category %}
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h2 class="display-6 text-center">Find</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row text-center">
|
|
||||||
{{ links.category_link(endpoint = 'search', label = 'Search', icon_html = icons.search(32)) }}
|
|
||||||
{{ links.category_link(endpoint = 'list_inventory', label = "List", icon_html = icons.table(32)) }}
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h2 class="display-6 text-center mt-5">Browse</h2>
|
<h2 class="display-6 text-center mt-5">Browse</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row text-center">
|
<div class="row text-center">
|
||||||
|
{{ links.category_link(endpoint = 'list_inventory', label = "Full Listing", icon_html = icons.table(32)) }}
|
||||||
{{ links.category_link(endpoint = 'inventory_index', label = "By User", icon_html = icons.user(32), arguments = {'category': 'user'}) }}
|
{{ links.category_link(endpoint = 'inventory_index', label = "By User", icon_html = icons.user(32), arguments = {'category': 'user'}) }}
|
||||||
{{ links.category_link(endpoint = 'inventory_index', label = 'By Location', icon_html = icons.map(32), arguments = {'category': 'location'}) }}
|
{{ links.category_link(endpoint = 'inventory_index', label = 'By Location', icon_html = icons.map(32), arguments = {'category': 'location'}) }}
|
||||||
{{ links.category_link(endpoint = 'inventory_index', label = 'By Type', icon_html = icons.motherboard(32), arguments = {'category': 'type'}) }}
|
{{ links.category_link(endpoint = 'inventory_index', label = 'By Type', icon_html = icons.motherboard(32), arguments = {'category': 'type'}) }}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}Inventory{% endblock %}</title>
|
<title>Inventory Manager{% if title %} - {% endif %}{% block title %}{% endblock %}</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet"
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
integrity="sha384-4Q6Gf2aSP4eDXB8Miphtr37CMZZQ5oXLH2yaXMJ2w8e2ZtHTl7GptT4jmndRuHDT" crossorigin="anonymous">
|
integrity="sha384-4Q6Gf2aSP4eDXB8Miphtr37CMZZQ5oXLH2yaXMJ2w8e2ZtHTl7GptT4jmndRuHDT" crossorigin="anonymous">
|
||||||
<link
|
<link
|
||||||
|
@ -55,6 +55,9 @@
|
||||||
<input type="text" class="form-control me-2" placeholder="Search" name="q" id="search" />
|
<input type="text" class="form-control me-2" placeholder="Search" name="q" id="search" />
|
||||||
<button class="btn btn-primary" type="submit" id="searchButton" disabled>Search</button>
|
<button class="btn btn-primary" type="submit" id="searchButton" disabled>Search</button>
|
||||||
</form>
|
</form>
|
||||||
|
<ul class="navbar-nav ms-2">
|
||||||
|
{{ links.navigation_link(endpoint='settings', label = '', icon_html = icons.gear()) }}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -66,7 +69,7 @@
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/mark.min.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/mark.min.js"
|
||||||
integrity="sha512-5CYOlHXGh6QpOFA/TeTylKLWfB3ftPsde7AnmhuitiTX4K5SqCLBeKro6sPS8ilsz1Q4NRx3v8Ko2IBiszzdww=="
|
integrity="sha512-5CYOlHXGh6QpOFA/TeTylKLWfB3ftPsde7AnmhuitiTX4K5SqCLBeKro6sPS8ilsz1Q4NRx3v8Ko2IBiszzdww=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
<script src="https://cdn.plot.ly/plotly-3.0.1.min.js" charset="utf-8"></script>
|
||||||
<script
|
<script
|
||||||
src="https://cdn.datatables.net/v/bs5/jq-3.7.0/moment-2.29.4/dt-2.3.2/b-3.2.3/b-colvis-3.2.3/b-print-3.2.3/cr-2.1.1/cc-1.0.4/r-3.0.4/rg-1.5.1/rr-1.5.0/sc-2.4.3/sr-1.4.1/datatables.min.js"
|
src="https://cdn.datatables.net/v/bs5/jq-3.7.0/moment-2.29.4/dt-2.3.2/b-3.2.3/b-colvis-3.2.3/b-print-3.2.3/cr-2.1.1/cc-1.0.4/r-3.0.4/rg-1.5.1/rr-1.5.0/sc-2.4.3/sr-1.4.1/datatables.min.js"
|
||||||
integrity="sha384-tNYRX2RiDDDRKCJgPF8Pw3rTxC1GUe1pt5qH1SBmwcazrEUj7Ii4C1Tz9wCCRUI4"
|
integrity="sha384-tNYRX2RiDDDRKCJgPF8Pw3rTxC1GUe1pt5qH1SBmwcazrEUj7Ii4C1Tz9wCCRUI4"
|
||||||
|
|
73
templates/settings.html
Normal file
73
templates/settings.html
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<form method="POST" action="{{ url_for('main.settings') }}">
|
||||||
|
{{ breadcrumbs.breadcrumb_header(
|
||||||
|
title=title,
|
||||||
|
submit_button=True
|
||||||
|
) }}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<label for="brandInput" class="form-label">Brands</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control rounded-bottom-0" id="brandInput" name="brandInput" placeholder="Add a new brand">
|
||||||
|
<button type="button" class="btn btn-primary rounded-bottom-0" id="addBrandButton" onclick="addBrand();" disabled>
|
||||||
|
{{ icons.plus(16) }}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-danger rounded-bottom-0" id="removeBrandButton" onclick="removeSelectedBrands()" disabled>
|
||||||
|
{{ icons.minus(16) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<select class="form-select border-top-0 rounded-top-0" id="brandList" size="10" multiple>
|
||||||
|
{% for brand in brands %}
|
||||||
|
<option value="{{ brand.id }}">{{ brand.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
const brandInput = document.querySelector('#brandInput');
|
||||||
|
const brandList = document.querySelector('#brandList');
|
||||||
|
const addBrandButton = document.querySelector('#addBrandButton');
|
||||||
|
const removeBrandButton = document.querySelector('#removeBrandButton');
|
||||||
|
|
||||||
|
brandInput.addEventListener('input', () => {
|
||||||
|
addBrandButton.disabled = brandInput.value.trim() === '';
|
||||||
|
});
|
||||||
|
|
||||||
|
brandList.addEventListener('change', () => {
|
||||||
|
removeBrandButton.disabled = brandList.selectedOptions.length === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
function sortOptions() {
|
||||||
|
const options = Array.from(brandList.options);
|
||||||
|
options.sort((a, b) => a.text.localeCompare(b.text));
|
||||||
|
brandList.innerHTML = '';
|
||||||
|
options.forEach(option => brandList.appendChild(option));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tempIdCounter = -1;
|
||||||
|
|
||||||
|
function addBrand() {
|
||||||
|
const newBrand = brandInput.value.trim();
|
||||||
|
if (newBrand) {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.textContent = newBrand;
|
||||||
|
option.value = tempIdCounter--;
|
||||||
|
brandList.appendChild(option);
|
||||||
|
brandInput.value = '';
|
||||||
|
addBrandButton.disabled = true;
|
||||||
|
sortOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSelectedBrands() {
|
||||||
|
Array.from(brandList.selectedOptions).forEach(option => option.remove());
|
||||||
|
removeBrandButton.disabled = true;
|
||||||
|
}
|
||||||
|
{% endblock %}
|
Loading…
Add table
Add a link
Reference in a new issue