Files
astrololo/services/presentation/app/static/predictions.js
T
gitea 8cc329ab17
build / build (push) Successful in 1m4s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 12m9s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 1m6s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 38s
Testy / Kontrola składni wszystkich warstw (push) Successful in 20s
feat(prezentacja): zapamiętane predykcje okresowe (PRE-22) + wymaganie o cache-bustingu
Piata wskazowka partnerow: Kalendarz ma pozwalac liczyc predykcje dla KILKU
okresow i je zachowywac, zeby wszystkie trafily potem do raportu („Skompiluj",
PRE-23/24).

Nowy predictions.js — magazyn w localStorage, tak jak wspolne dane formularza
(PRE-21): serwer zostaje bezstanowy, zadne dane urodzeniowe ani tresci z baz nie
laduja po stronie uslugi. Zakladka „Skompiluj" odczyta to samo miejsce przez
window.astrololoPredictions (jedno zrodlo prawdy).

Decyzje projektowe:

- KLUCZEM TOZSAMOSCI JEST OKRES. Ponowne policzenie tego samego zakresu podmienia
  wpis zamiast dokladac duplikat — inaczej lista puchlaby przy kazdej probie
  z innym modelem albo budzetem. Dzieki temu zapis jest idempotentny, wiec dziala
  tez wariant bez strumienia (zapis przy wczytaniu strony z gotowym wynikiem)
  i odswiezenie niczego nie mnozy.
- progress.js OGLASZA gotowy horoskop zdarzeniem `astrololo:horoscope` z profilem,
  zamiast sam zapisywac. To okno postepu, a nie magazyn — zapisywanie zostaje
  odpowiedzialnoscia predictions.js. Filtr po profilu pilnuje, zeby interpretacja
  natalna nie trafila na liste predykcji okresowych.
- Przepelniony magazyn (horoskopy bywaja dlugie) jest ZGLASZANY uzytkownikowi,
  a nie polykany — inaczej wynik znikalby po cichu.

UI: lista zapamietanych predykcji na Kalendarzu — okres, data zapisu, objetosc
i przycisk usuwania. Skrypt podpiety w timeline.html (nie w base.html), zeby nie
kolidowac z rownolegle otwartym #29.

PRE-26 — dopisane wymaganie o wersjonowaniu plikow statycznych. Przy PRE-25
przegladarka podala STARY styles.css i powiekszanie kosmogramu „nie dzialalo",
mimo ze klasa byla nakladana. Objaw jest zdradliwy: szablony sa nowe, wiec strona
wyglada na zaktualizowana, a funkcja po prostu milczy. Po wdrozeniu moze to
spotkac uzytkownikow.

Weryfikacja na zywej aplikacji: dwa okresy zapisane; powtorzenie tego samego
zakresu podmienilo wpis (dalej 2, tekst zaktualizowany); lista posortowana po
dacie; predykcje przetrwaly przejscie na inna zakladke i powrot; interpretacja
natalna NIE wpadla na liste; usuwanie zmniejszylo licznik 2 -> 1 i przerysowalo
liste. Testy: 14 nowych. Calosc: prezentacja 101 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 17:57:24 +02:00

