forked from FrederikBaerentsen/BrickTracker
feat(filter): added not equal to filter on sets page, so it is possible to filter for not-tags, not-status, not-year etc
This commit is contained in:
@@ -92,7 +92,15 @@ class BrickSetList(BrickRecordList[BrickSet]):
|
||||
# Convert theme name to theme ID for filtering
|
||||
theme_id_filter = None
|
||||
if theme_filter:
|
||||
theme_id_filter = self._theme_name_to_id(theme_filter)
|
||||
# Check if this is a NOT filter
|
||||
if theme_filter.startswith('-'):
|
||||
# Extract the actual theme value without the "-" prefix
|
||||
actual_theme = theme_filter[1:]
|
||||
theme_id = self._theme_name_to_id(actual_theme)
|
||||
# Re-add the "-" prefix to the theme ID
|
||||
theme_id_filter = f'-{theme_id}' if theme_id else None
|
||||
else:
|
||||
theme_id_filter = self._theme_name_to_id(theme_filter)
|
||||
|
||||
# Check if any filters are applied
|
||||
has_filters = any([status_filter, theme_id_filter, owner_filter, purchase_location_filter, storage_filter, tag_filter, year_filter, duplicate_filter])
|
||||
|
||||
@@ -8,20 +8,36 @@ AND (LOWER("rebrickable_sets"."name") LIKE LOWER('%{{ search_query }}%')
|
||||
{% endif %}
|
||||
|
||||
{% if theme_filter %}
|
||||
{% if theme_filter is string and theme_filter.startswith('-') %}
|
||||
AND "rebrickable_sets"."theme_id" != {{ theme_filter[1:] }}
|
||||
{% else %}
|
||||
AND "rebrickable_sets"."theme_id" = {{ theme_filter }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if year_filter %}
|
||||
{% if year_filter is string and year_filter.startswith('-') %}
|
||||
AND "rebrickable_sets"."year" != {{ year_filter[1:] }}
|
||||
{% else %}
|
||||
AND "rebrickable_sets"."year" = {{ year_filter }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if storage_filter %}
|
||||
{% if storage_filter.startswith('-') %}
|
||||
AND ("bricktracker_sets"."storage" IS NULL OR "bricktracker_sets"."storage" != '{{ storage_filter[1:] }}')
|
||||
{% else %}
|
||||
AND "bricktracker_sets"."storage" = '{{ storage_filter }}'
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if purchase_location_filter %}
|
||||
{% if purchase_location_filter.startswith('-') %}
|
||||
AND ("bricktracker_sets"."purchase_location" IS NULL OR "bricktracker_sets"."purchase_location" != '{{ purchase_location_filter[1:] }}')
|
||||
{% else %}
|
||||
AND "bricktracker_sets"."purchase_location" = '{{ purchase_location_filter }}'
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if status_filter %}
|
||||
{% if status_filter == 'has-missing' %}
|
||||
@@ -52,7 +68,13 @@ AND NOT EXISTS (
|
||||
{% endif %}
|
||||
|
||||
{% if owner_filter %}
|
||||
{% if owner_filter.startswith('owner-') %}
|
||||
{% if owner_filter.startswith('-owner-') %}
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "bricktracker_set_owners"
|
||||
WHERE "bricktracker_set_owners"."id" = "bricktracker_sets"."id"
|
||||
AND "bricktracker_set_owners"."{{ owner_filter[1:].replace('-', '_') }}" = 1
|
||||
)
|
||||
{% elif owner_filter.startswith('owner-') %}
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM "bricktracker_set_owners"
|
||||
WHERE "bricktracker_set_owners"."id" = "bricktracker_sets"."id"
|
||||
@@ -62,7 +84,13 @@ AND EXISTS (
|
||||
{% endif %}
|
||||
|
||||
{% if tag_filter %}
|
||||
{% if tag_filter.startswith('tag-') %}
|
||||
{% if tag_filter.startswith('-tag-') %}
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "bricktracker_set_tags"
|
||||
WHERE "bricktracker_set_tags"."id" = "bricktracker_sets"."id"
|
||||
AND "bricktracker_set_tags"."{{ tag_filter[1:].replace('-', '_') }}" = 1
|
||||
)
|
||||
{% elif tag_filter.startswith('tag-') %}
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM "bricktracker_set_tags"
|
||||
WHERE "bricktracker_set_tags"."id" = "bricktracker_sets"."id"
|
||||
|
||||
@@ -91,28 +91,52 @@ AND (LOWER("rebrickable_sets"."name") LIKE LOWER('%{{ search_query }}%')
|
||||
{% endif %}
|
||||
|
||||
{% if theme_filter %}
|
||||
{% if theme_filter is string and theme_filter.startswith('-') %}
|
||||
AND "rebrickable_sets"."theme_id" != {{ theme_filter[1:] }}
|
||||
{% else %}
|
||||
AND "rebrickable_sets"."theme_id" = {{ theme_filter }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if year_filter %}
|
||||
{% if year_filter is string and year_filter.startswith('-') %}
|
||||
AND "rebrickable_sets"."year" != {{ year_filter[1:] }}
|
||||
{% else %}
|
||||
AND "rebrickable_sets"."year" = {{ year_filter }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if storage_filter %}
|
||||
{% if storage_filter.startswith('-') %}
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "bricktracker_sets" bs_filter
|
||||
WHERE bs_filter."set" = "rebrickable_sets"."set"
|
||||
AND bs_filter."storage" = '{{ storage_filter[1:] }}'
|
||||
)
|
||||
{% else %}
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM "bricktracker_sets" bs_filter
|
||||
WHERE bs_filter."set" = "rebrickable_sets"."set"
|
||||
AND bs_filter."storage" = '{{ storage_filter }}'
|
||||
)
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if purchase_location_filter %}
|
||||
{% if purchase_location_filter.startswith('-') %}
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM "bricktracker_sets" bs_filter
|
||||
WHERE bs_filter."set" = "rebrickable_sets"."set"
|
||||
AND bs_filter."purchase_location" = '{{ purchase_location_filter[1:] }}'
|
||||
)
|
||||
{% else %}
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM "bricktracker_sets" bs_filter
|
||||
WHERE bs_filter."set" = "rebrickable_sets"."set"
|
||||
AND bs_filter."purchase_location" = '{{ purchase_location_filter }}'
|
||||
)
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if status_filter %}
|
||||
{% if status_filter == 'has-storage' %}
|
||||
|
||||
@@ -67,28 +67,33 @@ class BrickGridFilter {
|
||||
|
||||
// Build filters
|
||||
for (const select of this.selects) {
|
||||
if (select.value != "") {
|
||||
// Get the actual filter value (includes "-" prefix if toggle is in NOT mode)
|
||||
const filterValue = typeof BrickFilterToggle !== 'undefined'
|
||||
? BrickFilterToggle.getFilterValue(select)
|
||||
: select.value;
|
||||
|
||||
if (filterValue != "") {
|
||||
// Multi-attribute filter
|
||||
switch (select.dataset.filter) {
|
||||
// List contains values
|
||||
case "value":
|
||||
options.filters.push({
|
||||
attribute: select.dataset.filterAttribute,
|
||||
value: select.value,
|
||||
value: filterValue,
|
||||
})
|
||||
break;
|
||||
|
||||
// List contains metadata attribute name, looking for true/false
|
||||
case "metadata":
|
||||
if (select.value.startsWith("-")) {
|
||||
if (filterValue.startsWith("-")) {
|
||||
options.filters.push({
|
||||
attribute: select.value.substring(1),
|
||||
attribute: filterValue.substring(1),
|
||||
bool: true,
|
||||
value: "0"
|
||||
})
|
||||
} else {
|
||||
options.filters.push({
|
||||
attribute: select.value,
|
||||
attribute: filterValue,
|
||||
bool: true,
|
||||
value: "1"
|
||||
});
|
||||
@@ -130,23 +135,58 @@ class BrickGridFilter {
|
||||
|
||||
// Value check
|
||||
// For consolidated cards, attributes may be comma or pipe-separated (e.g., "storage1,storage2" or "storage1|storage2")
|
||||
else if (attribute == null) {
|
||||
// Hide if attribute is missing
|
||||
current.parentElement.classList.add("d-none");
|
||||
return;
|
||||
} else if (attribute.includes(',') || attribute.includes('|')) {
|
||||
// Handle comma or pipe-separated values (consolidated cards)
|
||||
const separator = attribute.includes('|') ? '|' : ',';
|
||||
const values = attribute.split(separator).map(v => v.trim());
|
||||
if (!values.includes(filter.value)) {
|
||||
current.parentElement.classList.add("d-none");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Handle single values (regular cards)
|
||||
if (attribute != filter.value) {
|
||||
current.parentElement.classList.add("d-none");
|
||||
return;
|
||||
else {
|
||||
// Check if this is a NOT filter (value starts with "-")
|
||||
const isNot = filter.value.startsWith('-');
|
||||
const actualValue = isNot ? filter.value.substring(1) : filter.value;
|
||||
|
||||
if (attribute == null) {
|
||||
// If attribute is missing
|
||||
if (isNot) {
|
||||
// NOT filter: missing attribute means it doesn't match, so SHOW it
|
||||
// (e.g., NOT "Basement" and has no storage = show)
|
||||
// Continue to next filter
|
||||
} else {
|
||||
// Regular filter: missing attribute means hide
|
||||
current.parentElement.classList.add("d-none");
|
||||
return;
|
||||
}
|
||||
} else if (attribute.includes(',') || attribute.includes('|')) {
|
||||
// Handle comma or pipe-separated values (consolidated cards)
|
||||
const separator = attribute.includes('|') ? '|' : ',';
|
||||
const values = attribute.split(separator).map(v => v.trim());
|
||||
const hasValue = values.includes(actualValue);
|
||||
|
||||
if (isNot) {
|
||||
// NOT filter: hide if ANY of the values match
|
||||
if (hasValue) {
|
||||
current.parentElement.classList.add("d-none");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Regular filter: hide if NONE of the values match
|
||||
if (!hasValue) {
|
||||
current.parentElement.classList.add("d-none");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle single values (regular cards)
|
||||
const matches = (attribute == actualValue);
|
||||
|
||||
if (isNot) {
|
||||
// NOT filter: hide if it matches
|
||||
if (matches) {
|
||||
current.parentElement.classList.add("d-none");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Regular filter: hide if it doesn't match
|
||||
if (!matches) {
|
||||
current.parentElement.classList.add("d-none");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
// Filter toggle for NOT filtering
|
||||
class BrickFilterToggle {
|
||||
constructor() {
|
||||
// Find all filter toggle buttons
|
||||
this.toggles = document.querySelectorAll('.filter-toggle');
|
||||
|
||||
// Initialize each toggle
|
||||
this.toggles.forEach(toggle => {
|
||||
this.initializeToggle(toggle);
|
||||
});
|
||||
}
|
||||
|
||||
initializeToggle(toggle) {
|
||||
const targetId = toggle.dataset.filterTarget;
|
||||
const targetSelect = document.getElementById(targetId);
|
||||
|
||||
if (!targetSelect) {
|
||||
console.error(`Filter toggle: Target select #${targetId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we need to initialize in NOT mode based on URL parameters
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const filterParam = this.getFilterParamName(targetId);
|
||||
const filterValue = urlParams.get(filterParam);
|
||||
|
||||
// Initialize the NOT mode flag
|
||||
if (filterValue && filterValue.startsWith('-')) {
|
||||
targetSelect.dataset.notMode = 'true';
|
||||
this.setToggleState(toggle, 'not-equals');
|
||||
} else {
|
||||
targetSelect.dataset.notMode = 'false';
|
||||
this.setToggleState(toggle, 'equals');
|
||||
}
|
||||
|
||||
// Add click event listener to toggle button
|
||||
toggle.addEventListener('click', () => {
|
||||
this.handleToggleClick(toggle, targetSelect);
|
||||
});
|
||||
|
||||
// Add change event listener to the select
|
||||
targetSelect.addEventListener('change', () => {
|
||||
// If select is cleared (empty value), reset toggle to equals mode
|
||||
const selectValue = targetSelect.options[targetSelect.selectedIndex]?.value || '';
|
||||
if (!selectValue) {
|
||||
targetSelect.dataset.notMode = 'false';
|
||||
this.setToggleState(toggle, 'equals');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getFilterParamName(selectId) {
|
||||
// Map select IDs to URL parameter names
|
||||
const mapping = {
|
||||
'grid-status': 'status',
|
||||
'grid-theme': 'theme',
|
||||
'grid-owner': 'owner',
|
||||
'grid-storage': 'storage',
|
||||
'grid-purchase-location': 'purchase_location',
|
||||
'grid-tag': 'tag',
|
||||
'grid-year': 'year'
|
||||
};
|
||||
return mapping[selectId] || selectId.replace('grid-', '');
|
||||
}
|
||||
|
||||
handleToggleClick(toggle, targetSelect) {
|
||||
const selectValue = targetSelect.options[targetSelect.selectedIndex]?.value || '';
|
||||
|
||||
// Don't toggle if no value is selected
|
||||
if (!selectValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle the NOT mode
|
||||
const isNotMode = targetSelect.dataset.notMode === 'true';
|
||||
targetSelect.dataset.notMode = isNotMode ? 'false' : 'true';
|
||||
|
||||
// Update toggle button visual state
|
||||
this.setToggleState(toggle, isNotMode ? 'equals' : 'not-equals');
|
||||
|
||||
// Trigger change event on the select to update the grid filter
|
||||
targetSelect.dispatchEvent(new Event('change'));
|
||||
}
|
||||
|
||||
setToggleState(toggle, mode) {
|
||||
toggle.dataset.filterMode = mode;
|
||||
const icon = toggle.querySelector('i');
|
||||
|
||||
if (mode === 'not-equals') {
|
||||
icon.className = 'ri-indeterminate-circle-line';
|
||||
toggle.classList.remove('btn-outline-secondary');
|
||||
toggle.classList.add('btn-outline-danger');
|
||||
toggle.title = 'NOT equals (click to toggle)';
|
||||
} else {
|
||||
icon.className = 'ri-equal-line';
|
||||
toggle.classList.remove('btn-outline-danger');
|
||||
toggle.classList.add('btn-outline-secondary');
|
||||
toggle.title = 'Equals (click to toggle)';
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to get the actual filter value (with "-" prefix if in NOT mode)
|
||||
static getFilterValue(select) {
|
||||
const selectValue = select.options[select.selectedIndex]?.value || '';
|
||||
const isNotMode = select.dataset.notMode === 'true';
|
||||
|
||||
if (selectValue && isNotMode && !selectValue.startsWith('-')) {
|
||||
return '-' + selectValue;
|
||||
}
|
||||
return selectValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
new BrickFilterToggle();
|
||||
});
|
||||
+49
-23
@@ -145,12 +145,15 @@ function initializeFilterDropdowns() {
|
||||
// Set filter dropdown values from URL parameters
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// Helper function to strip "-" prefix from filter values
|
||||
const stripNotPrefix = (value) => value && value.startsWith('-') ? value.substring(1) : value;
|
||||
|
||||
// Set each filter dropdown value if the parameter exists
|
||||
const yearParam = urlParams.get('year');
|
||||
if (yearParam) {
|
||||
const yearDropdown = document.getElementById('grid-year');
|
||||
if (yearDropdown) {
|
||||
yearDropdown.value = yearParam;
|
||||
yearDropdown.value = stripNotPrefix(yearParam);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,14 +161,15 @@ function initializeFilterDropdowns() {
|
||||
if (themeParam) {
|
||||
const themeDropdown = document.getElementById('grid-theme');
|
||||
if (themeDropdown) {
|
||||
const cleanTheme = stripNotPrefix(themeParam);
|
||||
// Try to set the theme value directly first (for theme names)
|
||||
themeDropdown.value = themeParam;
|
||||
themeDropdown.value = cleanTheme;
|
||||
|
||||
// If that didn't work and the param is numeric (theme ID),
|
||||
// try to find the corresponding theme name by looking at cards
|
||||
if (themeDropdown.value !== themeParam && /^\d+$/.test(themeParam)) {
|
||||
if (themeDropdown.value !== cleanTheme && /^\d+$/.test(cleanTheme)) {
|
||||
// Look for a card with this theme ID and get its theme name
|
||||
const cardWithTheme = document.querySelector(`[data-theme-id="${themeParam}"]`);
|
||||
const cardWithTheme = document.querySelector(`[data-theme-id="${cleanTheme}"]`);
|
||||
if (cardWithTheme) {
|
||||
const themeName = cardWithTheme.getAttribute('data-theme');
|
||||
if (themeName) {
|
||||
@@ -180,7 +184,7 @@ function initializeFilterDropdowns() {
|
||||
if (statusParam) {
|
||||
const statusDropdown = document.getElementById('grid-status');
|
||||
if (statusDropdown) {
|
||||
statusDropdown.value = statusParam;
|
||||
statusDropdown.value = stripNotPrefix(statusParam);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +192,7 @@ function initializeFilterDropdowns() {
|
||||
if (ownerParam) {
|
||||
const ownerDropdown = document.getElementById('grid-owner');
|
||||
if (ownerDropdown) {
|
||||
ownerDropdown.value = ownerParam;
|
||||
ownerDropdown.value = stripNotPrefix(ownerParam);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +200,7 @@ function initializeFilterDropdowns() {
|
||||
if (purchaseLocationParam) {
|
||||
const purchaseLocationDropdown = document.getElementById('grid-purchase-location');
|
||||
if (purchaseLocationDropdown) {
|
||||
purchaseLocationDropdown.value = purchaseLocationParam;
|
||||
purchaseLocationDropdown.value = stripNotPrefix(purchaseLocationParam);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +208,7 @@ function initializeFilterDropdowns() {
|
||||
if (storageParam) {
|
||||
const storageDropdown = document.getElementById('grid-storage');
|
||||
if (storageDropdown) {
|
||||
storageDropdown.value = storageParam;
|
||||
storageDropdown.value = stripNotPrefix(storageParam);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +216,7 @@ function initializeFilterDropdowns() {
|
||||
if (tagParam) {
|
||||
const tagDropdown = document.getElementById('grid-tag');
|
||||
if (tagDropdown) {
|
||||
tagDropdown.value = tagParam;
|
||||
tagDropdown.value = stripNotPrefix(tagParam);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,6 +226,9 @@ function initializeClientSideFilterDropdowns() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
let needsFiltering = false;
|
||||
|
||||
// Helper function to strip "-" prefix from filter values
|
||||
const stripNotPrefix = (value) => value && value.startsWith('-') ? value.substring(1) : value;
|
||||
|
||||
// Check if we have any filter parameters to avoid flash of all content
|
||||
const hasFilterParams = urlParams.has('year') || urlParams.has('theme') || urlParams.has('storage') || urlParams.has('purchase_location');
|
||||
|
||||
@@ -238,7 +245,7 @@ function initializeClientSideFilterDropdowns() {
|
||||
if (yearParam) {
|
||||
const yearDropdown = document.getElementById('grid-year');
|
||||
if (yearDropdown) {
|
||||
yearDropdown.value = yearParam;
|
||||
yearDropdown.value = stripNotPrefix(yearParam);
|
||||
needsFiltering = true;
|
||||
}
|
||||
}
|
||||
@@ -248,16 +255,17 @@ function initializeClientSideFilterDropdowns() {
|
||||
if (themeParam) {
|
||||
const themeDropdown = document.getElementById('grid-theme');
|
||||
if (themeDropdown) {
|
||||
if (/^\d+$/.test(themeParam)) {
|
||||
const cleanTheme = stripNotPrefix(themeParam);
|
||||
if (/^\d+$/.test(cleanTheme)) {
|
||||
// Theme parameter is an ID, need to convert to theme name by looking at cards
|
||||
const themeNameFromId = findThemeNameById(themeParam);
|
||||
const themeNameFromId = findThemeNameById(cleanTheme);
|
||||
if (themeNameFromId) {
|
||||
themeDropdown.value = themeNameFromId;
|
||||
needsFiltering = true;
|
||||
}
|
||||
} else {
|
||||
// Theme parameter is already a name
|
||||
themeDropdown.value = themeParam.toLowerCase();
|
||||
themeDropdown.value = cleanTheme.toLowerCase();
|
||||
needsFiltering = true;
|
||||
}
|
||||
}
|
||||
@@ -268,7 +276,7 @@ function initializeClientSideFilterDropdowns() {
|
||||
if (storageParam) {
|
||||
const storageDropdown = document.getElementById('grid-storage');
|
||||
if (storageDropdown) {
|
||||
storageDropdown.value = storageParam;
|
||||
storageDropdown.value = stripNotPrefix(storageParam);
|
||||
needsFiltering = true;
|
||||
}
|
||||
}
|
||||
@@ -278,7 +286,7 @@ function initializeClientSideFilterDropdowns() {
|
||||
if (purchaseLocationParam) {
|
||||
const purchaseLocationDropdown = document.getElementById('grid-purchase-location');
|
||||
if (purchaseLocationDropdown) {
|
||||
purchaseLocationDropdown.value = purchaseLocationParam;
|
||||
purchaseLocationDropdown.value = stripNotPrefix(purchaseLocationParam);
|
||||
needsFiltering = true;
|
||||
}
|
||||
}
|
||||
@@ -343,14 +351,30 @@ function setupPaginationFilterDropdowns() {
|
||||
function performServerFilter() {
|
||||
const currentUrl = new URL(window.location);
|
||||
|
||||
// Get all filter values
|
||||
const statusFilter = document.getElementById('grid-status')?.value || '';
|
||||
const themeFilter = document.getElementById('grid-theme')?.value || '';
|
||||
const yearFilter = document.getElementById('grid-year')?.value || '';
|
||||
const ownerFilter = document.getElementById('grid-owner')?.value || '';
|
||||
const purchaseLocationFilter = document.getElementById('grid-purchase-location')?.value || '';
|
||||
const storageFilter = document.getElementById('grid-storage')?.value || '';
|
||||
const tagFilter = document.getElementById('grid-tag')?.value || '';
|
||||
// Get all filter values (using BrickFilterToggle helper to include "-" prefix if in NOT mode)
|
||||
const statusSelect = document.getElementById('grid-status');
|
||||
const themeSelect = document.getElementById('grid-theme');
|
||||
const yearSelect = document.getElementById('grid-year');
|
||||
const ownerSelect = document.getElementById('grid-owner');
|
||||
const purchaseLocationSelect = document.getElementById('grid-purchase-location');
|
||||
const storageSelect = document.getElementById('grid-storage');
|
||||
const tagSelect = document.getElementById('grid-tag');
|
||||
|
||||
// Helper to safely get filter value with NOT mode support
|
||||
const getFilterValue = (select) => {
|
||||
if (!select) return '';
|
||||
return typeof BrickFilterToggle !== 'undefined'
|
||||
? BrickFilterToggle.getFilterValue(select)
|
||||
: select.value;
|
||||
};
|
||||
|
||||
const statusFilter = getFilterValue(statusSelect);
|
||||
const themeFilter = getFilterValue(themeSelect);
|
||||
const yearFilter = getFilterValue(yearSelect);
|
||||
const ownerFilter = getFilterValue(ownerSelect);
|
||||
const purchaseLocationFilter = getFilterValue(purchaseLocationSelect);
|
||||
const storageFilter = getFilterValue(storageSelect);
|
||||
const tagFilter = getFilterValue(tagSelect);
|
||||
|
||||
// Update URL parameters
|
||||
if (statusFilter) {
|
||||
@@ -746,6 +770,8 @@ function initializeClearFiltersButton() {
|
||||
const dropdown = document.getElementById(dropdownId);
|
||||
if (dropdown) {
|
||||
dropdown.value = '';
|
||||
// Trigger change event to reset toggle button state
|
||||
dropdown.dispatchEvent(new Event('change'));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
<!-- BrickTracker scripts -->
|
||||
<script src="{{ url_for('static', filename='scripts/collapsible-state.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='scripts/changer.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='scripts/grid/filter_toggle.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='scripts/grid/filter.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='scripts/grid/grid.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='scripts/grid/sort.js') }}"></script>
|
||||
|
||||
@@ -64,3 +64,14 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro filter_toggle(filter_id) %}
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary filter-toggle"
|
||||
id="{{ filter_id }}-toggle"
|
||||
data-filter-target="{{ filter_id }}"
|
||||
data-filter-mode="equals"
|
||||
title="Toggle between equals and not equals">
|
||||
<i class="ri-equal-line"></i>
|
||||
</button>
|
||||
{% endmacro %}
|
||||
|
||||
+20
-17
@@ -1,3 +1,4 @@
|
||||
{% import 'macro/form.html' as form %}
|
||||
<div id="grid-filter" class="collapse {% if config['SHOW_GRID_FILTERS'] %}show{% endif %} row row-cols-lg-auto g-1 justify-content-center align-items-center pb-2">
|
||||
<div class="col-12 flex-grow-1">
|
||||
<label class="visually-hidden" for="grid-status">Status</label>
|
||||
@@ -8,26 +9,22 @@
|
||||
autocomplete="off">
|
||||
<option value="" {% if not current_status_filter %}selected{% endif %}>All</option>
|
||||
{% if not config['HIDE_TABLE_MISSING_PARTS'] %}
|
||||
<option value="has-missing" {% if current_status_filter == 'has-missing' %}selected{% endif %}>Has missing pieces</option>
|
||||
<option value="-has-missing" {% if current_status_filter == '-has-missing' %}selected{% endif %}>Has NO missing pieces</option>
|
||||
<option value="has-missing" {% if current_status_filter == 'has-missing' or current_status_filter == '-has-missing' %}selected{% endif %}>Missing pieces</option>
|
||||
{% endif %}
|
||||
{% if not config['HIDE_TABLE_DAMAGED_PARTS'] %}
|
||||
<option value="has-damaged" {% if current_status_filter == 'has-damaged' %}selected{% endif %}>Has damaged pieces</option>
|
||||
<option value="-has-damaged" {% if current_status_filter == '-has-damaged' %}selected{% endif %}>Has NO damaged pieces</option>
|
||||
<option value="has-damaged" {% if current_status_filter == 'has-damaged' or current_status_filter == '-has-damaged' %}selected{% endif %}>Damaged pieces</option>
|
||||
{% endif %}
|
||||
{% if not config['HIDE_SET_INSTRUCTIONS'] %}
|
||||
<option value="-has-missing-instructions" {% if current_status_filter == '-has-missing-instructions' %}selected{% endif %}>Has instructions</option>
|
||||
<option value="has-missing-instructions" {% if current_status_filter == 'has-missing-instructions' %}selected{% endif %}>Is MISSING instructions</option>
|
||||
<option value="has-missing-instructions" {% if current_status_filter == 'has-missing-instructions' or current_status_filter == '-has-missing-instructions' %}selected{% endif %}>Missing instructions</option>
|
||||
{% endif %}
|
||||
{% if brickset_storages | length %}
|
||||
<option value="has-storage" {% if current_status_filter == 'has-storage' %}selected{% endif %}>Is in storage</option>
|
||||
<option value="-has-storage" {% if current_status_filter == '-has-storage' %}selected{% endif %}>Is NOT in storage</option>
|
||||
<option value="has-storage" {% if current_status_filter == 'has-storage' or current_status_filter == '-has-storage' %}selected{% endif %}>In storage</option>
|
||||
{% endif %}
|
||||
{% for status in brickset_statuses %}
|
||||
<option value="{{ status.as_dataset() }}" {% if current_status_filter == status.as_dataset() %}selected{% endif %}>{{ status.fields.name }}</option>
|
||||
<option value="-{{ status.as_dataset() }}" {% if current_status_filter == ('-' + status.as_dataset()) %}selected{% endif %}>NOT: {{ status.fields.name }}</option>
|
||||
<option value="{{ status.as_dataset() }}" {% if current_status_filter == status.as_dataset() or current_status_filter == ('-' + status.as_dataset()) %}selected{% endif %}>{{ status.fields.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{{ form.filter_toggle('grid-status') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 flex-grow-1">
|
||||
@@ -39,9 +36,10 @@
|
||||
autocomplete="off">
|
||||
<option value="" {% if not current_theme_filter %}selected{% endif %}>All</option>
|
||||
{% for theme in collection.themes %}
|
||||
<option value="{{ theme | lower }}" {% if current_theme_filter == (theme | lower) %}selected{% endif %}>{{ theme }}</option>
|
||||
<option value="{{ theme | lower }}" {% if current_theme_filter == (theme | lower) or current_theme_filter == ('-' + (theme | lower)) %}selected{% endif %}>{{ theme }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{{ form.filter_toggle('grid-theme') }}
|
||||
</div>
|
||||
</div>
|
||||
{% if brickset_owners | length %}
|
||||
@@ -57,12 +55,13 @@
|
||||
<option value="{{ owner.as_dataset() }}" {% if current_owner_filter == owner.as_dataset() %}selected{% endif %}>{{ owner.fields.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{{ form.filter_toggle('grid-owner') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if brickset_purchase_locations | length %}
|
||||
<div class="col-12 flex-grow-1">
|
||||
<label class="visually-hidden" for="grid-owner">Purchase location</label>
|
||||
<label class="visually-hidden" for="grid-purchase-location">Purchase location</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="ri-building-line"></i><span class="ms-1 d-none d-md-inline"> Purchase location</span></span>
|
||||
<select id="grid-purchase-location" class="form-select"
|
||||
@@ -70,9 +69,10 @@
|
||||
autocomplete="off">
|
||||
<option value="" {% if not current_purchase_location_filter %}selected{% endif %}>All</option>
|
||||
{% for purchase_location in brickset_purchase_locations %}
|
||||
<option value="{{ purchase_location.fields.id }}" {% if current_purchase_location_filter == purchase_location.fields.id %}selected{% endif %}>{{ purchase_location.fields.name }}</option>
|
||||
<option value="{{ purchase_location.fields.id }}" {% if current_purchase_location_filter == purchase_location.fields.id or current_purchase_location_filter == ('-' + purchase_location.fields.id) %}selected{% endif %}>{{ purchase_location.fields.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{{ form.filter_toggle('grid-purchase-location') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -80,7 +80,7 @@
|
||||
<div class="w-100"></div>
|
||||
{% if brickset_storages | length %}
|
||||
<div class="col-12 flex-grow-1">
|
||||
<label class="visually-hidden" for="grid-owner">Storage</label>
|
||||
<label class="visually-hidden" for="grid-storage">Storage</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="ri-archive-2-line"></i><span class="ms-1 d-none d-md-inline"> Storage</span></span>
|
||||
<select id="grid-storage" class="form-select"
|
||||
@@ -88,9 +88,10 @@
|
||||
autocomplete="off">
|
||||
<option value="" {% if not current_storage_filter %}selected{% endif %}>All</option>
|
||||
{% for storage in brickset_storages %}
|
||||
<option value="{{ storage.fields.id }}" {% if current_storage_filter == storage.fields.id %}selected{% endif %}>{{ storage.fields.name }}</option>
|
||||
<option value="{{ storage.fields.id }}" {% if current_storage_filter == storage.fields.id or current_storage_filter == ('-' + storage.fields.id) %}selected{% endif %}>{{ storage.fields.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{{ form.filter_toggle('grid-storage') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -104,9 +105,10 @@
|
||||
autocomplete="off">
|
||||
<option value="" {% if not current_tag_filter %}selected{% endif %}>All</option>
|
||||
{% for tag in brickset_tags %}
|
||||
<option value="{{ tag.as_dataset() }}" {% if current_tag_filter == tag.as_dataset() %}selected{% endif %}>{{ tag.fields.name }}</option>
|
||||
<option value="{{ tag.as_dataset() }}" {% if current_tag_filter == tag.as_dataset() or current_tag_filter == ('-' + tag.as_dataset()) %}selected{% endif %}>{{ tag.fields.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{{ form.filter_toggle('grid-tag') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -119,9 +121,10 @@
|
||||
autocomplete="off">
|
||||
<option value="" {% if not current_year_filter %}selected{% endif %}>All</option>
|
||||
{% for year in collection.years %}
|
||||
<option value="{{ year }}" {% if current_year_filter == year %}selected{% endif %}>{{ year }}</option>
|
||||
<option value="{{ year }}" {% if current_year_filter == year or current_year_filter == ('-' + year|string) %}selected{% endif %}>{{ year }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{{ form.filter_toggle('grid-year') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-auto">
|
||||
|
||||
Reference in New Issue
Block a user