/* ── 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` : ""}

{(() => { const zi = Math.floor((azi - new Date(azi.getFullYear(), 0, 0)) / 86400000); const dc = U.DICTIONAR || []; const rand = (t, tag) => t ? ( ) : null; return ( {rand(dc.length ? dc[zi % dc.length] : null, "credințe")} {rand(dictValori.length ? dictValori[zi % dictValori.length] : null, "valori")} ); })()}
{toate.length === 0 ? (
Procesul nu a început încă
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 */}
{[ { id: "cred", titlu: "Credințele mele", lista: cred, view: "credinte", buton: "Credințele", gol: "Nicio acțiune aleasă din credințe - deschide o credință și alege acțiunile concrete." }, { id: "val", titlu: "Valorile mele", lista: val, view: "valori", buton: "Valorile", gol: "Nicio rutină sau angajament din valori - completează-ți Codul meu." }, ].map((b) => { const bDaily = b.lista.filter((e) => e.frecventa === "Zilnic"); const bSapt = b.lista.filter((e) => e.frecventa === "Săptămânal"); const bAngaj = b.lista.filter((e) => e.frecventa === "Angajament (la nevoie)"); return (

{b.titlu} - astăzi

{b.lista.length === 0 &&

{b.gol}

} {b.lista.length > 0 && bDaily.length === 0 && (

Nimic zilnic - urmărirea se face pe perioadele alese.

)} {bDaily.map((e) => randAzi(e, aziISO))} {bSapt.length > 0 && (

Săptămâna asta

{bSapt.map((e) => randAzi(e, saptPk))}
)} {bAngaj.length > 0 && (

Angajamente (la nevoie)

{bAngaj.map((e) => randAzi(e, aziISO))}
)}
); })}

Săptămâna mea

{/* 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."}

) : (
{days.map((d, i) => { const iso = U.fmtISO(d); const viitor = d > azi; const done = viitor ? 0 : b.lista.filter((e) => U.isLogged(store.log, e.key, iso)).length; const plin = !viitor && done === b.lista.length; return ( ); })}
)}
))}
{/* 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 (

{titlu}

{days.map((d, i) => ( {U.ZILE_RO[i]}{d.getDate()} ))} {group.map((e) => ( {e.text} {hibridSub(e)} {days.map((d, i) => { const pk = U.fmtISO(d); const on = U.isLogged(store.log, e.key, pk); const eAzi = U.sameDay(d, azi); return ( ); })} ))}
); } const refD = days[0] <= azi && azi <= days[6] ? azi : days[0]; const pk = U.periodKey(frecv, refD); const editabil = pk === U.periodKey(frecv, azi); return (

{titlu}

{group.map((e) => { const on = U.isLogged(store.log, e.key, pk); return ( {e.text} {hibridSub(e)} · {U.periodLabel(frecv, refD)} ); })}
); })} {angajamente.length > 0 && (

Angajamente (la nevoie) - se înregistrează când situația a apărut

{angajamente.map((e) => { const pk = U.fmtISO(azi); const on = U.isLogged(store.log, e.key, pk); return ( {e.text} {hibridSub(e)} ); })}
)}
); }; return (
{weekLabel} {!U.sameDay(monday, U.startOfWeek(azi)) && ( )}
{/* 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 ; const bife = bifeInFereastra(active, s.de, s.pana); const cls = s.trecuta ? (bife > 0 ? "ok" : "rosu") : (bife > 0 ? "ok curenta" : "curenta"); return {bife > 0 ? bife : ""}; }; const banda = (r, tip) => { const id = tip + "|" + r.nume; const deschis = desfacute.includes(id); return ( toggleRand(id)}> {/* fara sufixul „· valoare": sectiunea in care sta banda spune deja de unde vine */} {r.nume} {saptamani.map((s) => celulaBanda(r.itemi, s))} {deschis && r.itemi.map((e) => ( {e.text} {saptamani.map((s) => { if (s.viitoare) return ; const bife = bifeInFereastra([e], s.de, s.pana); const cls = s.trecuta ? (bife > 0 ? "ok" : "") : (bife > 0 ? "ok curenta" : "curenta"); return {bife > 0 ? bife : ""}; })} ))} ); }; return (

Ce fac în primele {ORIZONT} de zile{ziProces ? ` · ziua ${Math.min(ziProces, ORIZONT)} din ${ORIZONT}` : ""}

{[30, 60, 90, 120].map((o) => ( ))}
{!startOk ? (
Lipsește data de start a procesului
Săptămânile orizontului se calculează de la data de start - seteaz-o în consolă.
) : ( {sectiuni.map((sec, idxSec) => sec.randuri.length === 0 ? null : (

{sec.titlu}

{sec.statistici.map((s, i) => (
{s.val}
{s.lbl}
))}
{ panouri.current[idxSec] = el; }} onScroll={sincronScroll(idxSec)}>

{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 */} ))} {sec.randuri.map((r) => banda(r, sec.tip))}
{saptamani.map((s) => ( S{s.nr}
))}

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.

)}
); } window.PlanHibridView = PlanHibridView;