151 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Zapamiętane predykcje okresowe (PRE-22).
//
// Kalendarz pozwala policzyć horoskop na wybrany okres. Partnerzy chcą kilku
// okresów naraz — i żeby wszystkie trafiły potem do raportu (zakładka
// „Skompiluj", PRE-23/24). Tu jest magazyn i lista.
//
// Trzymamy w localStorage, tak jak wspólne dane formularza (PRE-21): serwer
// zostaje bezstanowy, żadne dane urodzeniowe ani treści z baz nie lądują po
// stronie usługi. Zakładka „Skompiluj" odczyta to samo miejsce.
(function () {
'use strict';
var KEY = 'astrololo.predictions.v1';
function read() {
try {
var v = JSON.parse(localStorage.getItem(KEY) || '[]');
return Array.isArray(v) ? v : [];
} catch (e) {
return []; // uszkodzony wpis nie może zablokować strony
}
}
function write(list) {
try {
localStorage.setItem(KEY, JSON.stringify(list));
return true;
} catch (e) {
return false; // najczęściej przepełniony magazyn
}
}
function byPeriod(a, b) {
return String(a.from_date).localeCompare(String(b.from_date));
}
// Klucz tożsamości to OKRES. Ponowne policzenie tego samego zakresu ma
// podmienić wpis, a nie dokładać duplikat — inaczej lista puchłaby przy
// każdej próbie z innym modelem.
function upsert(entry) {
var list = read().filter(function (p) {
return !(p.from_date === entry.from_date && p.to_date === entry.to_date);
});
list.push(entry);
list.sort(byPeriod);
return write(list) ? list : null;
}
function remove(id) {
var list = read().filter(function (p) { return p.id !== id; });
write(list);
return list;
}
function val(sel) {
var el = document.querySelector(sel);
return el ? el.value.trim() : '';
}
/** Wyciąga świeżo policzony horoskop okresowy ze strony. */
function capture() {
var text = val('#horoscopeText');
var from = val('input[name=from_date]');
var to = val('input[name=to_date]');
if (!text || !from || !to) return null;
return {
id: from + '..' + to,
from_date: from,
to_date: to,
person: val('input[name=person]'),
text: text,
saved_at: new Date().toISOString()
};
}
function fmtDate(iso) {
return String(iso || '').slice(0, 10);
}
function render() {
var host = document.getElementById('savedPredictions');
if (!host) return;
var list = read();
if (!list.length) {
host.innerHTML = '<p class="muted small">Brak zapamiętanych predykcji. ' +
'Policz horoskop na wybrany okres — zapamięta się sam i trafi do raportu.</p>';
return;
}
var rows = list.map(function (p) {
var chars = (p.text || '').length;
return '<tr>' +
'<td class="nowrap">' + fmtDate(p.from_date) + ' ' + fmtDate(p.to_date) + '</td>' +
'<td class="muted small">' + fmtDate(p.saved_at) + '</td>' +
'<td class="muted small">' + chars.toLocaleString('pl-PL') + ' znaków</td>' +
'<td><button type="button" class="ghost" data-drop="' +
String(p.id).replace(/"/g, '&quot;') + '">Usuń</button></td>' +
'</tr>';
}).join('');
host.innerHTML =
'<div class="meta">Zapamiętane predykcje (' + list.length + ') — wejdą do raportu</div>' +
'<table class="angles"><thead><tr><th>Okres</th><th>Zapisano</th>' +
'<th>Objętość</th><th></th></tr></thead><tbody>' + rows + '</tbody></table>';
Array.prototype.forEach.call(host.querySelectorAll('[data-drop]'), function (btn) {
btn.addEventListener('click', function () {
remove(btn.getAttribute('data-drop'));
render();
});
});
}
function saveCurrent() {
var entry = capture();
if (!entry) return;
var note = document.getElementById('predictionsNote');
if (upsert(entry)) {
if (note) note.textContent = 'Zapamiętano predykcję ' +
entry.from_date + ' ' + entry.to_date + '.';
} else if (note) {
// Horoskopy bywają długie; gdy magazyn się przepełni, trzeba powiedzieć
// wprost, zamiast po cichu zgubić wynik.
note.textContent = 'Nie udało się zapamiętać — magazyn przeglądarki pełny. ' +
'Usuń starsze predykcje i spróbuj ponownie.';
}
render();
}
function ready(fn) {
if (document.readyState !== 'loading') fn();
else document.addEventListener('DOMContentLoaded', fn);
}
ready(function () {
if (!document.getElementById('savedPredictions')) return; // nie ta zakładka
render();
// 1) horoskop policzony przez okno postępu (strumień)
document.addEventListener('astrololo:horoscope', function (e) {
if (!e.detail || e.detail.profile === 'period') saveCurrent();
});
// 2) wariant bez strumienia: strona wróciła z gotowym wynikiem po zwykłym
// POST. Zapis jest idempotentny (klucz = okres), więc odświeżenie strony
// niczego nie duplikuje.
if (document.querySelector('#horoscopeText')) saveCurrent();
});
// Udostępniamy magazyn zakładce „Skompiluj" (PRE-23) — jedno źródło prawdy.
window.astrololoPredictions = { read: read, remove: remove, key: KEY };
})();