feat(prob): added filter for tag and storage

This commit is contained in:
2025-11-06 18:06:27 +01:00
parent 61450312ff
commit 7567cb51af
7 changed files with 110 additions and 8 deletions
+11 -1
View File
@@ -248,7 +248,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
return self
def problem_filtered(self, owner_id: str | None = None, color_id: str | None = None, theme_id: str | None = None, year: str | None = None, /) -> Self:
def problem_filtered(self, owner_id: str | None = None, color_id: str | None = None, theme_id: str | None = None, year: str | None = None, storage_id: str | None = None, tag_id: str | None = None, /) -> Self:
# Save the filter parameters for client-side filtering
if owner_id is not None:
self.fields.owner_id = owner_id
@@ -265,6 +265,10 @@ class BrickPartList(BrickRecordList[BrickPart]):
context['theme_id'] = theme_id
if year and year != 'all':
context['year'] = year
if storage_id and storage_id != 'all':
context['storage_id'] = storage_id
if tag_id and tag_id != 'all':
context['tag_id'] = tag_id
if current_app.config.get('SKIP_SPARE_PARTS', False):
context['skip_spare_parts'] = True
@@ -279,6 +283,8 @@ class BrickPartList(BrickRecordList[BrickPart]):
color_id: str | None = None,
theme_id: str | None = None,
year: str | None = None,
storage_id: str | None = None,
tag_id: str | None = None,
search_query: str | None = None,
page: int = 1,
per_page: int = 50,
@@ -295,6 +301,10 @@ class BrickPartList(BrickRecordList[BrickPart]):
filter_context['theme_id'] = theme_id
if year and year != 'all':
filter_context['year'] = year
if storage_id and storage_id != 'all':
filter_context['storage_id'] = storage_id
if tag_id and tag_id != 'all':
filter_context['tag_id'] = tag_id
if search_query:
filter_context['search_query'] = search_query
if current_app.config.get('SKIP_SPARE_PARTS', False):
+12
View File
@@ -53,6 +53,12 @@ ON "bricktracker_sets"."set" IS NOT DISTINCT FROM "rebrickable_sets"."set"
LEFT JOIN "bricktracker_set_owners"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
-- Left join with set tags (for tag filtering)
{% if tag_id and tag_id != 'all' %}
LEFT JOIN "bricktracker_set_tags"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_tags"."id"
{% endif %}
-- Left join with minifigures
LEFT JOIN "bricktracker_minifigures"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_minifigures"."id"
@@ -75,6 +81,12 @@ AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures
{% if year and year != 'all' %}
{% set _ = conditions.append('"rebrickable_sets"."year" = ' ~ year) %}
{% endif %}
{% if storage_id and storage_id != 'all' %}
{% set _ = conditions.append('"bricktracker_sets"."storage" = \'' ~ storage_id ~ '\'') %}
{% endif %}
{% if tag_id and tag_id != 'all' %}
{% set _ = conditions.append('"bricktracker_set_tags"."tag_' ~ tag_id ~ '" = 1') %}
{% endif %}
{% if search_query %}
{% set search_condition = '(LOWER("rebrickable_parts"."name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("rebrickable_parts"."color_name") LIKE LOWER(\'%' ~ search_query ~ '%\') OR LOWER("bricktracker_parts"."part") LIKE LOWER(\'%' ~ search_query ~ '%\'))' %}
{% set _ = conditions.append(search_condition) %}
@@ -0,0 +1,21 @@
-- Get distinct storages from problem parts' sets
SELECT DISTINCT
"bricktracker_sets"."storage" AS "storage_id",
"bricktracker_metadata_storages"."name" AS "storage_name",
COUNT(DISTINCT "bricktracker_parts"."part") as "part_count"
FROM "bricktracker_parts"
INNER JOIN "bricktracker_sets"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
LEFT JOIN "bricktracker_metadata_storages"
ON "bricktracker_sets"."storage" IS NOT DISTINCT FROM "bricktracker_metadata_storages"."id"
{% if owner_id and owner_id != 'all' %}
INNER JOIN "bricktracker_set_owners"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
{% endif %}
WHERE ("bricktracker_parts"."missing" > 0 OR "bricktracker_parts"."damaged" > 0)
AND "bricktracker_sets"."storage" IS NOT NULL
{% if owner_id and owner_id != 'all' %}
AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1
{% endif %}
GROUP BY "bricktracker_sets"."storage", "bricktracker_metadata_storages"."name"
ORDER BY "bricktracker_metadata_storages"."name" ASC
@@ -0,0 +1,7 @@
-- Get list of all tags (simplified - filtering happens at application level)
-- Tags use dynamic columns in bricktracker_set_tags, making direct SQL filtering complex
SELECT
"bricktracker_metadata_tags"."id" AS "tag_id",
"bricktracker_metadata_tags"."name" AS "tag_name"
FROM "bricktracker_metadata_tags"
ORDER BY "bricktracker_metadata_tags"."name" ASC
+16 -2
View File
@@ -105,6 +105,8 @@ def problem() -> str:
color_id = request.args.get('color', 'all')
theme_id = request.args.get('theme', 'all')
year = request.args.get('year', 'all')
storage_id = request.args.get('storage', 'all')
tag_id = request.args.get('tag', 'all')
search_query, sort_field, sort_order, page = get_request_params()
# Get pagination configuration
@@ -118,6 +120,8 @@ def problem() -> str:
color_id=color_id,
theme_id=theme_id,
year=year,
storage_id=storage_id,
tag_id=tag_id,
search_query=search_query,
page=page,
per_page=per_page,
@@ -128,7 +132,7 @@ def problem() -> str:
pagination_context = build_pagination_context(page, per_page, total_count, is_mobile)
else:
# ORIGINAL MODE - Single page with all data for client-side search
parts = BrickPartList().problem_filtered(owner_id, color_id, theme_id, year)
parts = BrickPartList().problem_filtered(owner_id, color_id, theme_id, year, storage_id, tag_id)
pagination_context = None
# Get list of owners for filter dropdown
@@ -157,6 +161,12 @@ def problem() -> str:
# Get list of years for filter dropdown (problem parts only)
years = BrickSQL().fetchall('part/years/list_problem', **filter_context)
# Get list of storages for filter dropdown (problem parts only)
storages = BrickSQL().fetchall('part/storages/list_problem', **filter_context)
# Get list of tags for filter dropdown (problem parts only)
tags = BrickSQL().fetchall('part/tags/list_problem', **filter_context)
return render_template(
'problem.html',
table_collection=parts,
@@ -172,7 +182,11 @@ def problem() -> str:
themes=themes,
selected_theme=theme_id,
years=years,
selected_year=year
selected_year=year,
storages=storages,
selected_storage=storage_id,
tags=tags,
selected_tag=tag_id
)
+13 -1
View File
@@ -314,12 +314,14 @@ window.updateUrlParams = function(params, resetPage = true) {
window.location.href = currentUrl.toString();
};
// Shared filter application (supports owner, color, theme, year, and problems filters)
// Shared filter application (supports owner, color, theme, year, storage, tag, and problems filters)
window.applyPageFilters = function(tableId) {
const ownerSelect = document.getElementById('filter-owner');
const colorSelect = document.getElementById('filter-color');
const themeSelect = document.getElementById('filter-theme');
const yearSelect = document.getElementById('filter-year');
const storageSelect = document.getElementById('filter-storage');
const tagSelect = document.getElementById('filter-tag');
const problemsSelect = document.getElementById('filter-problems');
const params = {};
@@ -343,6 +345,16 @@ window.applyPageFilters = function(tableId) {
params.year = yearSelect.value;
}
// Handle storage filter
if (storageSelect) {
params.storage = storageSelect.value;
}
// Handle tag filter
if (tagSelect) {
params.tag = tagSelect.value;
}
// Handle problems filter (for minifigures page)
if (problemsSelect) {
params.problems = problemsSelect.value;
+30 -4
View File
@@ -1,6 +1,6 @@
<div id="table-filter" class="collapse {% if config['SHOW_GRID_FILTERS'] %}show{% endif %} row row-cols-lg-auto g-1 justify-content-center align-items-center">
{% if owners | length %}
<div class="col-12 col-md-6 col-lg-3 flex-grow-1">
<div class="col-12 col-md-6 col-lg-2 flex-grow-1">
<div class="input-group">
<span class="input-group-text"><i class="ri-user-line"></i><span class="ms-1 d-none d-md-inline"> Owner</span></span>
<select id="filter-owner" class="form-select" onchange="applyFiltersAndKeepOpen()" autocomplete="off">
@@ -13,7 +13,7 @@
</div>
{% endif %}
{% if colors | length %}
<div class="col-12 col-md-6 col-lg-3 flex-grow-1">
<div class="col-12 col-md-6 col-lg-2 flex-grow-1">
<div class="input-group">
<span class="input-group-text"><i class="ri-palette-line"></i><span class="ms-1 d-none d-md-inline"> Color</span></span>
<select id="filter-color" class="form-select" onchange="applyFiltersAndKeepOpen()" autocomplete="off">
@@ -28,7 +28,7 @@
</div>
{% endif %}
{% if themes | length %}
<div class="col-12 col-md-6 col-lg-3 flex-grow-1">
<div class="col-12 col-md-6 col-lg-2 flex-grow-1">
<div class="input-group">
<span class="input-group-text"><i class="ri-price-tag-3-line"></i><span class="ms-1 d-none d-md-inline"> Theme</span></span>
<select id="filter-theme" class="form-select" onchange="applyFiltersAndKeepOpen()" autocomplete="off">
@@ -41,7 +41,7 @@
</div>
{% endif %}
{% if years | length %}
<div class="col-12 col-md-6 col-lg-3 flex-grow-1">
<div class="col-12 col-md-6 col-lg-2 flex-grow-1">
<div class="input-group">
<span class="input-group-text"><i class="ri-calendar-line"></i><span class="ms-1 d-none d-md-inline"> Year</span></span>
<select id="filter-year" class="form-select" onchange="applyFiltersAndKeepOpen()" autocomplete="off">
@@ -53,4 +53,30 @@
</div>
</div>
{% endif %}
{% if storages | length %}
<div class="col-12 col-md-6 col-lg-2 flex-grow-1">
<div class="input-group">
<span class="input-group-text"><i class="ri-inbox-line"></i><span class="ms-1 d-none d-md-inline"> Storage</span></span>
<select id="filter-storage" class="form-select" onchange="applyFiltersAndKeepOpen()" autocomplete="off">
<option value="all" {% if selected_storage == 'all' %}selected{% endif %}>All storages</option>
{% for storage in storages %}
<option value="{{ storage.storage_id }}" {% if selected_storage == storage.storage_id %}selected{% endif %}>{{ storage.storage_name }}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
{% if tags | length %}
<div class="col-12 col-md-6 col-lg-2 flex-grow-1">
<div class="input-group">
<span class="input-group-text"><i class="ri-price-tag-line"></i><span class="ms-1 d-none d-md-inline"> Tag</span></span>
<select id="filter-tag" class="form-select" onchange="applyFiltersAndKeepOpen()" autocomplete="off">
<option value="all" {% if selected_tag == 'all' %}selected{% endif %}>All tags</option>
{% for tag in tags %}
<option value="{{ tag.tag_id }}" {% if selected_tag == tag.tag_id %}selected{% endif %}>{{ tag.tag_name }}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
</div>