aboutsummaryrefslogtreecommitdiffstats
path: root/app/components.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/components.js')
-rw-r--r--app/components.js379
1 files changed, 303 insertions, 76 deletions
diff --git a/app/components.js b/app/components.js
index 3308b62..42d7242 100644
--- a/app/components.js
+++ b/app/components.js
@@ -441,6 +441,57 @@ export class Widgets {
}
/**
+ * Create a dual range filter input
+ * @param {string} id
+ * @param {string} labelText
+ * @param {number} minValue
+ * @param {number} maxValue
+ * @param {number} currentMin
+ * @param {number} currentMax
+ * @param {number} step
+ * @param {(min: number, max: number) => void} onChange
+ * @returns {HTMLElement}
+ */
+ static rangeFilter(id, labelText, minValue, maxValue, currentMin, currentMax, step, onChange) {
+ return Dom.div(
+ new DomOptions({
+ children: [
+ Dom.label(
+ id,
+ labelText,
+ new DomOptions({
+ styles: {
+ fontSize: "0.85rem",
+ fontWeight: "bold",
+ marginBottom: "0.25rem",
+ },
+ }),
+ ),
+ Dom.range(
+ minValue,
+ maxValue,
+ currentMin,
+ currentMax,
+ step,
+ (min, max) => onChange(min, max),
+ new DomOptions({
+ id,
+ styles: {
+ marginBottom: "1rem",
+ },
+ }),
+ ),
+ ],
+ styles: {
+ display: "flex",
+ flexDirection: "column",
+ marginBottom: "1.75rem",
+ },
+ }),
+ );
+ }
+
+ /**
* Create a number filter input
* @param {string} id
* @param {string} labelText
@@ -506,6 +557,8 @@ export class Sidebar {
#onColorChange;
/** @type {(param: string) => void} */
#onAreaColorChange;
+ /** @type {HTMLElement|null} */
+ #filtersSectionElement;
/**
* @param {House[]|null} allHouses
@@ -542,6 +595,7 @@ export class Sidebar {
initialParam,
);
+ this.#filtersSectionElement = null;
this.#rootElement = this.#render();
}
@@ -754,12 +808,36 @@ export class Sidebar {
}),
);
}
-
/**
* @param {Filters} filters
* @param {() => void} onChange
*/
static filtersSection(filters, onChange) {
+ // Calculate reasonable ranges based on typical values
+ const priceRange = {
+ max: 1000000,
+ min: 0,
+ step: 10000,
+ };
+
+ const yearRange = {
+ max: new Date().getFullYear(),
+ min: 1800,
+ step: 1,
+ };
+
+ const areaRange = {
+ max: 500,
+ min: 0,
+ step: 10,
+ };
+
+ const lotRange = {
+ max: 5000,
+ min: 0,
+ step: 100,
+ };
+
return Dom.div(
new DomOptions({
children: [
@@ -774,33 +852,172 @@ export class Sidebar {
},
}),
),
+
+ // Price Range
+ Dom.div(
+ new DomOptions({
+ children: [
+ Dom.label(
+ "price-range",
+ "Price Range (€)",
+ new DomOptions({
+ styles: {
+ fontSize: "0.85rem",
+ fontWeight: "bold",
+ marginBottom: "0.25rem",
+ },
+ }),
+ ),
+ Dom.range(
+ priceRange.min,
+ priceRange.max,
+ filters.minPrice || priceRange.min,
+ filters.maxPrice === Number.POSITIVE_INFINITY ? priceRange.max : filters.maxPrice,
+ priceRange.step,
+ (min, max) => {
+ filters.minPrice = min;
+ filters.maxPrice = max === priceRange.max ? Number.POSITIVE_INFINITY : max;
+ onChange();
+ },
+ new DomOptions({
+ id: "price-range",
+ styles: {
+ marginBottom: "1.5rem",
+ },
+ }),
+ ),
+ ],
+ styles: {
+ display: "flex",
+ flexDirection: "column",
+ },
+ }),
+ ),
+
+ // Construction Year Range
Dom.div(
new DomOptions({
children: [
- Widgets.numberFilter("min-price", "Min price (€)", filters.minPrice, (v) => {
- filters.minPrice = v ?? 0;
- onChange();
- }),
- Widgets.numberFilter("max-price", "Max price (€)", filters.maxPrice, (v) => {
- filters.maxPrice = v ?? Number.POSITIVE_INFINITY;
- onChange();
- }),
+ Dom.label(
+ "year-range",
+ "Construction Year",
+ new DomOptions({
+ styles: {
+ fontSize: "0.85rem",
+ fontWeight: "bold",
+ marginBottom: "0.25rem",
+ },
+ }),
+ ),
+ Dom.range(
+ yearRange.min,
+ yearRange.max,
+ filters.minYear || yearRange.min,
+ filters.maxYear === Number.POSITIVE_INFINITY ? yearRange.max : filters.maxYear,
+ yearRange.step,
+ (min, max) => {
+ filters.minYear = min;
+ filters.maxYear = max === yearRange.max ? Number.POSITIVE_INFINITY : max;
+ onChange();
+ },
+ new DomOptions({
+ id: "year-range",
+ styles: {
+ marginBottom: "1.5rem",
+ },
+ }),
+ ),
],
- id: "price-row",
styles: {
display: "flex",
- gap: "0.5rem",
+ flexDirection: "column",
},
}),
),
- Widgets.numberFilter("min-year", "Min year", filters.minYear, (v) => {
- filters.minYear = v ?? 0;
- onChange();
- }),
- Widgets.numberFilter("min-area", "Min area (m²)", filters.minArea, (v) => {
- filters.minArea = v ?? 0;
- onChange();
- }),
+
+ // Living Area Range
+ Dom.div(
+ new DomOptions({
+ children: [
+ Dom.label(
+ "area-range",
+ "Living Area (m²)",
+ new DomOptions({
+ styles: {
+ fontSize: "0.85rem",
+ fontWeight: "bold",
+ marginBottom: "0.25rem",
+ },
+ }),
+ ),
+ Dom.range(
+ areaRange.min,
+ areaRange.max,
+ filters.minArea || areaRange.min,
+ filters.maxArea === Number.POSITIVE_INFINITY ? areaRange.max : filters.maxArea,
+ areaRange.step,
+ (min, max) => {
+ filters.minArea = min;
+ filters.maxArea = max === areaRange.max ? Number.POSITIVE_INFINITY : max;
+ onChange();
+ },
+ new DomOptions({
+ id: "area-range",
+ styles: {
+ marginBottom: "1.5rem",
+ },
+ }),
+ ),
+ ],
+ styles: {
+ display: "flex",
+ flexDirection: "column",
+ },
+ }),
+ ),
+
+ // Lot Size Range
+ Dom.div(
+ new DomOptions({
+ children: [
+ Dom.label(
+ "lot-range",
+ "Lot Size (m²)",
+ new DomOptions({
+ styles: {
+ fontSize: "0.85rem",
+ fontWeight: "bold",
+ marginBottom: "0.25rem",
+ },
+ }),
+ ),
+ Dom.range(
+ lotRange.min,
+ lotRange.max,
+ filters.minLot || lotRange.min,
+ filters.maxLot === Number.POSITIVE_INFINITY ? lotRange.max : filters.maxLot,
+ lotRange.step,
+ (min, max) => {
+ filters.minLot = min;
+ filters.maxLot = max === lotRange.max ? Number.POSITIVE_INFINITY : max;
+ onChange();
+ },
+ new DomOptions({
+ id: "lot-range",
+ styles: {
+ marginBottom: "1.5rem",
+ },
+ }),
+ ),
+ ],
+ styles: {
+ display: "flex",
+ flexDirection: "column",
+ },
+ }),
+ ),
+
+ // Districts Multi-select
Dom.div(
new DomOptions({
children: [
@@ -838,8 +1055,43 @@ export class Sidebar {
}),
),
],
- id: "district-multi-select",
- styles: { display: "flex", flexDirection: "column", marginTop: "1rem" },
+ styles: {
+ display: "flex",
+ flexDirection: "column",
+ },
+ }),
+ ),
+
+ // Clear Filters Button
+ Dom.button(
+ "Clear All Filters",
+ () => {
+ // Reset all ranges to their maximum possible values
+ filters.minPrice = 0;
+ filters.maxPrice = Number.POSITIVE_INFINITY;
+ filters.minYear = 1800;
+ filters.maxYear = Number.POSITIVE_INFINITY;
+ filters.minArea = 0;
+ filters.maxArea = Number.POSITIVE_INFINITY;
+ filters.minLot = 0;
+ filters.maxLot = Number.POSITIVE_INFINITY;
+ filters.districts = [];
+
+ // Update the UI by triggering onChange
+ onChange();
+ },
+ new DomOptions({
+ styles: {
+ background: "#f44336",
+ border: "none",
+ borderRadius: "4px",
+ color: "white",
+ cursor: "pointer",
+ fontSize: "0.85rem",
+ marginTop: "1rem",
+ padding: "0.5rem 1rem",
+ width: "100%",
+ },
}),
),
],
@@ -852,22 +1104,39 @@ export class Sidebar {
}
/**
- * Update histogram data
+ * Update sidebar with new data
* @param {House[]} houses
- * @param {string} parameter
+ * @param {string} histogramParameter
*/
- updateHistogram(houses, parameter) {
- const values = houses.map((house) => house.get(parameter));
- if (this.#histogram) {
- this.#histogram.update(values, parameter);
- }
+ update(houses, histogramParameter) {
+ this.#allHouses = houses;
+
+ // Update histogram
+ const values = houses.map((house) => house.get(histogramParameter));
+ this.#histogram.update(values, histogramParameter);
+
+ // Update filters section
+ this.#updateFiltersSection();
+
+ // Update districts
+ this.updateDistricts(houses);
}
/**
- * Handle histogram bar click
- * @param {number} min
- * @param {number} max
+ * Update filters section with current data
*/
+ #updateFiltersSection() {
+ if (!this.#allHouses || this.#allHouses.length === 0) return;
+
+ const newFiltersSection = Sidebar.filtersSection(this.#filters, this.#onFilterChange);
+
+ if (this.#filtersSectionElement) {
+ this.#filtersSectionElement.replaceWith(newFiltersSection);
+ }
+
+ this.#filtersSectionElement = newFiltersSection;
+ }
+
/**
* Handle histogram bar click
* @param {number} min
@@ -880,69 +1149,27 @@ export class Sidebar {
case HouseParameter.price: {
this.#filters.minPrice = min;
this.#filters.maxPrice = max;
- // Update the input fields to reflect the new filter values
- const minPriceInput = document.getElementById("min-price");
- const maxPriceInput = document.getElementById("max-price");
- if (minPriceInput) minPriceInput.value = Math.round(min).toString();
- if (maxPriceInput) maxPriceInput.value = Math.round(max).toString();
break;
}
case HouseParameter.area: {
this.#filters.minArea = min;
- // Update the input field to reflect the new filter value
- const minAreaInput = document.getElementById("min-area");
- if (minAreaInput) minAreaInput.value = Math.round(min).toString();
+ this.#filters.maxArea = max;
break;
}
case HouseParameter.year: {
this.#filters.minYear = min;
- // Update the input field to reflect the new filter value
- const minYearInput = document.getElementById("min-year");
- if (minYearInput) minYearInput.value = Math.round(min).toString();
+ this.#filters.maxYear = max;
break;
}
case HouseParameter.score: {
- // Note: You might want to add score filters to your Filters class
+ // Handle score filtering if needed
console.log(`Score range: ${min} - ${max}`);
- // If you add score filters to Filters class, you would do:
- // this.#filters.minScore = min;
- // this.#filters.maxScore = max;
break;
}
}
- // Update the input fields to show the new filter values
- this.#updateFilterInputs();
-
// Trigger the filter change to update the application
this.#onFilterChange();
}
- /**
- * Update filter input values to match current filters
- */
- #updateFilterInputs() {
- const minPriceInput = document.getElementById("min-price");
- const maxPriceInput = document.getElementById("max-price");
- const minAreaInput = document.getElementById("min-area");
- const minYearInput = document.getElementById("min-year");
-
- if (minPriceInput)
- minPriceInput.value = this.#filters.minPrice
- ? Math.round(this.#filters.minPrice).toString()
- : "";
- if (maxPriceInput)
- maxPriceInput.value =
- this.#filters.maxPrice !== Number.POSITIVE_INFINITY
- ? Math.round(this.#filters.maxPrice).toString()
- : "";
- if (minAreaInput)
- minAreaInput.value = this.#filters.minArea
- ? Math.round(this.#filters.minArea).toString()
- : "";
- if (minYearInput)
- minYearInput.value = this.#filters.minYear
- ? Math.round(this.#filters.minYear).toString()
- : "";
- }
/**
* @param {Histogram} histogram