mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
Map View in Fredy :D (#253)
* init map view * switching off 3d buildings when sattelite view is on * rename menu items * upgrading dependencies, adding provider to popups * adding screenshot for map view * fixing readme * next release version
This commit is contained in:
committed by
GitHub
parent
7fd8be07a2
commit
d43c5b3f97
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
@@ -173,9 +173,9 @@ export const storeListings = (jobId, providerId, listings) => {
|
||||
SqliteConnection.withTransaction((db) => {
|
||||
const stmt = db.prepare(
|
||||
`INSERT INTO listings (id, hash, provider, job_id, price, size, title, image_url, description, address,
|
||||
link, created_at, is_active)
|
||||
link, created_at, is_active, latitude, longitude)
|
||||
VALUES (@id, @hash, @provider, @job_id, @price, @size, @title, @image_url, @description, @address, @link,
|
||||
@created_at, 1)
|
||||
@created_at, 1, @latitude, @longitude)
|
||||
ON CONFLICT(job_id, hash) DO NOTHING`,
|
||||
);
|
||||
|
||||
@@ -193,6 +193,8 @@ export const storeListings = (jobId, providerId, listings) => {
|
||||
address: removeParentheses(item.address),
|
||||
link: item.link,
|
||||
created_at: Date.now(),
|
||||
latitude: item.latitude || null,
|
||||
longitude: item.longitude || null,
|
||||
};
|
||||
stmt.run(params);
|
||||
}
|
||||
@@ -392,6 +394,105 @@ export const deleteListingsById = (ids) => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all listings that are active, have an address, and do not yet have geocoordinates.
|
||||
*
|
||||
* @returns {Object[]} Array of listing objects {id, address}.
|
||||
*/
|
||||
export const getListingsToGeocode = () => {
|
||||
return SqliteConnection.query(
|
||||
`SELECT id, address
|
||||
FROM listings
|
||||
WHERE is_active = 1
|
||||
AND address IS NOT NULL
|
||||
AND (latitude IS NULL OR longitude IS NULL)`,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the geocoordinates for a listing.
|
||||
*
|
||||
* @param {string} id - The listing ID.
|
||||
* @param {number} latitude
|
||||
* @param {number} longitude
|
||||
* @returns {void}
|
||||
*/
|
||||
export const updateListingGeocoordinates = (id, latitude, longitude) => {
|
||||
SqliteConnection.execute(
|
||||
`UPDATE listings
|
||||
SET latitude = @latitude,
|
||||
longitude = @longitude
|
||||
WHERE id = @id`,
|
||||
{ id, latitude, longitude },
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return listings with geocoordinates for the map view, with optional filtering.
|
||||
*
|
||||
* @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 } = {}) => {
|
||||
const baseWhereParts = [
|
||||
'l.latitude IS NOT NULL',
|
||||
'l.longitude IS NOT NULL',
|
||||
'l.latitude != -1',
|
||||
'l.longitude != -1',
|
||||
'l.is_active = 1',
|
||||
];
|
||||
const params = { userId: userId || '__NO_USER__' };
|
||||
|
||||
if (!isAdmin) {
|
||||
baseWhereParts.push(
|
||||
`(j.user_id = @userId OR EXISTS (SELECT 1 FROM json_each(j.shared_with_user) AS sw WHERE sw.value = @userId))`,
|
||||
);
|
||||
}
|
||||
|
||||
if (jobId) {
|
||||
params.jobId = jobId;
|
||||
baseWhereParts.push('l.job_id = @jobId');
|
||||
}
|
||||
|
||||
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
|
||||
FROM listings l
|
||||
LEFT JOIN jobs j ON j.id = l.job_id
|
||||
WHERE ${wherePartsForListings.join(' AND ')}`,
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all listings with only the fields: title, address, and price.
|
||||
* This is the single helper requested for simple consumers.
|
||||
@@ -401,3 +502,24 @@ export const deleteListingsById = (ids) => {
|
||||
export const getAllEntriesFromListings = () => {
|
||||
return SqliteConnection.query(`SELECT title, address, price FROM listings`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return geocoordinates for a given address if it has been geocoded before.
|
||||
*
|
||||
* @param {string} address
|
||||
* @returns {{lat: number, lng: number}|null}
|
||||
*/
|
||||
export const getGeocoordinatesByAddress = (address) => {
|
||||
const row = SqliteConnection.query(
|
||||
`SELECT latitude, longitude
|
||||
FROM listings
|
||||
WHERE address = @address
|
||||
AND latitude IS NOT NULL
|
||||
AND longitude IS NOT NULL
|
||||
AND latitude != -1
|
||||
AND longitude != -1
|
||||
LIMIT 1`,
|
||||
{ address },
|
||||
)[0];
|
||||
return row ? { lat: row.latitude, lng: row.longitude } : null;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
// Migration: Add geocoordinates to listings for map display
|
||||
|
||||
export function up(db) {
|
||||
db.exec(`
|
||||
ALTER TABLE listings ADD COLUMN latitude REAL;
|
||||
ALTER TABLE listings ADD COLUMN longitude REAL;
|
||||
`);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Copyright (c) 2026 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user