/* ── Acasă / Calendarul meu / Plan strategic (credinte + valori) ────────────────
Au pornit pe 24.07 ca variante PARALELE („(nou)"), langa paginile de atunci, ca sa
aleaga Petre. Pe 25.07 Marian a ales: paginile vechi (AcasaView / CalendarView /
PlanStrategicView) au fost STERSE din app.jsx, iar astea le-au luat locul si slugurile
(„acasa" / „calendar" / „plan"); store-urile salvate cu slugurile „-nou" se traduc la
incarcare (REDENUMIRI in loadStore).
Fiecare pagina arata cele doua fluxuri in BLOCURI SEPARATE, nu amestecate.
Unirea: itemii credintelor (planEntries) + itemii valorilor (window.valItemiPlan -
rutinele Z/S/L + angajamentul din Codul meu), bifati in ACELASI store.log; cheile
valorilor au prefixul "val|" si nu se ciocnesc cu ale credintelor.
Helperii vin din window.PRP_UTIL (ancora aditiva din app.jsx). */
function hibridItemi(store) {
const U = window.PRP_UTIL;
const cred = U.planEntries(store.plan);
const val = (window.valItemiPlan ? window.valItemiPlan(store) : []);
return { cred, val, toate: [...cred, ...val] };
}
/* sub-eticheta sursei pe un item, indiferent de flux */
function hibridSub(e) {
if (e.cred) return e.cred.nume + " · Aria " + String(e.aria.nr).padStart(2, "0");
return e.valoare.nume + " · " + e.eticheta.toLowerCase();
}
function hibridToggleLog(set) {
return (key, pk) => set((s) => {
const forKey = { ...(s.log[key] || {}) };
if (forKey[pk]) delete forKey[pk]; else forKey[pk] = true;
return { ...s, log: { ...s.log, [key]: forKey } };
});
}
/* ── Acasă (nou) ── */
function AcasaHibridView({ store, set }) {
const U = window.PRP_UTIL;
const azi = new Date(); azi.setHours(0, 0, 0, 0);
const aziISO = U.fmtISO(azi);
const { cred, val, toate } = hibridItemi(store);
/* zilnicele se numara PE FLUX (benzile saptamanii + cifra zilei sunt despartite);
agregat raman doar conformarea pe 7 zile si sirul de zile complete, care masoara procesul intreg */
const sumOA = cred.reduce((n, e) => n + (e.oa || 0), 0);
const sumOR = cred.reduce((n, e) => n + (e.orNr || 0), 0);
/* conformarea si sirul de zile NU se mai calculeaza pe tot: fiecare flux le are pe ale lui,
langa statisticile lui (vezi grupurile de mai jos) */
const nume = store.consola.nume.trim();
const prenume = nume.split(/\s+/)[0] || "";
const dataRo = azi.toLocaleDateString("ro-RO", { weekday: "long", day: "numeric", month: "long", year: "numeric" });
let ziProces = null;
if (store.consola.dataStart) {
const start = new Date(store.consola.dataStart + "T00:00:00");
if (!isNaN(start) && start <= azi) ziProces = Math.floor((azi - start) / 86400000) + 1;
}
const toggleLog = hibridToggleLog(set);
const monday = U.startOfWeek(azi);
const days = Array.from({ length: 7 }, (_, i) => U.addDays(monday, i));
const saptPk = U.periodKey("Săptămânal", azi);
const goto = (view) => () => set((s) => ({ ...s, view }));
/* DOUA definitii ale zilei, alaturi (cerinta Marian 24.07 seara): una din dictionarul
credintelor + una din dictionarul valorilor - fiecare cu rotatia ei pe ziua anului */
const dictValori = React.useMemo(() => {
const din = [];
const pv = window.PRP_VALORI || {};
(pv.concepte || []).forEach((c) => din.push({ termen: c.nume, definitie: c.definitie }));
(pv.categorii || []).forEach((c) => c.valori.forEach((v) => din.push({ termen: v.nume, definitie: v.definitie })));
return din;
}, []);
const randAzi = (e, pk) => {
const on = U.isLogged(store.log, e.key, pk);
return (
{e.text}
{hibridSub(e)}
);
};
return (
{prenume ? `Bună, ${prenume}` : "Bună"}
{dataRo}{ziProces ? ` · ziua ${ziProces} din proces` : ""}
Alege acțiuni concrete din credințe și valorile care te definesc - împreună formează
planul tău de implementare, urmărit aici zi de zi.
) : (
{/* cele doua fluxuri, in BLOCURI SEPARATE pe aceeasi pagina (cerinta Marian 25.07):
fiecare bloc isi tine propriile zilnice / saptamanale / angajamente, ca sa se
vada dintr-o privire ce vine din credinte si ce din valori */}
{/* o banda de saptamana pentru FIECARE flux, una sub alta si aliniate pe zile
(cerinta Marian 25.07) - se citesc si separat, si comparativ pe aceeasi zi */}
{[
{ id: "cred", eticheta: "Credințe", lista: cred.filter((e) => e.frecventa === "Zilnic") },
{ id: "val", eticheta: "Valori", lista: val.filter((e) => e.frecventa === "Zilnic") },
].map((b, idx) => (
{b.eticheta}
{b.lista.length === 0 ? (
{b.id === "cred" ? "Nicio acțiune zilnică din credințe." : "Nicio rutină zilnică din valori."}
{/* statisticile, tot pe fluxuri (cerinta Marian 25.07: „totul trebuie sa fie
despartit"): fiecare grup isi masoara PROPRIILE bife - conformarea si sirul
de zile complete se calculeaza pe lista fluxului, nu pe amandoua la un loc,
deci un flux neglijat nu mai poate fi ascuns de celalalt */}
{[
{ id: "cred", eticheta: "Credințe", lista: cred },
{ id: "val", eticheta: "Valori", lista: val },
].filter((b) => b.lista.length > 0).map((b) => {
const zilnice = b.lista.filter((e) => e.frecventa === "Zilnic");
const doneAzi = zilnice.filter((e) => U.isLogged(store.log, e.key, aziISO)).length;
const c7b = U.conform7(b.lista, store.log, azi);
const streakB = U.streakZile(b.lista, store.log, azi);
return (
{b.eticheta}
{zilnice.length > 0 && (
{doneAzi} / {zilnice.length}
zilnice bifate azi
)}
{b.id === "cred" && sumOA > 0 && (
{/* aceeasi ordine ca in Plan si in Consola: alocat intai, realizat dupa
(auditul 25.07 a gasit aceeasi masura citita invers pe doua ecrane) */}
{sumOA} / {sumOR}
obiective alocate / realizate
)}
{b.id === "val" && (
{val.length}
rutine și angajamente asumate
)}
{c7b && (
{c7b.pct}%
conformare ultimele 7 zile ({c7b.done}/{c7b.expected})
)}
{streakB}
{streakB === 1 ? "zi completă la rând" : "zile complete la rând"}
);
})}
)}
);
}
window.AcasaHibridView = AcasaHibridView;
/* ── Calendarul (nou) ── */
function CalendarHibridView({ store, set }) {
const U = window.PRP_UTIL;
const azi = new Date(); azi.setHours(0, 0, 0, 0);
const [monday, setMonday] = React.useState(() => U.startOfWeek(azi));
const { cred, val, toate } = hibridItemi(store);
const days = Array.from({ length: 7 }, (_, i) => U.addDays(monday, i));
const toggleLog = hibridToggleLog(set);
const weekLabel = `${days[0].getDate()} ${days[0].toLocaleDateString("ro-RO", { month: "short" })} - ${days[6].getDate()} ${days[6].toLocaleDateString("ro-RO", { month: "short" })} ${days[6].getFullYear()}`;
const grupuri = [
["Zilnic", "Acțiunile și rutinele zilnice - se bifează doar ziua de azi; trecutul rămâne evidență"],
["Săptămânal", "Săptămânale - o bifă pe săptămână"],
["Lunar", "Lunare"],
["Trimestrial", "Trimestriale"],
["Anual", "Anuale"],
];
if (toate.length === 0) {
return (
Nimic de urmărit încă
Alege acțiuni din credințe și completează-ți rutinele valorilor în Codul meu -
toate apar aici, pe zile.
);
}
/* panourile de frecventa ale UNUI flux (credinte sau valori) - aceeasi mecanica pentru
ambele, doar lista difera; angajamentele intra tot aici, ca sa ramana in blocul sursei */
const panouriFlux = (lista) => {
const angajamente = lista.filter((e) => e.frecventa === "Angajament (la nevoie)");
/* angajamentele se inregistreaza pe ZIUA DE AZI; cat timp te uiti la alta saptamana, butonul
ar scrie in alta parte decat arata ecranul, deci se blocheaza (audit 25.07) */
const saptCurenta = days[0] <= azi && azi <= days[6];
return (
{grupuri.map(([frecv, titlu]) => {
const group = lista.filter((e) => e.frecventa === frecv);
if (!group.length) return null;
if (frecv === "Zilnic") {
return (
{/* doua SECTIUNI pe aceeasi pagina - credintele si valorile, fiecare cu grupurile ei
de frecventa si cu angajamentele ei (cerinta Marian 25.07). Sectiunea unui flux
fara itemi nu se afiseaza; daca ambele sunt goale, ecranul gol de mai sus preia. */}
{[
{ id: "cred", titlu: "Credințele mele", lista: cred },
{ id: "val", titlu: "Valorile mele", lista: val },
].map((b) => b.lista.length === 0 ? null : (
{b.titlu}
{panouriFlux(b.lista)}
))}
);
}
window.CalendarHibridView = CalendarHibridView;
/* ── Plan strategic (nou) ── */
function PlanHibridView({ store, set }) {
const U = window.PRP_UTIL;
const azi = new Date(); azi.setHours(0, 0, 0, 0);
const { cred, val } = hibridItemi(store);
const goto = (view) => () => set((s) => ({ ...s, view }));
if (cred.length === 0 && val.length === 0) {
return (
Planul strategic pornește din credințe și valori
Asumă-ți acțiuni din mecanismele credințelor și completează-ți rutinele valorilor
în Codul meu - progresul lor se urmărește aici, pe orizonturi de 30 / 60 / 90 / 120 de zile.
);
}
const start = store.consola.dataStart ? new Date(store.consola.dataStart + "T00:00:00") : null;
const startOk = !!start && !isNaN(start) && start <= azi;
const ziProces = startOk ? Math.floor((azi - start) / 86400000) + 1 : null;
const ziuaData = (n) => U.addDays(start, n - 1);
const ORIZONT = store.planOrizont;
const bifeInFereastra = (list, de, pana) => {
let n = 0;
list.forEach((e) => {
Object.keys(store.log[e.key] || {}).forEach((pk) => {
const d = U.pkToDate(pk);
if (d && d >= de && d <= pana) n += 1;
});
});
return n;
};
/* Data asumarii vine acum si de la valori (`adaugat`, scris de valori.jsx la completarea
campului), deci ambele fluxuri se judeca la fel. Inainte, valorile se judecau de la PRIMA
BIFA, iar o valoare asumata si niciodata bifata - exact cazul pe care rosul trebuie sa-l
prinda - nu devenea rosie niciodata (audit 25.07). */
const CREDINTE = U.CREDINTE;
const credActive = (CREDINTE || []).filter((c) => cred.some((e) => e.slug === c.slug));
const valActive = [];
val.forEach((e) => {
const g = valActive.find((x) => x.id === e.valoare.id);
if (g) g.itemi.push(e); else valActive.push({ id: e.valoare.id, nume: e.valoare.nume, itemi: [e] });
});
const credRanduri = credActive.map((c) => {
const ale = cred.filter((e) => e.slug === c.slug);
const oa = ale.reduce((n, e) => n + (e.oa || 0), 0);
const or = ale.reduce((n, e) => n + (e.orNr || 0), 0);
const impliniri = startOk ? bifeInFereastra(ale, ziuaData(1), ziuaData(ORIZONT)) : null;
return { nume: c.nume, itemi: ale, oa, or, rest: oa - or, impliniri };
});
const valRanduri = valActive.map((v) => ({
nume: v.nume, itemi: v.itemi,
impliniri: startOk ? bifeInFereastra(v.itemi, ziuaData(1), ziuaData(ORIZONT)) : null,
}));
const totCred = credRanduri.reduce((t, r) => ({ oa: t.oa + r.oa, or: t.or + r.or, imp: t.imp + (r.impliniri || 0) }), { oa: 0, or: 0, imp: 0 });
const totValImp = valRanduri.reduce((n, r) => n + (r.impliniri || 0), 0);
const nSapt = Math.ceil(ORIZONT / 7);
const saptamani = startOk ? Array.from({ length: nSapt }, (_, i) => {
const de = ziuaData(i * 7 + 1);
const panaBruta = ziuaData((i + 1) * 7);
const limita = ziuaData(ORIZONT);
const pana = panaBruta < limita ? panaBruta : limita;
return { nr: i + 1, de, pana, trecuta: pana < azi, viitoare: de > azi };
}) : [];
const [desfacute, setDesfacute] = React.useState([]);
const toggleRand = (id) => setDesfacute(desfacute.includes(id) ? desfacute.filter((x) => x !== id) : [...desfacute, id]);
/* Derulare orizontala COMUNA intre cele doua sectiuni: de la 90 de zile in sus tabelele nu mai
incap in panou, iar daca fiecare deruleaza singur, saptamana S12 a credintelor ajunge langa
S3 a valorilor - adica exact comparatia pe care alinierea trebuia s-o asigure. */
const panouri = React.useRef([]);
const sincronScroll = (i) => (ev) => {
const alt = panouri.current[1 - i];
const x = ev.currentTarget.scrollLeft;
if (alt && Math.abs(alt.scrollLeft - x) > 1) alt.scrollLeft = x;
};
/* cele DOUA sectiuni ale planului (cerinta Marian 25.07): fiecare cu cifrele ei deasupra
si cu benzile ei dedesubt. Latimea primei coloane e comuna, ca saptamanile sa fie aliniate
intre sectiuni; e singura constanta de geometrie de care depinde citirea comparativa. */
const LATIME_NUME = 268;
const LATIME_SAPT = 34; /* = .sapt-cell din index.html */
const SPATIU = 3; /* = border-spacing al .sapt-tbl */
/* Latimea TABELULUI, calculata, nu lasata pe seama continutului: cu `width:auto`, tabelul se
stramtoreaza cand depaseste panoul si comprima prima coloana ALTFEL in fiecare dintre cele
doua sectiuni (masurat la auditul din 25.07: 268/268 la 30 de zile, dar 144/185 la 120,
deci saptamanile nu mai cadeau una sub alta). Cu latime explicita, geometria e identica. */
const latimeTabel = LATIME_NUME + saptamani.length * LATIME_SAPT + (saptamani.length + 2) * SPATIU;
const sectiuni = [
{
tip: "cred", titlu: "Credințele mele", subtitlu: "Credințele în timp", randuri: credRanduri,
statistici: [
{ val: ({totCred.oa} / {totCred.or}),
lbl: "obiective alocate / realizate" },
...(startOk ? [{ val: totCred.imp, lbl: "împliniri în orizont" }] : []),
],
},
{
tip: "val", titlu: "Valorile mele", subtitlu: "Valorile în timp", randuri: valRanduri,
statistici: [
{ val: val.length, lbl: "rutine și angajamente asumate" },
...(startOk ? [{ val: totValImp, lbl: "împliniri în orizont" }] : []),
],
},
];
/* celula agregata a unei benzi - regula echitabila: rosu doar pe o saptamana trecuta in
care itemii erau deja asumati (credinte: campul adaugat; valori: de la prima bifa) */
const celulaBanda = (list, s) => {
if (s.viitoare) return
;
const active = list.filter((e) => {
const ad = e.adaugat ? new Date(e.adaugat + "T00:00:00") : start;
return ad && !isNaN(ad) && ad <= s.pana;
});
if (active.length === 0) return
{sec.subtitlu} - săptămânile orizontului de {ORIZONT} de zile
{/* latime NUMITA pe prima coloana, identica in ambele tabele: altfel fiecare
tabel si-ar lua latimea din cel mai lung nume propriu si saptamanile S1..Sn
ale celor doua sectiuni n-ar mai cadea una sub alta */}
{saptamani.map((s) => (
S{s.nr}
))}
{sec.randuri.map((r) => banda(r, sec.tip))}
))}
Verde = săptămână cu împliniri · roșu = săptămână trecută fără nicio împlinire (doar după asumare) ·
contur = săptămâna curentă. Apasă pe un rând pentru detalierea pe acțiuni.