feat: added filters to /parts, /problems, /minifigures

This commit is contained in:
FrederikBaerentsen
2025-11-06 17:51:43 +01:00
parent 22cdb713d7
commit 61450312ff
20 changed files with 508 additions and 37 deletions
+43
View File
@@ -43,6 +43,19 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
return self
# Load all minifigures with problems filter
def all_filtered(self, /, problems_filter: str = 'all', theme_id: str = 'all', year: str = 'all') -> Self:
context = {}
if problems_filter and problems_filter != 'all':
context['problems_filter'] = problems_filter
if theme_id and theme_id != 'all':
context['theme_id'] = theme_id
if year and year != 'all':
context['year'] = year
self.list(override_query=self.all_query, **context)
return self
# Load all minifigures by owner
def all_by_owner(self, owner_id: str | None = None, /) -> Self:
# Save the owner_id parameter
@@ -53,10 +66,31 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
return self
# Load all minifigures by owner with problems filter
def all_by_owner_filtered(self, /, owner_id: str | None = None, problems_filter: str = 'all', theme_id: str = 'all', year: str = 'all') -> Self:
# Save the owner_id parameter
self.fields.owner_id = owner_id
context = {}
if problems_filter and problems_filter != 'all':
context['problems_filter'] = problems_filter
if theme_id and theme_id != 'all':
context['theme_id'] = theme_id
if year and year != 'all':
context['year'] = year
# Load the minifigures from the database
self.list(override_query=self.all_by_owner_query, **context)
return self
# Load minifigures with pagination support
def all_filtered_paginated(
self,
owner_id: str | None = None,
problems_filter: str = 'all',
theme_id: str = 'all',
year: str = 'all',
search_query: str | None = None,
page: int = 1,
per_page: int = 50,
@@ -74,6 +108,15 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
if search_query:
filter_context['search_query'] = search_query
if problems_filter and problems_filter != 'all':
filter_context['problems_filter'] = problems_filter
if theme_id and theme_id != 'all':
filter_context['theme_id'] = theme_id
if year and year != 'all':
filter_context['year'] = year
# Field mapping for sorting
field_mapping = {
'name': '"rebrickable_minifigures"."name"',
+23 -3
View File
@@ -57,8 +57,8 @@ class BrickPartList(BrickRecordList[BrickPart]):
return self
# Load all parts with filters (owner and/or color)
def all_filtered(self, owner_id: str | None = None, color_id: str | None = None, /) -> Self:
# Load all parts with filters (owner, color, theme, year)
def all_filtered(self, owner_id: str | None = None, color_id: str | None = None, theme_id: str | None = None, year: str | None = None, /) -> Self:
# Save the filter parameters
if owner_id is not None:
self.fields.owner_id = owner_id
@@ -75,6 +75,10 @@ class BrickPartList(BrickRecordList[BrickPart]):
context = {}
if current_app.config.get('SKIP_SPARE_PARTS', False):
context['skip_spare_parts'] = True
if theme_id and theme_id != 'all':
context['theme_id'] = theme_id
if year and year != 'all':
context['year'] = year
# Load the parts from the database
self.list(override_query=query, **context)
@@ -86,6 +90,8 @@ class BrickPartList(BrickRecordList[BrickPart]):
self,
owner_id: str | None = None,
color_id: str | None = None,
theme_id: str | None = None,
year: str | None = None,
search_query: str | None = None,
page: int = 1,
per_page: int = 50,
@@ -102,6 +108,10 @@ class BrickPartList(BrickRecordList[BrickPart]):
if color_id and color_id != 'all':
filter_context['color_id'] = color_id
if theme_id and theme_id != 'all':
filter_context['theme_id'] = theme_id
if year and year != 'all':
filter_context['year'] = year
if search_query:
filter_context['search_query'] = search_query
if current_app.config.get('SKIP_SPARE_PARTS', False):
@@ -238,7 +248,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
return self
def problem_filtered(self, owner_id: str | None = None, color_id: 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, /) -> Self:
# Save the filter parameters for client-side filtering
if owner_id is not None:
self.fields.owner_id = owner_id
@@ -251,6 +261,10 @@ class BrickPartList(BrickRecordList[BrickPart]):
context['owner_id'] = owner_id
if color_id and color_id != 'all':
context['color_id'] = color_id
if theme_id and theme_id != 'all':
context['theme_id'] = theme_id
if year and year != 'all':
context['year'] = year
if current_app.config.get('SKIP_SPARE_PARTS', False):
context['skip_spare_parts'] = True
@@ -263,6 +277,8 @@ class BrickPartList(BrickRecordList[BrickPart]):
self,
owner_id: str | None = None,
color_id: str | None = None,
theme_id: str | None = None,
year: str | None = None,
search_query: str | None = None,
page: int = 1,
per_page: int = 50,
@@ -275,6 +291,10 @@ class BrickPartList(BrickRecordList[BrickPart]):
filter_context['owner_id'] = owner_id
if color_id and color_id != 'all':
filter_context['color_id'] = color_id
if theme_id and theme_id != 'all':
filter_context['theme_id'] = theme_id
if year and year != 'all':
filter_context['year'] = year
if search_query:
filter_context['search_query'] = search_query
if current_app.config.get('SKIP_SPARE_PARTS', False):
@@ -28,6 +28,8 @@ ON "bricktracker_minifigures"."figure" IS NOT DISTINCT FROM "rebrickable_minifig
{% block group %}{% endblock %}
{% block having %}{% endblock %}
{% if order %}
ORDER BY {{ order }}
{% endif %}
+29 -1
View File
@@ -17,6 +17,14 @@ IFNULL(COUNT("bricktracker_minifigures"."id"), 0) AS "total_sets"
{% endblock %}
{% block join %}
{% if theme_id or year %}
-- Join with sets for theme/year filtering
INNER JOIN "bricktracker_sets" AS "filter_sets"
ON "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "filter_sets"."id"
INNER JOIN "rebrickable_sets" AS "filter_rs"
ON "filter_sets"."set" IS NOT DISTINCT FROM "filter_rs"."set"
{% endif %}
-- LEFT JOIN + SELECT to avoid messing the total
LEFT JOIN (
SELECT
@@ -35,8 +43,28 @@ AND "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "problem_join"."figu
{% endblock %}
{% block where %}
WHERE 1=1
{% if theme_id and theme_id != 'all' %}
AND "filter_rs"."theme_id" = {{ theme_id }}
{% endif %}
{% if year and year != 'all' %}
AND "filter_rs"."year" = {{ year }}
{% endif %}
{% if search_query %}
WHERE (LOWER("rebrickable_minifigures"."name") LIKE LOWER('%{{ search_query }}%'))
AND (LOWER("rebrickable_minifigures"."name") LIKE LOWER('%{{ search_query }}%'))
{% endif %}
{% endblock %}
{% block having %}
{% if problems_filter %}
HAVING 1=1
{% if problems_filter == 'missing' %}
AND SUM(IFNULL("problem_join"."total_missing", 0)) > 0
{% elif problems_filter == 'damaged' %}
AND SUM(IFNULL("problem_join"."total_damaged", 0)) > 0
{% elif problems_filter == 'both' %}
AND SUM(IFNULL("problem_join"."total_missing", 0)) > 0 AND SUM(IFNULL("problem_join"."total_damaged", 0)) > 0
{% endif %}
{% endif %}
{% endblock %}
@@ -29,6 +29,10 @@ COUNT("bricktracker_minifigures"."id") AS "total_sets"
INNER JOIN "bricktracker_sets"
ON "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
-- Join with rebrickable sets for theme/year filtering
INNER JOIN "rebrickable_sets"
ON "bricktracker_sets"."set" IS NOT DISTINCT FROM "rebrickable_sets"."set"
-- Left join with set owners (using dynamic columns)
LEFT JOIN "bricktracker_set_owners"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
@@ -64,6 +68,12 @@ AND "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "problem_join"."figu
{% if owner_id and owner_id != 'all' %}
{% set _ = conditions.append('"bricktracker_set_owners"."owner_' ~ owner_id ~ '" = 1') %}
{% endif %}
{% if theme_id and theme_id != 'all' %}
{% set _ = conditions.append('"rebrickable_sets"."theme_id" = ' ~ theme_id) %}
{% endif %}
{% if year and year != 'all' %}
{% set _ = conditions.append('"rebrickable_sets"."year" = ' ~ year) %}
{% endif %}
{% if search_query %}
{% set _ = conditions.append('(LOWER("rebrickable_minifigures"."name") LIKE LOWER(\'%' ~ search_query ~ '%\'))') %}
{% endif %}
@@ -75,4 +85,17 @@ WHERE {{ conditions | join(' AND ') }}
{% block group %}
GROUP BY
"rebrickable_minifigures"."figure"
{% endblock %}
{% block having %}
{% if problems_filter %}
HAVING 1=1
{% if problems_filter == 'missing' %}
AND SUM(IFNULL("problem_join"."total_missing", 0)) > 0
{% elif problems_filter == 'damaged' %}
AND SUM(IFNULL("problem_join"."total_damaged", 0)) > 0
{% elif problems_filter == 'both' %}
AND SUM(IFNULL("problem_join"."total_missing", 0)) > 0 AND SUM(IFNULL("problem_join"."total_damaged", 0)) > 0
{% endif %}
{% endif %}
{% endblock %}
@@ -0,0 +1,16 @@
-- Get distinct themes from minifigures' sets
SELECT DISTINCT
"rebrickable_sets"."theme_id",
COUNT(DISTINCT "bricktracker_minifigures"."figure") as "minifigure_count"
FROM "bricktracker_minifigures"
INNER JOIN "bricktracker_sets"
ON "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
INNER JOIN "rebrickable_sets"
ON "bricktracker_sets"."set" IS NOT DISTINCT FROM "rebrickable_sets"."set"
{% if owner_id and owner_id != 'all' %}
INNER JOIN "bricktracker_set_owners"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
WHERE "bricktracker_set_owners"."owner_{{ owner_id }}" = 1
{% endif %}
GROUP BY "rebrickable_sets"."theme_id"
ORDER BY "rebrickable_sets"."theme_id" ASC
@@ -0,0 +1,16 @@
-- Get distinct years from minifigures' sets
SELECT DISTINCT
"rebrickable_sets"."year",
COUNT(DISTINCT "bricktracker_minifigures"."figure") as "minifigure_count"
FROM "bricktracker_minifigures"
INNER JOIN "bricktracker_sets"
ON "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
INNER JOIN "rebrickable_sets"
ON "bricktracker_sets"."set" IS NOT DISTINCT FROM "rebrickable_sets"."set"
{% if owner_id and owner_id != 'all' %}
INNER JOIN "bricktracker_set_owners"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
WHERE "bricktracker_set_owners"."owner_{{ owner_id }}" = 1
{% endif %}
GROUP BY "rebrickable_sets"."year"
ORDER BY "rebrickable_sets"."year" DESC
+13
View File
@@ -24,6 +24,13 @@ SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_minifigures"
LEFT JOIN "bricktracker_minifigures"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_minifigures"."id"
AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures"."figure"
{% if theme_id or year %}
INNER JOIN "bricktracker_sets" AS "filter_sets"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "filter_sets"."id"
INNER JOIN "rebrickable_sets" AS "filter_rs"
ON "filter_sets"."set" IS NOT DISTINCT FROM "filter_rs"."set"
{% endif %}
{% endblock %}
{% block where %}
@@ -31,6 +38,12 @@ AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures
{% if color_id and color_id != 'all' %}
{% set _ = conditions.append('"bricktracker_parts"."color" = ' ~ color_id) %}
{% endif %}
{% if theme_id and theme_id != 'all' %}
{% set _ = conditions.append('"filter_rs"."theme_id" = ' ~ theme_id) %}
{% endif %}
{% if year and year != 'all' %}
{% set _ = conditions.append('"filter_rs"."year" = ' ~ year) %}
{% 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) %}
+10
View File
@@ -45,6 +45,10 @@ SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_minifigures"
INNER JOIN "bricktracker_sets"
ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
-- Join with rebrickable sets for theme/year filtering
INNER JOIN "rebrickable_sets"
ON "bricktracker_sets"."set" IS NOT DISTINCT FROM "rebrickable_sets"."set"
-- Left join with set owners (using dynamic columns)
LEFT JOIN "bricktracker_set_owners"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
@@ -65,6 +69,12 @@ AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures
{% if color_id and color_id != 'all' %}
{% set _ = conditions.append('"bricktracker_parts"."color" = ' ~ color_id) %}
{% endif %}
{% if theme_id and theme_id != 'all' %}
{% set _ = conditions.append('"rebrickable_sets"."theme_id" = ' ~ theme_id) %}
{% endif %}
{% if year and year != 'all' %}
{% set _ = conditions.append('"rebrickable_sets"."year" = ' ~ year) %}
{% 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) %}
+16
View File
@@ -0,0 +1,16 @@
-- Get distinct themes from parts' sets
SELECT DISTINCT
"rebrickable_sets"."theme_id",
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"
INNER JOIN "rebrickable_sets"
ON "bricktracker_sets"."set" IS NOT DISTINCT FROM "rebrickable_sets"."set"
{% if owner_id and owner_id != 'all' %}
INNER JOIN "bricktracker_set_owners"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
WHERE "bricktracker_set_owners"."owner_{{ owner_id }}" = 1
{% endif %}
GROUP BY "rebrickable_sets"."theme_id"
ORDER BY "rebrickable_sets"."theme_id" ASC
@@ -0,0 +1,19 @@
-- Get distinct themes from problem parts' sets
SELECT DISTINCT
"rebrickable_sets"."theme_id",
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"
INNER JOIN "rebrickable_sets"
ON "bricktracker_sets"."set" IS NOT DISTINCT FROM "rebrickable_sets"."set"
{% 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)
{% if owner_id and owner_id != 'all' %}
AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1
{% endif %}
GROUP BY "rebrickable_sets"."theme_id"
ORDER BY "rebrickable_sets"."theme_id" ASC
+16
View File
@@ -0,0 +1,16 @@
-- Get distinct years from parts' sets
SELECT DISTINCT
"rebrickable_sets"."year",
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"
INNER JOIN "rebrickable_sets"
ON "bricktracker_sets"."set" IS NOT DISTINCT FROM "rebrickable_sets"."set"
{% if owner_id and owner_id != 'all' %}
INNER JOIN "bricktracker_set_owners"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_owners"."id"
WHERE "bricktracker_set_owners"."owner_{{ owner_id }}" = 1
{% endif %}
GROUP BY "rebrickable_sets"."year"
ORDER BY "rebrickable_sets"."year" DESC
@@ -0,0 +1,19 @@
-- Get distinct years from problem parts' sets
SELECT DISTINCT
"rebrickable_sets"."year",
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"
INNER JOIN "rebrickable_sets"
ON "bricktracker_sets"."set" IS NOT DISTINCT FROM "rebrickable_sets"."set"
{% 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)
{% if owner_id and owner_id != 'all' %}
AND "bricktracker_set_owners"."owner_{{ owner_id }}" = 1
{% endif %}
GROUP BY "rebrickable_sets"."year"
ORDER BY "rebrickable_sets"."year" DESC
+34 -2
View File
@@ -16,6 +16,9 @@ minifigure_page = Blueprint('minifigure', __name__, url_prefix='/minifigures')
def list() -> str:
# Get filter parameters from request
owner_id = request.args.get('owner', 'all')
problems_filter = request.args.get('problems', 'all')
theme_id = request.args.get('theme', 'all')
year = request.args.get('year', 'all')
search_query, sort_field, sort_order, page = get_request_params()
# Get pagination configuration
@@ -26,6 +29,9 @@ def list() -> str:
# PAGINATION MODE - Server-side pagination with search
minifigures, total_count = BrickMinifigureList().all_filtered_paginated(
owner_id=owner_id,
problems_filter=problems_filter,
theme_id=theme_id,
year=year,
search_query=search_query,
page=page,
per_page=per_page,
@@ -37,19 +43,45 @@ def list() -> str:
else:
# ORIGINAL MODE - Single page with all data for client-side search
if owner_id == 'all' or owner_id is None or owner_id == '':
minifigures = BrickMinifigureList().all()
minifigures = BrickMinifigureList().all_filtered(problems_filter=problems_filter, theme_id=theme_id, year=year)
else:
minifigures = BrickMinifigureList().all_by_owner(owner_id)
minifigures = BrickMinifigureList().all_by_owner_filtered(owner_id=owner_id, problems_filter=problems_filter, theme_id=theme_id, year=year)
pagination_context = None
# Get list of owners for filter dropdown
owners = BrickSetOwnerList.list()
# Prepare context for dependent filters
filter_context = {}
if owner_id != 'all' and owner_id:
filter_context['owner_id'] = owner_id
# Get list of themes for filter dropdown
from ..theme_list import BrickThemeList
from ..sql import BrickSQL
theme_list = BrickThemeList()
themes_data = BrickSQL().fetchall('minifigure/themes/list', **filter_context)
themes = []
for theme_data in themes_data:
theme = theme_list.get(theme_data['theme_id'])
themes.append({
'theme_id': theme_data['theme_id'],
'theme_name': theme.name if theme else f"Theme {theme_data['theme_id']}"
})
# Get list of years for filter dropdown
years = BrickSQL().fetchall('minifigure/years/list', **filter_context)
template_context = {
'table_collection': minifigures,
'owners': owners,
'selected_owner': owner_id,
'selected_problems': problems_filter,
'themes': themes,
'selected_theme': theme_id,
'years': years,
'selected_year': year,
'search_query': search_query,
'use_pagination': use_pagination,
'current_sort': sort_field,
+60 -15
View File
@@ -19,6 +19,8 @@ def list() -> str:
# Get filter parameters from request
owner_id = request.args.get('owner', 'all')
color_id = request.args.get('color', 'all')
theme_id = request.args.get('theme', 'all')
year = request.args.get('year', 'all')
search_query, sort_field, sort_order, page = get_request_params()
# Get pagination configuration
@@ -30,6 +32,8 @@ def list() -> str:
parts, total_count = BrickPartList().all_filtered_paginated(
owner_id=owner_id,
color_id=color_id,
theme_id=theme_id,
year=year,
search_query=search_query,
page=page,
per_page=per_page,
@@ -40,19 +44,34 @@ def list() -> 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().all_filtered(owner_id, color_id)
parts = BrickPartList().all_filtered(owner_id, color_id, theme_id, year)
pagination_context = None
# Get list of owners for filter dropdown
owners = BrickSetOwnerList.list()
# Get list of colors for filter dropdown
# Prepare context for color query (filter by owner if selected)
color_context = {}
# Prepare context for dependent filters
filter_context = {}
if owner_id != 'all' and owner_id:
color_context['owner_id'] = owner_id
filter_context['owner_id'] = owner_id
colors = BrickSQL().fetchall('part/colors/list', **color_context)
# Get list of colors for filter dropdown
colors = BrickSQL().fetchall('part/colors/list', **filter_context)
# Get list of themes for filter dropdown
from ..theme_list import BrickThemeList
theme_list = BrickThemeList()
themes_data = BrickSQL().fetchall('part/themes/list', **filter_context)
themes = []
for theme_data in themes_data:
theme = theme_list.get(theme_data['theme_id'])
themes.append({
'theme_id': theme_data['theme_id'],
'theme_name': theme.name if theme else f"Theme {theme_data['theme_id']}"
})
# Get list of years for filter dropdown
years = BrickSQL().fetchall('part/years/list', **filter_context)
template_context = {
'table_collection': parts,
@@ -60,6 +79,10 @@ def list() -> str:
'selected_owner': owner_id,
'colors': colors,
'selected_color': color_id,
'themes': themes,
'selected_theme': theme_id,
'years': years,
'selected_year': year,
'search_query': search_query,
'use_pagination': use_pagination,
'current_sort': sort_field,
@@ -80,6 +103,8 @@ def problem() -> str:
# Get filter parameters from request
owner_id = request.args.get('owner', 'all')
color_id = request.args.get('color', 'all')
theme_id = request.args.get('theme', 'all')
year = request.args.get('year', 'all')
search_query, sort_field, sort_order, page = get_request_params()
# Get pagination configuration
@@ -91,6 +116,8 @@ def problem() -> str:
parts, total_count = BrickPartList().problem_paginated(
owner_id=owner_id,
color_id=color_id,
theme_id=theme_id,
year=year,
search_query=search_query,
page=page,
per_page=per_page,
@@ -101,20 +128,34 @@ 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)
parts = BrickPartList().problem_filtered(owner_id, color_id, theme_id, year)
pagination_context = None
# Get list of owners for filter dropdown
owners = BrickSetOwnerList.list()
# Get list of colors for filter dropdown
# Prepare context for color query (filter by owner if selected)
color_context = {}
if owner_id != 'all':
color_context['owner_id'] = owner_id
# Prepare context for dependent filters
filter_context = {}
if owner_id != 'all' and owner_id:
filter_context['owner_id'] = owner_id
# Get colors from problem parts (following same pattern as parts page)
colors = BrickSQL().fetchall('part/colors/list_problem', **color_context)
# Get list of colors for filter dropdown (problem parts only)
colors = BrickSQL().fetchall('part/colors/list_problem', **filter_context)
# Get list of themes for filter dropdown (problem parts only)
from ..theme_list import BrickThemeList
theme_list = BrickThemeList()
themes_data = BrickSQL().fetchall('part/themes/list_problem', **filter_context)
themes = []
for theme_data in themes_data:
theme = theme_list.get(theme_data['theme_id'])
themes.append({
'theme_id': theme_data['theme_id'],
'theme_name': theme.name if theme else f"Theme {theme_data['theme_id']}"
})
# Get list of years for filter dropdown (problem parts only)
years = BrickSQL().fetchall('part/years/list_problem', **filter_context)
return render_template(
'problem.html',
@@ -127,7 +168,11 @@ def problem() -> str:
owners=owners,
colors=colors,
selected_owner=owner_id,
selected_color=color_id
selected_color=color_id,
themes=themes,
selected_theme=theme_id,
years=years,
selected_year=year
)
+19 -1
View File
@@ -314,10 +314,13 @@ window.updateUrlParams = function(params, resetPage = true) {
window.location.href = currentUrl.toString();
};
// Shared filter application (supports owner and color filters)
// Shared filter application (supports owner, color, theme, year, 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 problemsSelect = document.getElementById('filter-problems');
const params = {};
// Handle owner filter
@@ -330,6 +333,21 @@ window.applyPageFilters = function(tableId) {
params.color = colorSelect.value;
}
// Handle theme filter
if (themeSelect) {
params.theme = themeSelect.value;
}
// Handle year filter
if (yearSelect) {
params.year = yearSelect.value;
}
// Handle problems filter (for minifigures page)
if (problemsSelect) {
params.problems = problemsSelect.value;
}
// Update URL with new parameters
window.updateUrlParams(params, true);
};
+55 -9
View File
@@ -6,15 +6,51 @@ function isPaginationMode() {
return tableElement && tableElement.getAttribute('data-table') === 'false';
}
function filterByOwner() {
const select = document.getElementById('filter-owner');
const selectedOwner = select.value;
function applyFilters() {
const ownerSelect = document.getElementById('filter-owner');
const problemsSelect = document.getElementById('filter-problems');
const themeSelect = document.getElementById('filter-theme');
const yearSelect = document.getElementById('filter-year');
const currentUrl = new URL(window.location);
if (selectedOwner === 'all') {
currentUrl.searchParams.delete('owner');
} else {
currentUrl.searchParams.set('owner', selectedOwner);
// Apply owner filter
if (ownerSelect) {
const selectedOwner = ownerSelect.value;
if (selectedOwner === 'all') {
currentUrl.searchParams.delete('owner');
} else {
currentUrl.searchParams.set('owner', selectedOwner);
}
}
// Apply problems filter
if (problemsSelect) {
const selectedProblems = problemsSelect.value;
if (selectedProblems === 'all') {
currentUrl.searchParams.delete('problems');
} else {
currentUrl.searchParams.set('problems', selectedProblems);
}
}
// Apply theme filter
if (themeSelect) {
const selectedTheme = themeSelect.value;
if (selectedTheme === 'all') {
currentUrl.searchParams.delete('theme');
} else {
currentUrl.searchParams.set('theme', selectedTheme);
}
}
// Apply year filter
if (yearSelect) {
const selectedYear = yearSelect.value;
if (selectedYear === 'all') {
currentUrl.searchParams.delete('year');
} else {
currentUrl.searchParams.set('year', selectedYear);
}
}
// Reset to page 1 when filtering
@@ -25,15 +61,25 @@ function filterByOwner() {
window.location.href = currentUrl.toString();
}
// Legacy function for compatibility
function filterByOwner() {
applyFilters();
}
// Initialize filter and sort states for minifigures page
function initializeCollapsibleStates() {
initializePageCollapsibleStates('minifigures');
}
// Keep filters expanded after selection
function filterByOwnerAndKeepOpen() {
function applyFiltersAndKeepOpen() {
preserveCollapsibleStateOnChange('table-filter', 'minifigures-filter-state');
filterByOwner();
applyFilters();
}
// Legacy function for compatibility
function filterByOwnerAndKeepOpen() {
applyFiltersAndKeepOpen();
}
// Setup table search and sort functionality
+39 -2
View File
@@ -1,9 +1,9 @@
<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 flex-grow-1">
<div class="col-12 col-md-6 col-lg-3 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="filterByOwnerAndKeepOpen()" autocomplete="off">
<select id="filter-owner" class="form-select" onchange="applyFiltersAndKeepOpen()" autocomplete="off">
<option value="all" {% if selected_owner == 'all' %}selected{% endif %}>All owners</option>
{% for owner in owners %}
<option value="{{ owner.fields.id }}" {% if selected_owner == owner.fields.id %}selected{% endif %}>{{ owner.fields.name }}</option>
@@ -12,4 +12,41 @@
</div>
</div>
{% endif %}
<div class="col-12 col-md-6 col-lg-3 flex-grow-1">
<div class="input-group">
<span class="input-group-text"><i class="ri-error-warning-line"></i><span class="ms-1 d-none d-md-inline"> Problems</span></span>
<select id="filter-problems" class="form-select" onchange="applyFiltersAndKeepOpen()" autocomplete="off">
<option value="all" {% if selected_problems == 'all' %}selected{% endif %}>All minifigures</option>
<option value="missing" {% if selected_problems == 'missing' %}selected{% endif %}>With missing parts</option>
<option value="damaged" {% if selected_problems == 'damaged' %}selected{% endif %}>With damaged parts</option>
<option value="both" {% if selected_problems == 'both' %}selected{% endif %}>With missing and damaged parts</option>
</select>
</div>
</div>
{% if themes | length %}
<div class="col-12 col-md-6 col-lg-3 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">
<option value="all" {% if selected_theme == 'all' %}selected{% endif %}>All themes</option>
{% for theme in themes %}
<option value="{{ theme.theme_id }}" {% if selected_theme == theme.theme_id|string %}selected{% endif %}>{{ theme.theme_name }}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
{% if years | length %}
<div class="col-12 col-md-6 col-lg-3 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">
<option value="all" {% if selected_year == 'all' %}selected{% endif %}>All years</option>
{% for year in years %}
<option value="{{ year.year }}" {% if selected_year == year.year|string %}selected{% endif %}>{{ year.year }}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
</div>
+28 -2
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 flex-grow-1">
<div class="col-12 col-md-6 col-lg-3 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 flex-grow-1">
<div class="col-12 col-md-6 col-lg-3 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">
@@ -27,4 +27,30 @@
</div>
</div>
{% endif %}
{% if themes | length %}
<div class="col-12 col-md-6 col-lg-3 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">
<option value="all" {% if selected_theme == 'all' %}selected{% endif %}>All themes</option>
{% for theme in themes %}
<option value="{{ theme.theme_id }}" {% if selected_theme == theme.theme_id|string %}selected{% endif %}>{{ theme.theme_name }}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
{% if years | length %}
<div class="col-12 col-md-6 col-lg-3 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">
<option value="all" {% if selected_year == 'all' %}selected{% endif %}>All years</option>
{% for year in years %}
<option value="{{ year.year }}" {% if selected_year == year.year|string %}selected{% endif %}>{{ year.year }}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
</div>
+28 -2
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 flex-grow-1">
<div class="col-12 col-md-6 col-lg-3 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 flex-grow-1">
<div class="col-12 col-md-6 col-lg-3 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">
@@ -27,4 +27,30 @@
</div>
</div>
{% endif %}
{% if themes | length %}
<div class="col-12 col-md-6 col-lg-3 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">
<option value="all" {% if selected_theme == 'all' %}selected{% endif %}>All themes</option>
{% for theme in themes %}
<option value="{{ theme.theme_id }}" {% if selected_theme == theme.theme_id|string %}selected{% endif %}>{{ theme.theme_name }}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
{% if years | length %}
<div class="col-12 col-md-6 col-lg-3 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">
<option value="all" {% if selected_year == 'all' %}selected{% endif %}>All years</option>
{% for year in years %}
<option value="{{ year.year }}" {% if selected_year == year.year|string %}selected{% endif %}>{{ year.year }}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
</div>