Sprawdzone lokalizacje w Historii cen zapisane na koncie
Deploy Polska Lokalnie / deploy (push) Successful in 12s

Lista ostatnio sprawdzonych lokalizacji zyla w localStorage, wiec ginela
przy zmianie urzadzenia. Teraz jest polem ustawien konta
(user_settings.price_history_markets), a wpisy z przegladarki
przenosimy raz przy pierwszym wejsciu.

To historia przegladania, nie alerty - nic z niej nie wysyla powiadomien.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-23 12:21:39 +02:00
parent b5d121e24e
commit d4dd898387
7 changed files with 98 additions and 7 deletions
@@ -114,6 +114,14 @@ public class UserSettings {
@ColumnDefault("false")
private boolean notifyProductNews = false;
/**
* Lokalizacje ostatnio sprawdzone w Historii cen (JSON, maks. kilka pozycji). Lista jest
* na koncie, a nie w przegladarce, wiec porownanie cen wyglada tak samo na kazdym urzadzeniu.
* To zwykla historia przegladania - nie ma z nia zwiazanych zadnych powiadomien.
*/
@Column(name = "price_history_markets", columnDefinition = "text")
private String priceHistoryMarkets;
// --- Preferencje wyszukiwania (podpowiadane przy filtrowaniu ofert) ---
@Column(length = 255)
@@ -314,6 +322,14 @@ public class UserSettings {
return searchLocations;
}
public String getPriceHistoryMarkets() {
return priceHistoryMarkets;
}
public void setPriceHistoryMarkets(String priceHistoryMarkets) {
this.priceHistoryMarkets = priceHistoryMarkets;
}
public void setSearchLocations(String searchLocations) {
this.searchLocations = searchLocations;
}
@@ -25,6 +25,7 @@ public record UserSettingsRequest(
Boolean notifyPriceAlerts,
Boolean notifyMessages,
Boolean notifyProductNews,
String priceHistoryMarkets,
String searchLocations,
String searchPropertyType,
Integer searchBudgetMax,
@@ -22,6 +22,7 @@ public record UserSettingsResponse(
boolean notifyPriceAlerts,
boolean notifyMessages,
boolean notifyProductNews,
String priceHistoryMarkets,
String searchLocations,
String searchPropertyType,
Integer searchBudgetMax,
@@ -52,6 +53,7 @@ public record UserSettingsResponse(
settings.isNotifyPriceAlerts(),
settings.isNotifyMessages(),
settings.isNotifyProductNews(),
settings.getPriceHistoryMarkets(),
settings.getSearchLocations(),
settings.getSearchPropertyType(),
settings.getSearchBudgetMax(),
@@ -135,6 +135,10 @@ public class UserSettingsService {
if (request.notifyProductNews() != null) {
settings.setNotifyProductNews(request.notifyProductNews());
}
if (request.priceHistoryMarkets() != null) {
String markets = request.priceHistoryMarkets().trim();
settings.setPriceHistoryMarkets(markets.isEmpty() ? null : markets);
}
if (request.searchLocations() != null) {
String locations = request.searchLocations().trim();
textModerationService.validateOrThrow(locations);
+51 -7
View File
@@ -444,7 +444,27 @@ function toSavedSearchPayload(search: SavedSearchRecord | SavedSearchDraft) {
};
}
// Lokalizacje, ktorych cene za m2 sprawdzil uzytkownik - najnowsza pierwsza, przechowywane per konto.
// Klucz zostaje tylko po to, zeby raz przeniesc na konto lokalizacje zapisane w przegladarce
// przed przeniesieniem tej listy na serwer.
const PRICE_HISTORY_MARKETS_SUFFIX = 'price-history-markets';
function parsePriceHistoryMarkets(raw: string | null): PriceHistoryMarket[] {
if (!raw) {
return [];
}
try {
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed.filter((item) => item && typeof item.key === 'string') : [];
} catch {
return [];
}
}
/** Lista lokalizacji z Historii cen jest ustawieniem konta - zapisujemy ja przy kazdej zmianie. */
function savePriceHistoryMarkets(markets: PriceHistoryMarket[]): void {
apiFetch<unknown>('/me/settings', { method: 'PUT', body: JSON.stringify({ priceHistoryMarkets: JSON.stringify(markets) }) })
.catch(() => { /* brak sieci - lista wroci do stanu z konta przy nastepnym wejsciu */ });
}
const PRICE_HISTORY_MARKETS_LIMIT = 5;
const PRICE_ALERT_EVENTS_LIMIT = 40;
const SAVED_SEARCHES_LIMIT = 60;
@@ -21484,9 +21504,33 @@ function PriceHistoryPage({ user }: { user: AuthUser | null }) {
const [suggestLoading, setSuggestLoading] = useState(false);
const [hoveredHistoryIndex, setHoveredHistoryIndex] = useState<number | null>(null);
// Historia sprawdzonych lokalizacji tego konta - najnowsza na poczatku, maksymalnie 5 pozycji.
const [recentMarkets, setRecentMarkets] = useState<PriceHistoryMarket[]>(
() => readUserJsonStorage<PriceHistoryMarket[]>(user, PRICE_HISTORY_MARKETS_SUFFIX, []),
);
// Lista siedzi w ustawieniach konta (/api/me/settings), wiec porownanie wyglada tak samo
// na kazdym urzadzeniu. Wpisy z przegladarki przenosimy raz, przy pierwszym wejsciu.
const [recentMarkets, setRecentMarkets] = useState<PriceHistoryMarket[]>([]);
useEffect(() => {
if (!user) {
setRecentMarkets([]);
return;
}
let cancelled = false;
const local = readUserJsonStorage<PriceHistoryMarket[]>(user, PRICE_HISTORY_MARKETS_SUFFIX, []);
apiFetch<{ priceHistoryMarkets: string | null }>('/me/settings')
.then((settings) => {
if (cancelled) {
return;
}
const fromAccount = parsePriceHistoryMarkets(settings.priceHistoryMarkets);
if (fromAccount.length === 0 && local.length > 0) {
setRecentMarkets(local);
savePriceHistoryMarkets(local);
} else {
setRecentMarkets(fromAccount);
}
writeUserJsonStorage<PriceHistoryMarket[]>(user, PRICE_HISTORY_MARKETS_SUFFIX, []);
})
.catch(() => { if (!cancelled) { setRecentMarkets(local); } });
return () => { cancelled = true; };
}, [user]);
// Ostatnio otwarte oferte (sprzedaz i wynajem) - wspolny rejestr zasilany przy wejsciu na oferte.
const recentListings = useMemo(() => readRecentlyViewed(user).slice(0, RECENTLY_VIEWED_PANEL_LIMIT), [user]);
const skipNextFetch = useRef(false);
@@ -21719,7 +21763,7 @@ function PriceHistoryPage({ user }: { user: AuthUser | null }) {
// Nowa lokalizacja wchodzi na poczatek listy, najstarsza wypada po przekroczeniu limitu.
setRecentMarkets((current) => {
const next = [market, ...current.filter((item) => item.key !== market.key)].slice(0, PRICE_HISTORY_MARKETS_LIMIT);
writeUserJsonStorage(user, PRICE_HISTORY_MARKETS_SUFFIX, next);
savePriceHistoryMarkets(next);
return next;
});
};
@@ -21729,18 +21773,18 @@ function PriceHistoryPage({ user }: { user: AuthUser | null }) {
};
// Usuwanie pojedynczej lokalizacji z porownania i czyszczenie calej listy - zapis leci od razu
// do pamieci konta, zeby stan zgadzal sie po odswiezeniu strony.
// na konto, zeby stan zgadzal sie po odswiezeniu i na innym urzadzeniu.
const removeComparison = (marketKey: string) => {
setRecentMarkets((current) => {
const next = current.filter((item) => item.key !== marketKey);
writeUserJsonStorage(user, PRICE_HISTORY_MARKETS_SUFFIX, next);
savePriceHistoryMarkets(next);
return next;
});
};
const clearComparisons = () => {
setRecentMarkets([]);
writeUserJsonStorage(user, PRICE_HISTORY_MARKETS_SUFFIX, []);
savePriceHistoryMarkets([]);
};
// Wejscie z adresu z parametrami (np. "Zobacz historie cen" przy alercie lokalizacji)
+2
View File
@@ -37,6 +37,8 @@ export type UserSettings = {
notifyPriceAlerts: boolean;
notifyMessages: boolean;
notifyProductNews: boolean;
// Lokalizacje sprawdzone w Historii cen (JSON) - historia przegladania, bez powiadomien.
priceHistoryMarkets: string | null;
searchLocations: string | null;
searchPropertyType: string | null;
searchBudgetMax: number | null;
+22
View File
@@ -597,3 +597,25 @@ test.describe('Ulubione', () => {
await expect(page.locator('main')).not.toContainText(title.trim().slice(0, 20));
});
});
// Lista sprawdzonych lokalizacji w Historii cen jest ustawieniem konta, nie pamiecia przegladarki.
test.describe('Historia cen', () => {
test('sprawdzone lokalizacje przezywaja wyczyszczenie pamieci przegladarki', async ({ page }) => {
await login(page);
await goto(page, ROUTES.priceHistory);
await page.locator('.history-location-display').click();
await page.locator('.history-location-pop input').fill('Kraków');
await page.locator('.history-location-pop button', { hasText: 'Kraków' }).first().click();
await expect(page.locator('main')).toContainText('Kraków');
await page.evaluate(() => {
const token = window.localStorage.getItem('polskalokalnie-auth-token');
window.localStorage.clear();
if (token) {
window.localStorage.setItem('polskalokalnie-auth-token', token);
}
});
await goto(page, ROUTES.priceHistory);
await expect(page.locator('main')).toContainText('Kraków');
});
});