diff --git a/bricktracker/minifigure_list.py b/bricktracker/minifigure_list.py
index fa73562..40c13d0 100644
--- a/bricktracker/minifigure_list.py
+++ b/bricktracker/minifigure_list.py
@@ -21,6 +21,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
# Queries
all_query: str = 'minifigure/list/all'
+ all_by_owner_query: str = 'minifigure/list/all_by_owner'
damaged_part_query: str = 'minifigure/list/damaged_part'
last_query: str = 'minifigure/list/last'
missing_part_query: str = 'minifigure/list/missing_part'
@@ -42,6 +43,16 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
return self
+ # Load all minifigures by owner
+ def all_by_owner(self, owner_id: str | None = None, /) -> Self:
+ # Save the owner_id parameter
+ self.fields.owner_id = owner_id
+
+ # Load the minifigures from the database
+ self.list(override_query=self.all_by_owner_query)
+
+ return self
+
# Minifigures with a part damaged part
def damaged_part(self, part: str, color: int, /) -> Self:
# Save the parameters to the fields
@@ -83,11 +94,17 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
else:
brickset = None
+ # Prepare template context for owner filtering
+ context = {}
+ if hasattr(self.fields, 'owner_id') and self.fields.owner_id is not None:
+ context['owner_id'] = self.fields.owner_id
+
# Load the sets from the database
for record in super().select(
override_query=override_query,
order=order,
limit=limit,
+ **context
):
minifigure = BrickMinifigure(brickset=brickset, record=record)
@@ -132,6 +149,10 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
if self.brickset is not None:
parameters['id'] = self.brickset.fields.id
+ # Add owner_id parameter for owner filtering
+ if hasattr(self.fields, 'owner_id') and self.fields.owner_id is not None:
+ parameters['owner_id'] = self.fields.owner_id
+
return parameters
# Import the minifigures from Rebrickable
diff --git a/bricktracker/part_list.py b/bricktracker/part_list.py
index a12ef89..96f8653 100644
--- a/bricktracker/part_list.py
+++ b/bricktracker/part_list.py
@@ -23,6 +23,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
# Queries
all_query: str = 'part/list/all'
+ all_by_owner_query: str = 'part/list/all_by_owner'
different_color_query = 'part/list/with_different_color'
last_query: str = 'part/list/last'
minifigure_query: str = 'part/list/from_minifigure'
@@ -46,6 +47,35 @@ class BrickPartList(BrickRecordList[BrickPart]):
return self
+ # Load all parts by owner
+ def all_by_owner(self, owner_id: str | None = None, /) -> Self:
+ # Save the owner_id parameter
+ self.fields.owner_id = owner_id
+
+ # Load the parts from the database
+ self.list(override_query=self.all_by_owner_query)
+
+ 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:
+ # Save the filter parameters
+ if owner_id is not None:
+ self.fields.owner_id = owner_id
+ if color_id is not None:
+ self.fields.color_id = color_id
+
+ # Choose query based on whether owner filtering is needed
+ if owner_id and owner_id != 'all':
+ query = self.all_by_owner_query
+ else:
+ query = self.all_query
+
+ # Load the parts from the database
+ self.list(override_query=query)
+
+ return self
+
# Base part list
def list(
self,
@@ -69,11 +99,19 @@ class BrickPartList(BrickRecordList[BrickPart]):
else:
minifigure = None
+ # Prepare template context for filtering
+ context_vars = {}
+ if hasattr(self.fields, 'owner_id') and self.fields.owner_id is not None:
+ context_vars['owner_id'] = self.fields.owner_id
+ if hasattr(self.fields, 'color_id') and self.fields.color_id is not None:
+ context_vars['color_id'] = self.fields.color_id
+
# Load the sets from the database
for record in super().select(
override_query=override_query,
order=order,
limit=limit,
+ **context_vars
):
part = BrickPart(
brickset=brickset,
diff --git a/bricktracker/sql/minifigure/list/all_by_owner.sql b/bricktracker/sql/minifigure/list/all_by_owner.sql
new file mode 100644
index 0000000..6ae14ba
--- /dev/null
+++ b/bricktracker/sql/minifigure/list/all_by_owner.sql
@@ -0,0 +1,71 @@
+{% extends 'minifigure/base/base.sql' %}
+
+{% block total_missing %}
+SUM(IFNULL("problem_join"."total_missing", 0)) AS "total_missing",
+{% endblock %}
+
+{% block total_damaged %}
+SUM(IFNULL("problem_join"."total_damaged", 0)) AS "total_damaged",
+{% endblock %}
+
+{% block total_quantity %}
+{% if owner_id and owner_id != 'all' %}
+SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN IFNULL("bricktracker_minifigures"."quantity", 0) ELSE 0 END) AS "total_quantity",
+{% else %}
+SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_quantity",
+{% endif %}
+{% endblock %}
+
+{% block total_sets %}
+{% if owner_id and owner_id != 'all' %}
+COUNT(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_minifigures"."id" ELSE NULL END) AS "total_sets"
+{% else %}
+COUNT("bricktracker_minifigures"."id") AS "total_sets"
+{% endif %}
+{% endblock %}
+
+{% block join %}
+-- Join with sets to get owner information
+INNER JOIN "bricktracker_sets"
+ON "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
+
+-- 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"
+
+-- LEFT JOIN + SELECT to avoid messing the total
+LEFT JOIN (
+ SELECT
+ "bricktracker_parts"."id",
+ "bricktracker_parts"."figure",
+ {% if owner_id and owner_id != 'all' %}
+ SUM(CASE WHEN "owner_parts"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."missing" ELSE 0 END) AS "total_missing",
+ SUM(CASE WHEN "owner_parts"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."damaged" ELSE 0 END) AS "total_damaged"
+ {% else %}
+ SUM("bricktracker_parts"."missing") AS "total_missing",
+ SUM("bricktracker_parts"."damaged") AS "total_damaged"
+ {% endif %}
+ FROM "bricktracker_parts"
+ INNER JOIN "bricktracker_sets" AS "parts_sets"
+ ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "parts_sets"."id"
+ LEFT JOIN "bricktracker_set_owners" AS "owner_parts"
+ ON "parts_sets"."id" IS NOT DISTINCT FROM "owner_parts"."id"
+ WHERE "bricktracker_parts"."figure" IS NOT NULL
+ GROUP BY
+ "bricktracker_parts"."id",
+ "bricktracker_parts"."figure"
+) "problem_join"
+ON "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "problem_join"."id"
+AND "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "problem_join"."figure"
+{% endblock %}
+
+{% block where %}
+{% if owner_id and owner_id != 'all' %}
+WHERE "bricktracker_set_owners"."owner_{{ owner_id }}" = 1
+{% endif %}
+{% endblock %}
+
+{% block group %}
+GROUP BY
+ "rebrickable_minifigures"."figure"
+{% endblock %}
\ No newline at end of file
diff --git a/bricktracker/sql/part/colors/list.sql b/bricktracker/sql/part/colors/list.sql
new file mode 100644
index 0000000..a055ec0
--- /dev/null
+++ b/bricktracker/sql/part/colors/list.sql
@@ -0,0 +1,16 @@
+SELECT DISTINCT
+ "rebrickable_parts"."color_id" AS "color_id",
+ "rebrickable_parts"."color_name" AS "color_name",
+ "rebrickable_parts"."color_rgb" AS "color_rgb"
+FROM "rebrickable_parts"
+INNER JOIN "bricktracker_parts"
+ON "bricktracker_parts"."part" IS NOT DISTINCT FROM "rebrickable_parts"."part"
+AND "bricktracker_parts"."color" IS NOT DISTINCT FROM "rebrickable_parts"."color_id"
+{% if owner_id and owner_id != 'all' %}
+INNER JOIN "bricktracker_sets"
+ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
+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 %}
+ORDER BY "rebrickable_parts"."color_name" ASC
\ No newline at end of file
diff --git a/bricktracker/sql/part/list/all.sql b/bricktracker/sql/part/list/all.sql
index 8bb8dcb..bcc729b 100644
--- a/bricktracker/sql/part/list/all.sql
+++ b/bricktracker/sql/part/list/all.sql
@@ -26,6 +26,12 @@ ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_minifigures"."id
AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures"."figure"
{% endblock %}
+{% block where %}
+{% if color_id and color_id != 'all' %}
+WHERE "bricktracker_parts"."color" = {{ color_id }}
+{% endif %}
+{% endblock %}
+
{% block group %}
GROUP BY
"bricktracker_parts"."part",
diff --git a/bricktracker/sql/part/list/all_by_owner.sql b/bricktracker/sql/part/list/all_by_owner.sql
new file mode 100644
index 0000000..ccfc301
--- /dev/null
+++ b/bricktracker/sql/part/list/all_by_owner.sql
@@ -0,0 +1,78 @@
+{% extends 'part/base/base.sql' %}
+
+{% block total_missing %}
+{% if owner_id and owner_id != 'all' %}
+SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."missing" ELSE 0 END) AS "total_missing",
+{% else %}
+SUM("bricktracker_parts"."missing") AS "total_missing",
+{% endif %}
+{% endblock %}
+
+{% block total_damaged %}
+{% if owner_id and owner_id != 'all' %}
+SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."damaged" ELSE 0 END) AS "total_damaged",
+{% else %}
+SUM("bricktracker_parts"."damaged") AS "total_damaged",
+{% endif %}
+{% endblock %}
+
+{% block total_quantity %}
+{% if owner_id and owner_id != 'all' %}
+SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1) ELSE 0 END) AS "total_quantity",
+{% else %}
+SUM("bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_quantity",
+{% endif %}
+{% endblock %}
+
+{% block total_sets %}
+{% if owner_id and owner_id != 'all' %}
+COUNT(DISTINCT CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN "bricktracker_parts"."id" ELSE NULL END) AS "total_sets",
+{% else %}
+COUNT(DISTINCT "bricktracker_parts"."id") AS "total_sets",
+{% endif %}
+{% endblock %}
+
+{% block total_minifigures %}
+{% if owner_id and owner_id != 'all' %}
+SUM(CASE WHEN "bricktracker_set_owners"."owner_{{ owner_id }}" = 1 THEN IFNULL("bricktracker_minifigures"."quantity", 0) ELSE 0 END) AS "total_minifigures"
+{% else %}
+SUM(IFNULL("bricktracker_minifigures"."quantity", 0)) AS "total_minifigures"
+{% endif %}
+{% endblock %}
+
+{% block join %}
+-- Join with sets to get owner information
+INNER JOIN "bricktracker_sets"
+ON "bricktracker_parts"."id" IS NOT DISTINCT FROM "bricktracker_sets"."id"
+
+-- 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"
+
+-- Left join with 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"
+{% endblock %}
+
+{% block where %}
+{% set has_where = false %}
+{% if owner_id and owner_id != 'all' %}
+WHERE "bricktracker_set_owners"."owner_{{ owner_id }}" = 1
+{% set has_where = true %}
+{% endif %}
+{% if color_id and color_id != 'all' %}
+{% if has_where %}
+AND "bricktracker_parts"."color" = {{ color_id }}
+{% else %}
+WHERE "bricktracker_parts"."color" = {{ color_id }}
+{% endif %}
+{% endif %}
+{% endblock %}
+
+{% block group %}
+GROUP BY
+ "bricktracker_parts"."part",
+ "bricktracker_parts"."color",
+ "bricktracker_parts"."spare"
+{% endblock %}
\ No newline at end of file
diff --git a/bricktracker/views/minifigure.py b/bricktracker/views/minifigure.py
index 7123e4a..aeabe7e 100644
--- a/bricktracker/views/minifigure.py
+++ b/bricktracker/views/minifigure.py
@@ -1,9 +1,10 @@
-from flask import Blueprint, render_template
+from flask import Blueprint, render_template, request
from .exceptions import exception_handler
from ..minifigure import BrickMinifigure
from ..minifigure_list import BrickMinifigureList
from ..set_list import BrickSetList, set_metadata_lists
+from ..set_owner_list import BrickSetOwnerList
minifigure_page = Blueprint('minifigure', __name__, url_prefix='/minifigures')
@@ -12,9 +13,23 @@ minifigure_page = Blueprint('minifigure', __name__, url_prefix='/minifigures')
@minifigure_page.route('/', methods=['GET'])
@exception_handler(__file__)
def list() -> str:
+ # Get owner filter from request
+ owner_id = request.args.get('owner', 'all')
+
+ # Get minifigures filtered by owner
+ if owner_id == 'all' or owner_id is None or owner_id == '':
+ minifigures = BrickMinifigureList().all()
+ else:
+ minifigures = BrickMinifigureList().all_by_owner(owner_id)
+
+ # Get list of owners for filter dropdown
+ owners = BrickSetOwnerList.list()
+
return render_template(
'minifigures.html',
- table_collection=BrickMinifigureList().all(),
+ table_collection=minifigures,
+ owners=owners,
+ selected_owner=owner_id,
)
diff --git a/bricktracker/views/part.py b/bricktracker/views/part.py
index fc800c4..f8e72f2 100644
--- a/bricktracker/views/part.py
+++ b/bricktracker/views/part.py
@@ -1,10 +1,12 @@
-from flask import Blueprint, render_template
+from flask import Blueprint, render_template, request
from .exceptions import exception_handler
from ..minifigure_list import BrickMinifigureList
from ..part import BrickPart
from ..part_list import BrickPartList
from ..set_list import BrickSetList, set_metadata_lists
+from ..set_owner_list import BrickSetOwnerList
+from ..sql import BrickSQL
part_page = Blueprint('part', __name__, url_prefix='/parts')
@@ -13,9 +15,32 @@ part_page = Blueprint('part', __name__, url_prefix='/parts')
@part_page.route('/', methods=['GET'])
@exception_handler(__file__)
def list() -> str:
+
+ # Get filter parameters from request
+ owner_id = request.args.get('owner', 'all')
+ color_id = request.args.get('color', 'all')
+
+ # Get parts with filters applied
+ parts = BrickPartList().all_filtered(owner_id, color_id)
+
+ # 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' and owner_id:
+ color_context['owner_id'] = owner_id
+
+ colors = BrickSQL().fetchall('part/colors/list', **color_context)
+
return render_template(
'parts.html',
- table_collection=BrickPartList().all()
+ table_collection=parts,
+ owners=owners,
+ selected_owner=owner_id,
+ colors=colors,
+ selected_color=color_id,
)
diff --git a/static/scripts/minifigures.js b/static/scripts/minifigures.js
new file mode 100644
index 0000000..8da5e76
--- /dev/null
+++ b/static/scripts/minifigures.js
@@ -0,0 +1,148 @@
+// Minifigures page functionality
+function filterByOwner() {
+ const select = document.getElementById('filter-owner');
+ const selectedOwner = select.value;
+ const currentUrl = new URL(window.location);
+
+ if (selectedOwner === 'all') {
+ currentUrl.searchParams.delete('owner');
+ } else {
+ currentUrl.searchParams.set('owner', selectedOwner);
+ }
+
+ window.location.href = currentUrl.toString();
+}
+
+// Keep filters expanded after selection
+function filterByOwnerAndKeepOpen() {
+ // Remember if filters were open
+ const filterSection = document.getElementById('table-filter');
+ const wasOpen = filterSection && filterSection.classList.contains('show');
+
+ filterByOwner();
+
+ // Store the state to restore after page reload
+ if (wasOpen) {
+ sessionStorage.setItem('keepFiltersOpen', 'true');
+ }
+}
+
+// Setup table search and sort functionality
+document.addEventListener("DOMContentLoaded", () => {
+ const searchInput = document.getElementById('table-search');
+ const searchClear = document.getElementById('table-search-clear');
+
+ // Restore filter state after page load
+ if (sessionStorage.getItem('keepFiltersOpen') === 'true') {
+ const filterSection = document.getElementById('table-filter');
+ const filterButton = document.querySelector('[data-bs-target="#table-filter"]');
+
+ if (filterSection && filterButton) {
+ filterSection.classList.add('show');
+ filterButton.setAttribute('aria-expanded', 'true');
+ }
+
+ sessionStorage.removeItem('keepFiltersOpen');
+ }
+
+ if (searchInput && searchClear) {
+ // Wait for table to be initialized by setup_tables
+ setTimeout(() => {
+ const tableElement = document.querySelector('table[data-table="true"]');
+ if (tableElement && window.brickTableInstance) {
+ // Enable custom search for minifigures table
+ window.brickTableInstance.table.searchable = true;
+
+ // Connect search input to table
+ searchInput.addEventListener('input', (e) => {
+ window.brickTableInstance.table.search(e.target.value);
+ });
+
+ // Clear search
+ searchClear.addEventListener('click', () => {
+ searchInput.value = '';
+ window.brickTableInstance.table.search('');
+ });
+
+ // Setup sort buttons
+ setupSortButtons();
+ }
+ }, 100);
+ }
+});
+
+function setupSortButtons() {
+ // Sort button functionality
+ const sortButtons = document.querySelectorAll('[data-sort-attribute]');
+ const clearButton = document.querySelector('[data-sort-clear]');
+
+ sortButtons.forEach(button => {
+ button.addEventListener('click', () => {
+ const attribute = button.dataset.sortAttribute;
+ const isDesc = button.dataset.sortDesc === 'true';
+
+ // Get column index based on attribute
+ const columnMap = {
+ 'name': 1,
+ 'parts': 2,
+ 'quantity': 3,
+ 'missing': 4,
+ 'damaged': 5,
+ 'sets': 6
+ };
+
+ const columnIndex = columnMap[attribute];
+ if (columnIndex !== undefined && window.brickTableInstance) {
+ // Determine sort direction
+ const isCurrentlyActive = button.classList.contains('btn-primary');
+ const currentDirection = button.dataset.currentDirection || (isDesc ? 'desc' : 'asc');
+ const newDirection = isCurrentlyActive ?
+ (currentDirection === 'asc' ? 'desc' : 'asc') :
+ (isDesc ? 'desc' : 'asc');
+
+ // Clear other active buttons
+ sortButtons.forEach(btn => {
+ btn.classList.remove('btn-primary');
+ btn.classList.add('btn-outline-primary');
+ btn.removeAttribute('data-current-direction');
+ });
+
+ // Mark this button as active
+ button.classList.remove('btn-outline-primary');
+ button.classList.add('btn-primary');
+ button.dataset.currentDirection = newDirection;
+
+ // Apply sort using Simple DataTables API
+ window.brickTableInstance.table.columns.sort(columnIndex, newDirection);
+ }
+ });
+ });
+
+ if (clearButton) {
+ clearButton.addEventListener('click', () => {
+ // Clear all sort buttons
+ sortButtons.forEach(btn => {
+ btn.classList.remove('btn-primary');
+ btn.classList.add('btn-outline-primary');
+ btn.removeAttribute('data-current-direction');
+ });
+
+ // Reset table sort - remove all sorting
+ if (window.brickTableInstance) {
+ // Destroy and recreate to clear sorting
+ const tableElement = document.querySelector('#minifigures');
+ const currentPerPage = window.brickTableInstance.table.options.perPage;
+ window.brickTableInstance.table.destroy();
+
+ setTimeout(() => {
+ // Create new instance using the globally available BrickTable class
+ const newInstance = new window.BrickTable(tableElement, currentPerPage);
+ window.brickTableInstance = newInstance;
+
+ // Re-enable search functionality
+ newInstance.table.searchable = true;
+ }, 50);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/static/scripts/parts.js b/static/scripts/parts.js
new file mode 100644
index 0000000..5c1393c
--- /dev/null
+++ b/static/scripts/parts.js
@@ -0,0 +1,190 @@
+// Parts page functionality
+function applyFilters() {
+ const ownerSelect = document.getElementById('filter-owner');
+ const colorSelect = document.getElementById('filter-color');
+ const currentUrl = new URL(window.location);
+
+ // Handle owner filter
+ if (ownerSelect) {
+ const selectedOwner = ownerSelect.value;
+ if (selectedOwner === 'all') {
+ currentUrl.searchParams.delete('owner');
+ } else {
+ currentUrl.searchParams.set('owner', selectedOwner);
+ }
+ }
+
+ // Handle color filter
+ if (colorSelect) {
+ const selectedColor = colorSelect.value;
+ if (selectedColor === 'all') {
+ currentUrl.searchParams.delete('color');
+ } else {
+ currentUrl.searchParams.set('color', selectedColor);
+ }
+ }
+
+ window.location.href = currentUrl.toString();
+}
+
+function setupColorDropdown() {
+ const colorSelect = document.getElementById('filter-color');
+ if (!colorSelect) return;
+
+ // Add color squares to option text
+ const options = colorSelect.querySelectorAll('option[data-color-rgb]');
+ options.forEach(option => {
+ const colorRgb = option.dataset.colorRgb;
+ const colorId = option.dataset.colorId;
+ const colorName = option.textContent.trim();
+
+ if (colorRgb && colorId !== '9999') {
+ // Create a visual indicator (using Unicode square)
+ option.textContent = `${colorName}`; //■
+ //option.style.color = `#${colorRgb}`;
+ }
+ });
+}
+
+// Keep filters expanded after selection
+function applyFiltersAndKeepOpen() {
+ // Remember if filters were open
+ const filterSection = document.getElementById('table-filter');
+ const wasOpen = filterSection && filterSection.classList.contains('show');
+
+ applyFilters();
+
+ // Store the state to restore after page reload
+ if (wasOpen) {
+ sessionStorage.setItem('keepFiltersOpen', 'true');
+ }
+}
+
+function setupSortButtons() {
+ // Sort button functionality
+ const sortButtons = document.querySelectorAll('[data-sort-attribute]');
+ const clearButton = document.querySelector('[data-sort-clear]');
+
+ sortButtons.forEach(button => {
+ button.addEventListener('click', () => {
+ const attribute = button.dataset.sortAttribute;
+ const isDesc = button.dataset.sortDesc === 'true';
+
+ // Get column index based on attribute
+ const columnMap = {
+ 'name': 1,
+ 'color': 2,
+ 'quantity': 3,
+ 'missing': 4,
+ 'damaged': 5,
+ 'sets': 6,
+ 'minifigures': 7
+ };
+
+ const columnIndex = columnMap[attribute];
+ if (columnIndex !== undefined && window.partsTableInstance) {
+ // Determine sort direction
+ const isCurrentlyActive = button.classList.contains('btn-primary');
+ const currentDirection = button.dataset.currentDirection || (isDesc ? 'desc' : 'asc');
+ const newDirection = isCurrentlyActive ?
+ (currentDirection === 'asc' ? 'desc' : 'asc') :
+ (isDesc ? 'desc' : 'asc');
+
+ // Clear other active buttons
+ sortButtons.forEach(btn => {
+ btn.classList.remove('btn-primary');
+ btn.classList.add('btn-outline-primary');
+ btn.removeAttribute('data-current-direction');
+ });
+
+ // Mark this button as active
+ button.classList.remove('btn-outline-primary');
+ button.classList.add('btn-primary');
+ button.dataset.currentDirection = newDirection;
+
+ // Apply sort using Simple DataTables API
+ window.partsTableInstance.table.columns.sort(columnIndex, newDirection);
+ }
+ });
+ });
+
+ if (clearButton) {
+ clearButton.addEventListener('click', () => {
+ // Clear all sort buttons
+ sortButtons.forEach(btn => {
+ btn.classList.remove('btn-primary');
+ btn.classList.add('btn-outline-primary');
+ btn.removeAttribute('data-current-direction');
+ });
+
+ // Reset table sort - remove all sorting
+ if (window.partsTableInstance) {
+ // Destroy and recreate to clear sorting
+ const tableElement = document.querySelector('#parts');
+ const currentPerPage = window.partsTableInstance.table.options.perPage;
+ window.partsTableInstance.table.destroy();
+
+ setTimeout(() => {
+ // Create new instance using the globally available BrickTable class
+ const newInstance = new window.BrickTable(tableElement, currentPerPage);
+ window.partsTableInstance = newInstance;
+
+ // Re-enable search functionality
+ newInstance.table.searchable = true;
+ }, 50);
+ }
+ });
+ }
+}
+
+// Setup table search and sort functionality
+document.addEventListener("DOMContentLoaded", () => {
+ const searchInput = document.getElementById('table-search');
+ const searchClear = document.getElementById('table-search-clear');
+
+ // Setup color dropdown with color squares
+ setupColorDropdown();
+
+ // Restore filter state after page load
+ if (sessionStorage.getItem('keepFiltersOpen') === 'true') {
+ const filterSection = document.getElementById('table-filter');
+ const filterButton = document.querySelector('[data-bs-target="#table-filter"]');
+
+ if (filterSection && filterButton) {
+ filterSection.classList.add('show');
+ filterButton.setAttribute('aria-expanded', 'true');
+ }
+
+ sessionStorage.removeItem('keepFiltersOpen');
+ }
+
+ if (searchInput && searchClear) {
+ // Wait for table to be initialized by setup_tables
+ const setupSearch = () => {
+ const tableElement = document.querySelector('table[data-table="true"]');
+ if (tableElement && window.partsTableInstance) {
+ // Enable custom search for parts table
+ window.partsTableInstance.table.searchable = true;
+
+ // Connect search input to table
+ searchInput.addEventListener('input', (e) => {
+ window.partsTableInstance.table.search(e.target.value);
+ });
+
+ // Clear search
+ searchClear.addEventListener('click', () => {
+ searchInput.value = '';
+ window.partsTableInstance.table.search('');
+ });
+
+ // Setup sort buttons
+ setupSortButtons();
+ } else {
+ // If table instance not ready, try again
+ setTimeout(setupSearch, 100);
+ }
+ };
+
+ setTimeout(setupSearch, 100);
+ }
+});
\ No newline at end of file
diff --git a/static/scripts/table.js b/static/scripts/table.js
index f96f10b..f9cf06f 100644
--- a/static/scripts/table.js
+++ b/static/scripts/table.js
@@ -1,4 +1,5 @@
-class BrickTable {
+// Make BrickTable globally accessible
+window.BrickTable = class BrickTable {
constructor(table, per_page) {
const columns = [];
const no_sort_and_filter = [];
@@ -32,12 +33,17 @@ class BrickTable {
columns.push({ select: number, type: "number", searchable: false });
}
+ // Special configuration for tables with custom search/sort
+ const isMinifiguresTable = table.id === 'minifigures';
+ const isPartsTable = table.id === 'parts';
+ const hasCustomInterface = isMinifiguresTable || isPartsTable;
+
this.table = new simpleDatatables.DataTable(`#${table.id}`, {
columns: columns,
pagerDelta: 1,
perPage: per_page,
perPageSelect: [10, 25, 50, 100, 500, 1000],
- searchable: true,
+ searchable: !hasCustomInterface, // Disable built-in search for tables with custom interface
searchMethod: (table => (terms, cell, row, column, source) => table.search(terms, cell, row, column, source))(this),
searchQuerySeparator: "",
tableRender: () => {
@@ -92,5 +98,13 @@ class BrickTable {
// Helper to setup the tables
const setup_tables = (per_page) => document.querySelectorAll('table[data-table="true"]').forEach(
- el => new BrickTable(el, per_page)
+ el => {
+ const brickTable = new window.BrickTable(el, per_page);
+ // Store the instance globally for external access
+ if (el.id === 'minifigures') {
+ window.brickTableInstance = brickTable;
+ } else if (el.id === 'parts') {
+ window.partsTableInstance = brickTable;
+ }
+ }
);
diff --git a/templates/base.html b/templates/base.html
index 658ef37..b75c678 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -91,6 +91,8 @@
+
+