Compare commits

..

2 Commits

Author SHA1 Message Date
orangecoding
51b4e51f3f fixing setting kleinanzeigen listings to inactive if not available anymore 2026-01-16 11:36:51 +01:00
orangecoding
fa1899765c fixing some rendering issues in map 2026-01-16 10:46:50 +01:00
9 changed files with 1139 additions and 1487 deletions

View File

@@ -64,12 +64,10 @@ listingsRouter.get('/table', async (req, res) => {
});
listingsRouter.get('/map', async (req, res) => {
const { jobId, minPrice, maxPrice } = req.query || {};
const { jobId } = req.query || {};
res.body = listingStorage.getListingsForMap({
jobId: nullOrEmpty(jobId) ? null : jobId,
minPrice: minPrice ? parseInt(minPrice, 10) : null,
maxPrice: maxPrice ? parseInt(maxPrice, 10) : null,
userId: req.session.currentUser,
isAdmin: isAdminFn(req),
});

View File

@@ -4,6 +4,7 @@
*/
import * as utils from '../utils.js';
import checkIfListingIsActive from '../services/listings/listingActiveTester.js';
let appliedBlackList = [];
@@ -40,6 +41,7 @@ const config = {
},
normalize: normalize,
filter: applyBlacklist,
activeTester: checkIfListingIsActive,
};
export const init = (sourceConfig, blacklistTerms) => {

View File

@@ -26,6 +26,7 @@ export default async function checkIfListingIsActive(link) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const res = await fetch(link, {
redirect: 'manual',
headers: {
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',

View File

@@ -432,14 +432,11 @@ export const updateListingGeocoordinates = (id, latitude, longitude) => {
*
* @param {Object} params
* @param {string} [params.jobId]
* @param {boolean} [params.activeOnly=true]
* @param {number} [params.minPrice]
* @param {number} [params.maxPrice]
* @param {string} [params.userId]
* @param {boolean} [params.isAdmin=false]
* @returns {{listings: Object[], maxPrice: number}} Object containing listings and maxPrice.
*/
export const getListingsForMap = ({ jobId, minPrice, maxPrice, userId = null, isAdmin = false } = {}) => {
export const getListingsForMap = ({ jobId, userId = null, isAdmin = false } = {}) => {
const baseWhereParts = [
'l.latitude IS NOT NULL',
'l.longitude IS NOT NULL',
@@ -461,15 +458,6 @@ export const getListingsForMap = ({ jobId, minPrice, maxPrice, userId = null, is
}
const wherePartsForListings = [...baseWhereParts];
if (minPrice !== undefined && minPrice !== null) {
params.minPrice = minPrice;
wherePartsForListings.push('l.price >= @minPrice');
}
if (maxPrice !== undefined && maxPrice !== null) {
params.maxPrice = maxPrice;
wherePartsForListings.push('l.price <= @maxPrice');
}
const listings = SqliteConnection.query(
`SELECT l.*, j.name AS job_name
@@ -479,17 +467,8 @@ export const getListingsForMap = ({ jobId, minPrice, maxPrice, userId = null, is
params,
);
const maxPriceRow = SqliteConnection.query(
`SELECT MAX(l.price) AS maxPrice
FROM listings l
LEFT JOIN jobs j ON j.id = l.job_id
WHERE ${baseWhereParts.join(' AND ')}`,
params,
)[0];
return {
listings,
maxPrice: maxPriceRow?.maxPrice || 0,
};
};

1159
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "fredy",
"version": "18.0.0",
"version": "18.0.2",
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
"scripts": {
"prepare": "husky",
@@ -59,8 +59,8 @@
"Firefox ESR"
],
"dependencies": {
"@douyinfe/semi-icons": "^2.90.11",
"@douyinfe/semi-ui": "2.90.11",
"@douyinfe/semi-icons": "^2.90.13",
"@douyinfe/semi-ui": "2.90.13",
"@sendgrid/mail": "8.1.6",
"@vitejs/plugin-react": "5.1.2",
"adm-zip": "^0.5.16",
@@ -85,6 +85,7 @@
"react": "18.3.1",
"react-chartjs-2": "^5.3.1",
"react-dom": "18.3.1",
"react-range-slider-input": "^3.3.2",
"react-router": "7.12.0",
"react-router-dom": "7.12.0",
"restana": "5.1.0",
@@ -96,9 +97,9 @@
"zustand": "^5.0.10"
},
"devDependencies": {
"@babel/core": "7.28.5",
"@babel/eslint-parser": "7.28.5",
"@babel/preset-env": "7.28.5",
"@babel/core": "7.28.6",
"@babel/eslint-parser": "7.28.6",
"@babel/preset-env": "7.28.6",
"@babel/preset-react": "7.28.5",
"chai": "6.2.2",
"eslint": "9.39.2",
@@ -111,6 +112,6 @@
"lint-staged": "16.2.7",
"mocha": "11.7.5",
"nodemon": "^3.1.11",
"prettier": "3.7.4"
"prettier": "3.8.0"
}
}

View File

@@ -7,9 +7,11 @@ import React, { useEffect, useRef, useState } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import { useSelector, useActions } from '../../services/state/store.js';
import { Select, Slider, Space, Typography, Button, Popover, Divider, Switch, Banner } from '@douyinfe/semi-ui';
import { Select, Space, Typography, Button, Popover, Divider, Switch, Banner } from '@douyinfe/semi-ui';
import { IconFilter } from '@douyinfe/semi-icons';
import no_image from '../../assets/no_image.jpg';
import RangeSlider from 'react-range-slider-input';
import 'react-range-slider-input/dist/style.css';
import './Map.less';
const { Text } = Typography;
@@ -65,23 +67,31 @@ export default function MapView() {
const markers = useRef([]);
const actions = useActions();
const listings = useSelector((state) => state.listingsData.mapListings);
const maxPriceFromStore = useSelector((state) => state.listingsData.maxPrice);
const [style, setStyle] = useState('STANDARD');
const [show3dBuildings, setShow3dBuildings] = useState(false);
const jobs = useSelector((state) => state.jobsData.jobs);
const [jobId, setJobId] = useState(null);
const [priceRange, setPriceRange] = useState([0, 100000]);
const [priceRange, setPriceRange] = useState([0, 0]);
const [showFilterBar, setShowFilterBar] = useState(false);
const lastJobIdRef = useRef('__INITIAL__');
useEffect(() => {
if (maxPriceFromStore > 0 && lastJobIdRef.current !== jobId) {
setPriceRange([0, maxPriceFromStore]);
lastJobIdRef.current = jobId;
}
}, [maxPriceFromStore, jobId]);
setPriceRange([0, getMaxPrice()]);
}, [listings]);
const getMaxPrice = () => {
return listings.reduce((max, item) => {
const price = Number(item.price);
return Number.isFinite(price) && price > max ? price : max;
}, -Infinity);
};
const filterListings = () => {
const min = priceRange[0];
const max = priceRange[1] && priceRange[1] > 0 ? priceRange[1] : getMaxPrice();
return listings.filter((listing) => listing.price && listing.price >= min && listing.price <= max);
};
useEffect(() => {
if (map.current) return;
@@ -97,13 +107,22 @@ export default function MapView() {
map.current.addControl(
new maplibregl.NavigationControl({
showCompass: false,
showCompass: true,
visualizePitch: true,
visualizeRoll: true,
}),
'top-right',
);
map.current.addControl(
new maplibregl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true,
},
trackUserLocation: true,
}),
);
return () => {
map.current.remove();
};
@@ -199,14 +218,12 @@ export default function MapView() {
const fetchListings = async () => {
actions.listingsData.getListingsForMap({
jobId,
minPrice: priceRange[0] > 0 ? priceRange[0] : null,
maxPrice: maxPriceFromStore > 0 && priceRange[1] < maxPriceFromStore ? priceRange[1] : null,
});
};
useEffect(() => {
fetchListings();
}, [jobId, priceRange]);
}, [jobId]);
useEffect(() => {
if (!map.current) return;
@@ -214,7 +231,7 @@ export default function MapView() {
markers.current.forEach((marker) => marker.remove());
markers.current = [];
listings.forEach((listing) => {
filterListings().forEach((listing) => {
if (
listing.latitude != null &&
listing.longitude != null &&
@@ -247,7 +264,7 @@ export default function MapView() {
markers.current.push(marker);
}
});
}, [listings]);
}, [listings, priceRange]);
return (
<div className="map-view-container">
@@ -285,7 +302,9 @@ export default function MapView() {
placeholder="Job"
showClear
style={{ width: 150 }}
onChange={(val) => setJobId(val)}
onChange={(val) => {
setJobId(val);
}}
value={jobId}
>
{jobs?.map((j) => (
@@ -302,13 +321,18 @@ export default function MapView() {
<Text strong>Price Range ():</Text>
</div>
<div style={{ width: 250, padding: '0 10px' }}>
<Slider
range
<div className="map__rangesliderLabels">
<span>{priceRange[0]} </span>
<span>{priceRange[1]} </span>
</div>
<RangeSlider
min={0}
max={maxPriceFromStore || 100000}
max={getMaxPrice()}
step={100}
value={priceRange}
onChange={(val) => setPriceRange(val)}
onInput={(val) => {
setPriceRange(val);
}}
tipFormatter={(val) => `${val}`}
/>
</div>

View File

@@ -63,11 +63,22 @@
border-bottom-color: var(--semi-color-bg-1) !important;
}
.maplibregl-ctrl-group {
background: var(--semi-color-bg-1) !important;
}
.maplibregl-ctrl-group button {
background-color: var(--semi-color-bg-1) !important;
border-color: var(--semi-color-border) !important;
.map {
&__rangesliderLabels{
color: white;
display: flex;
justify-content: space-between;
margin-bottom: .3rem;
font-size: .7rem;
}
}
.range-slider .range-slider__thumb {
position: absolute;
z-index: 3;
top: 50%;
width: 16px;
height: 16px;
transform: translate(-50%, -50%);
border-radius: 50%;
background: #2196f3;
}

1331
yarn.lock

File diff suppressed because it is too large Load Diff