🥗

NutryBuddy

Accesso separato: Accedi per Super Admin, nutrizionisti e pazienti già attivati. Attivazione Paziente serve solo per il primo accesso con codice invito.

Dopo il login l'app apre automaticamente il pannello corretto in base al ruolo: super_admin o nutritionist.

Il paziente può attivare l'accesso solo se il nutrizionista ha già creato la sua scheda.

Hai aperto il link di recupero: scegli una nuova password e poi accedi normalmente.

Dati salvati online: Auth, database e Storage privato.

NutryBuddyApplicazione web per nutrizionisti
Home NutryBuddy - panoramica piattaforma per nutrizionista e paziente

Eliminazione paziente

Stai per richiedere l’eliminazione definitiva di questo paziente.

Questa richiesta non cancella subito il database: verrà inviata al Super Admin per approvazione.
Per sicurezza, scrivi ELIMINA per confermare l’invio della richiesta.

Eliminazione definitiva paziente

Stai per eliminare definitivamente questo paziente dall’intera piattaforma.

L’operazione rimuoverà: • scheda paziente
• anamnesi e dati clinici
• visite e andamento
• file e documenti caricati
• report e piani alimentari
• dati associati nel backend/database
• eventuale account paziente collegato
Questa azione è permanente e NON può essere annullata.
Per sicurezza, scrivi ELIMINA per confermare l’eliminazione definitiva.

Eliminazione definitiva nutrizionista

Stai per eliminare definitivamente questo nutrizionista dall’intera piattaforma.

L’operazione rimuoverà: • profilo nutrizionista
• accesso e account collegato
• dashboard nutrizionista
• pazienti associati al nutrizionista
• schede, anamnesi, visite, file, report e piani dei pazienti collegati
• dati associati nel backend/database
Questa azione è permanente e NON può essere annullata.
Per sicurezza, scrivi ELIMINA per confermare l’eliminazione definitiva.
OK
`; } function previewProtocol(id){ const html=protocolHtml(id); if(!html){toast("Protocollo non trovato");return;} const win=window.open();win.document.write(html);win.document.close(); } function printProtocol(id){previewProtocol(id);} function patientMaterials(p){ ensureFileSettings(p); const files=(p.assignedFileIds||[]).map(id=>(state.libraryFiles||[]).find(f=>f.id===id)).filter(Boolean); if(!files.length)return ""; const fileCards=files.map(f=>`
📎
${esc(f.name)}${esc(f.category||'Materiale')} condiviso dal nutrizionista
`).join(""); return `

📚 Materiali del tuo percorso

Qui trovi solo i documenti che il nutrizionista ha selezionato per te dalla Libreria File.

${fileCards}
`; } function reportPro(p){ return `
${latest(p).toFixed(1)} kgPeso attuale
${Math.round(progress(p))}%Progresso
${p.checkins.length}Check-in
${p.visits.length}Visite

Report paziente

Riepilogo esportabile del percorso, con andamento, visite, piano e aderenza.

`; } function updateCreateBMI(){ const weight=parseFloat($("cWeight")?.value || ""); const height=parseFloat($("cHeight")?.value || ""); const bmiEl=$("cBMI"); if(!bmiEl)return; if(weight && height){ const bmi=weight/Math.pow(height/100,2); bmiEl.value=isFinite(bmi)?bmi.toFixed(1):""; }else{ bmiEl.value=""; } } function updateVisitBMI(){ const weight=parseFloat($("vWeight")?.value || ""); const height=parseFloat($("vHeight")?.value || ""); const bmiEl=$("vBMI"); if(!bmiEl)return; if(weight && height){ const bmi=weight/Math.pow(height/100,2); bmiEl.value=isFinite(bmi)?bmi.toFixed(1):""; }else{ bmiEl.value=""; } } function saveVisit(){ const p=getP(state.selected); if(!p)return; p.visits=p.visits||[]; p.checkins=p.checkins||[]; p.appointments=p.appointments||[]; const w=parseFloat($("vWeight")?.value || ""); const bodyFat=$("vBodyFat") ? $("vBodyFat").value.trim() : ""; const plan=$("vPlan") ? $("vPlan").value.trim() : ""; const notes=$("vNotes") ? $("vNotes").value.trim() : ""; const price=parseFloat($("vPrice")?.value || "0") || 0; const paymentStatus=$("vPaymentStatus") ? $("vPaymentStatus").value : "paid"; const paymentMethod=$("vPaymentMethod") ? $("vPaymentMethod").value : "Contanti"; const visitDate=$("vDate") ? $("vDate").value : today(); const height=$("vHeight") ? $("vHeight").value.trim() : ""; const bmi=$("vBMI") ? $("vBMI").value.trim() : ""; const leanMass=$("vLeanMass") ? $("vLeanMass").value.trim() : ""; const musclePct=$("vMusclePct") ? $("vMusclePct").value.trim() : ""; const dailyKcal=$("vDailyKcal") ? $("vDailyKcal").value.trim() : ""; const visceralFat=$("vVisceralFat") ? $("vVisceralFat").value.trim() : ""; const bodyWater=$("vBodyWater") ? $("vBodyWater").value.trim() : ""; const arm=$("vArm") ? $("vArm").value.trim() : ""; const waist=$("vWaist") ? $("vWaist").value.trim() : ""; const hips=$("vHips") ? $("vHips").value.trim() : ""; const chest=$("vChest") ? $("vChest").value.trim() : ""; const thighRight=$("vThighRight") ? $("vThighRight").value.trim() : ""; const thighLeft=$("vThighLeft") ? $("vThighLeft").value.trim() : ""; const nextDate=$("vNextDate") ? $("vNextDate").value : ""; const nextTime=$("vNextTime") ? $("vNextTime").value : ""; const nextDuration=parseInt($("vNextDuration") ? $("vNextDuration").value : "60",10); const nextType=$("vNextType") ? $("vNextType").value : "Controllo nutrizionale"; const nextLocation=$("vNextLocation") ? $("vNextLocation").value.trim() : ""; const nextReminder=$("vNextReminder") ? $("vNextReminder").value : "24 ore prima"; if(!w && !plan && !notes && !bodyFat && !height && !musclePct && !dailyKcal && !visceralFat && !leanMass && !bodyWater && !arm && !waist && !hips && !chest && !thighRight && !thighLeft && !price && !nextDate){ toast("Inserisci almeno un dato visita"); return; } const visitId=Date.now(); if(height) p.height=height; p.visits.push({ id:visitId,date:visitDate||today(),weight:w||latest(p),height,bmi,bodyFat,leanMass,musclePct,dailyKcal,visceralFat,bodyWater,arm,waist,hips,chest,thighRight,thighLeft, plan:plan||"Aggiornamento visita",notes:notes||"Visita aggiornata", nextVisit: nextDate ? formatDateIT(nextDate)+(nextTime?(" alle "+nextTime):"") : "Da definire", price,paymentStatus,paymentMethod }); if(nextDate){ p.appointments.push({ id:visitId,patientEmail:p.email,patientName:p.name,date:nextDate,time:nextTime||"09:00", duration:nextDuration,type:nextType,location:nextLocation||"Studio nutrizionale", reminder:nextReminder,notes:notes||plan||"Appuntamento NutriBuddy",createdAt:today() }); } if(w)p.checkins.push({date:today(),weight:w,mood:"Visita nutrizionista",water:"",steps:"",note:"Peso aggiornato durante la visita"}); if(plan)p.notes=plan; save(); renderDoctor(); showSection('visite', document.querySelector('#detail .portalTab:nth-child(4)')); toast(nextDate ? "Visita e appuntamento salvati" : "Visita salvata"); } function savePlan(){ const p=getP(state.selected); if(!p)return; p.notes=$("planText").value.trim(); p.planDaily=$("planDaily") ? $("planDaily").value.trim() : ""; save();renderDoctor();toast("Piano salvato"); } function openPatient(){ load(); const email=$("pLoginEmail").value.trim().toLowerCase(); const p=getP(email); if(!p){toast("Paziente non trovato");return} state.current=email; p.lastAccess=today(); save(); renderPatient(); show("patientDash"); } function renderPatient(){ const p=getP(state.current); if(!p)return; const pct=Math.round(progress(p)); const lastCheck=p.checkins[p.checkins.length-1]||{}; const lastVisit=p.visits && p.visits.length ? p.visits[p.visits.length-1] : null; $("pHello").textContent=`Bentornato ${p.name.split(" ")[0]} 👋`; $("pHeroSub").textContent=`Sei a ${Math.abs(latest(p)-p.targetWeight).toFixed(1)} kg dal tuo obiettivo 🎯`; $("pWeight").textContent=latest(p).toFixed(1)+" kg"; $("pProgress").textContent=pct+"%"; $("pLast").textContent=formatDateIT(p.lastAccess); $("pGoal").textContent=p.goal; if($("progressFill")) $("progressFill").style.width=pct+"%"; if($("buddyMsg")) $("buddyMsg").textContent=pct>=60?"🔥 Stai andando fortissimo! Continua così.":"💪 Ottimo andamento, continua ad aggiornare il percorso."; if($("todayWater")) $("todayWater").textContent=lastCheck.water?`💧 ${lastCheck.water} L`:"💧 --"; if($("todaySteps")) $("todaySteps").textContent=lastCheck.steps?`🚶 ${lastCheck.steps}`:"🚶 --"; if($("todayCheck")) $("todayCheck").textContent=lastCheck.date===today()?"⚖️ Aggiornato oggi":"⚖️ Da aggiornare"; if($("timelineGoal")) $("timelineGoal").textContent=`Obiettivo: ${Number(p.targetWeight).toFixed(1)} kg`; if($("timelineVisit")) $("timelineVisit").textContent=`Prossima visita: ${lastVisit && lastVisit.nextVisit ? lastVisit.nextVisit : "Da definire"}`; $("pChart").innerHTML=chart(p); if($("patientInsightSlot")) $("patientInsightSlot").innerHTML=renderPatientBuddyInsights(p); if($("patientMaterialsSlot")) $("patientMaterialsSlot").innerHTML=patientMaterials(p); renderPatientAppointments(p); renderPatientVisits(p); } function renderPatientAppointments(p){ const el=$("patientAppointments"); if(!el)return; el.innerHTML=appointmentsList(p).replaceAll("Google Calendar","Aggiungi a Google"); } function renderPatientVisits(p){ const el=$("patientVisits"); if(!el)return; const visits=p.visits||[]; if(!visits.length){el.innerHTML='
Nessuna visita inserita dal nutrizionista.
';return;} el.innerHTML=visits.slice().reverse().map(v=>`
🩺
Dr. Nutrizionista · ${esc(v.date)}

“${esc(v.plan||p.notes||"Piano iniziale creato dal nutrizionista")}"

Peso visita: ${v.weight?Number(v.weight).toFixed(1)+' kg':'-'} · BMI: ${esc(v.bmi||'-')} · Massa grassa: ${esc(v.bodyFat||'-')} · Vita: ${esc(v.waist||'-')}
Note: ${esc(v.notes||'-')}
Prossima visita: ${esc(v.nextVisit||'Da definire')}
`).join(''); } function patientCheckin(addDays){ const p=getP(state.current); if(!p)return; const w=parseFloat($("newWeight").value); if(!w){toast("Inserisci il nuovo peso");return} state.dayOffset+=Number(addDays||0); p.lastAccess=today(); p.checkins.push({date:today(),weight:w,mood:$("mood").value,water:$("waterInput").value.trim(),steps:$("stepsInput").value.trim(),note:$("pNote").value.trim()}); $("newWeight").value="";$("waterInput").value="";$("stepsInput").value="";$("pNote").value=""; save(); renderPatient(); toast("Check-in salvato e visibile al nutrizionista"); } function formatDateIT(dateStr){ if(!dateStr)return "-"; const d=new Date(dateStr+"T00:00:00"); if(isNaN(d))return dateStr; return d.toLocaleDateString("it-IT",{day:"2-digit",month:"2-digit",year:"numeric"}); } function formatDateGoogle(dateStr,timeStr,durationMin){ const start=new Date(`${dateStr}T${timeStr||"09:00"}:00`); const end=new Date(start.getTime()+(Number(durationMin||60)*60000)); const fmt=d=>d.toISOString().replace(/[-:]|\.\d{3}/g,""); return `${fmt(start)}/${fmt(end)}`; } function googleCalendarUrl(app){ const title=encodeURIComponent(`${app.type||"Visita nutrizionale"} - ${app.patientName||"Paziente"}`); const details=encodeURIComponent(`Appuntamento NutriBuddy\nPaziente: ${app.patientName||""}\nNote: ${app.notes||""}\nReminder: ${app.reminder||""}`); const location=encodeURIComponent(app.location||"Studio nutrizionale"); const dates=formatDateGoogle(app.date, app.time, app.duration||60); return `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${title}&dates=${dates}&details=${details}&location=${location}`; } function openGoogleCalendarAppointment(patientEmail,appointmentId){ const p=getP(patientEmail); if(!p)return; const app=(p.appointments||[]).find(a=>String(a.id)===String(appointmentId)); if(!app){toast("Appuntamento non trovato");return} window.open(googleCalendarUrl(app),"_blank"); } function previewGoogleCalendarFromForm(){ const p=getP(state.selected); if(!p)return; const date=$("vNextDate")?.value; if(!date){toast("Inserisci data prossima visita");return} const app={ patientName:p.name,date,time:$("vNextTime")?.value||"09:00", duration:$("vNextDuration")?.value||60,type:$("vNextType")?.value||"Controllo nutrizionale", location:$("vNextLocation")?.value||"Studio nutrizionale", reminder:$("vNextReminder")?.value||"24 ore prima", notes:$("vNotes")?.value||$("vPlan")?.value||"" }; window.open(googleCalendarUrl(app),"_blank"); } function appointmentsList(p){ const apps=(p.appointments||[]).slice().sort((a,b)=>(a.date+a.time).localeCompare(b.date+b.time)); if(!apps.length)return `
Nessun appuntamento programmato.
`; return `
${apps.map(a=>`
📅
${formatDateIT(a.date)} · ${esc(a.time||"-")}${esc(a.type||"Visita")} · ${esc(a.location||"Studio nutrizionale")}${esc(a.reminder||"Reminder")}
`).join("")}
`; } function visitsTable(p){ return ` ${p.visits.slice().reverse().map(v=>{ const hasAmount=Number(v.price||0)>0; const isPaid=v.paymentStatus==="paid"; const statusText=isPaid ? "Pagato" : "Non pagato"; const statusIcon=isPaid ? "✓" : "!"; const statusClass=isPaid ? "paid" : "unpaid"; return ``; }).join("")}
Data Peso Composizione Misure Importo Stato pagamento Piano Note
${esc(v.date)} ${Number(v.weight||0).toFixed(1)} kg BMI: ${esc(v.bmi||"-")}
Grasso: ${esc(v.bodyFat||"-")}
Muscolo: ${esc(v.musclePct||"-")}
Magra: ${esc(v.leanMass||"-")}
Acqua: ${esc(v.bodyWater||"-")}
Viscerale: ${esc(v.visceralFat||"-")}
Kcal: ${esc(v.dailyKcal||"-")}
Altezza: ${esc(v.height||"-")} cm
Braccio: ${esc(v.arm||"-")}
Vita: ${esc(v.waist||"-")}
Fianchi: ${esc(v.hips||"-")}
Torace: ${esc(v.chest||"-")}
Coscia Dx: ${esc(v.thighRight||"-")}
Coscia Sx: ${esc(v.thighLeft||"-")}
${Number(v.price||0).toFixed(2)} € ${hasAmount ? `
${statusIcon} ${statusText} ${esc(v.paymentMethod||"-")}
` : "-"}
${esc(v.plan||"-")} ${esc(v.notes||"-")}
`; } function checkinTable(p){ return `${p.checkins.slice().reverse().map(c=>``).join("")}
DataPesoUmoreAcquaPassiNota
${esc(c.date)}${Number(c.weight).toFixed(1)} kg${esc(c.mood||"-")}${esc(c.water||"-")}${esc(c.steps||"-")}${esc(c.note||"-")}
`; } function chart(p){ const data=(p.checkins||[]).map(c=>({date:c.date,weight:Number(c.weight)})).filter(d=>!Number.isNaN(d.weight)); if(data.length<2){ return '
Servono almeno 2 pesi per il grafico.
'; } const first=data[0].weight; const last=data[data.length-1].weight; const previous=data.length>1 ? data[data.length-2].weight : first; const target=Number(p.targetWeight||last); const deltaLast=last-previous; const missing=Math.abs(last-target); const improved=deltaLast<0; const stable=deltaLast===0; const trendLabel=improved ? "In miglioramento" : stable ? "Stabile" : "Da monitorare"; const trendText=improved ? "Il peso è in calo rispetto all’ultimo controllo" : stable ? "Peso stabile rispetto all’ultimo controllo" : "Il peso è salito rispetto all’ultimo controllo"; const trendIcon=improved ? "↘" : stable ? "→" : "↗"; const trendClass=improved ? "good" : stable ? "warn" : "warn"; const w=900,h=390; const padL=72,padR=128,padT=44,padB=62; const all=data.map(d=>d.weight).concat([target]); const rawMin=Math.min(...all); const rawMax=Math.max(...all); const range=Math.max(4,rawMax-rawMin); const min=Math.floor(rawMin-range*.25); const max=Math.ceil(rawMax+range*.25); const x=i=>padL+(i/(data.length-1))*(w-padL-padR); const y=v=>padT+((max-v)/(max-min))*(h-padT-padB); const pts=data.map((d,i)=>({x:x(i),y:y(d.weight),v:d.weight,date:d.date})); const path=pts.map((q,i)=>`${i?'L':'M'}${q.x.toFixed(1)},${q.y.toFixed(1)}`).join(" "); const fill=`${path} L${pts[pts.length-1].x.toFixed(1)},${h-padB} L${pts[0].x.toFixed(1)},${h-padB} Z`; const targetY=y(target); const gridVals=[]; for(let i=0;i<6;i++) gridVals.push(min+((max-min)/5)*i); const dateLabel=d=>{ const parts=String(d).split("-"); return parts.length===3 ? `${parts[2]}/${parts[1]}` : d; }; const targetLabelX=w-padR+42; const lastDate=dateLabel(data[data.length-1].date); return `
${last.toFixed(1)} kg Ultimo peso
${trendIcon} ${deltaLast.toFixed(1)} kg Variazione dall’ultimo controllo
${target.toFixed(1)} kg Obiettivo
Peso (kg) ${gridVals.map(v=>{ const gy=y(v); return ` ${v.toFixed(0)}`; }).join("")} ${target.toFixed(1)} kg Target ${pts.map(q=>``).join("")} ${pts.map(q=>` ${q.v.toFixed(1)} ${dateLabel(q.date)} `).join("")}
${trendIcon}
${trendLabel}${trendText}
⚖️
${missing.toFixed(1)} kgMancano al target
🗓️
${lastDate}Ultimo controllo
`; } function visitSeries(p, key){ return (p.visits||[]).map(v=>({date:v.date||'', value:parseFloat(String(v[key]||'').replace(',','.'))})).filter(x=>!Number.isNaN(x.value)); } function miniLineChart(title, series, opts={}){ const clean=(series||[]).filter(x=>!Number.isNaN(Number(x.value))); if(clean.length<2) return '
Servono almeno 2 visite con questo dato.
'; const w=760,h=280,padL=56,padR=34,padT=28,padB=44; const vals=clean.map(x=>Number(x.value)); let min=Math.min(...vals), max=Math.max(...vals); const range=Math.max(1,max-min); min=Math.floor((min-range*.2)*10)/10; max=Math.ceil((max+range*.2)*10)/10; const x=i=>padL+(i/(clean.length-1))*(w-padL-padR); const y=v=>padT+((max-v)/(max-min))*(h-padT-padB); const pts=clean.map((d,i)=>({x:x(i),y:y(Number(d.value)),v:Number(d.value),date:d.date})); const path=pts.map((q,i)=>`${i?'L':'M'}${q.x.toFixed(1)},${q.y.toFixed(1)}`).join(' '); const labels=[]; for(let i=0;i<5;i++) labels.push(min+((max-min)/4)*i); const formatDate=d=>{const a=String(d).split('-');return a.length===3?`${a[2]}/${a[1]}`:d}; const suffix=opts.suffix||''; return `
${title} ${labels.map(v=>`${v.toFixed(opts.decimals??1)}`).join('')} ${pts.map(q=>`${q.v.toFixed(opts.decimals??1)}${suffix}${formatDate(q.date)}`).join('')}
`; } function bmiChart(p){ return miniLineChart('BMI', visitSeries(p,'bmi'), {decimals:1}); } function bodyCompositionChart(p){ const fat=visitSeries(p,'bodyFat'); const lean=visitSeries(p,'leanMass'); return multiLineChart([ {name:'Massa grassa %', data:fat, color:'#2f9e68', suffix:'%'}, {name:'Massa magra kg', data:lean, color:'#ffb84d', suffix:' kg'} ]); } function circumferenceChart(p){ return multiLineChart([ {name:'Vita', data:visitSeries(p,'waist'), color:'#2f9e68'}, {name:'Fianchi', data:visitSeries(p,'hips'), color:'#ffb84d'}, {name:'Torace', data:visitSeries(p,'chest'), color:'#5d8cff'}, {name:'Braccio', data:visitSeries(p,'arm'), color:'#b26cff'}, {name:'Coscia Dx', data:visitSeries(p,'thighRight'), color:'#ff6f91'}, {name:'Coscia Sx', data:visitSeries(p,'thighLeft'), color:'#00a6a6'} ], {unit:' cm'}); } function multiLineChart(series, opts={}){ const usable=series.map(s=>({...s,data:(s.data||[]).filter(x=>!Number.isNaN(Number(x.value)))})).filter(s=>s.data.length>=2); if(!usable.length) return '
Servono almeno 2 visite con questi dati.
'; const w=760,h=280,padL=56,padR=34,padT=28,padB=56; const all=usable.flatMap(s=>s.data.map(d=>Number(d.value))); let min=Math.min(...all), max=Math.max(...all); const range=Math.max(1,max-min); min=Math.floor((min-range*.2)*10)/10; max=Math.ceil((max+range*.2)*10)/10; const maxLen=Math.max(...usable.map(s=>s.data.length)); const x=i=>padL+(i/(Math.max(1,maxLen-1)))*(w-padL-padR); const y=v=>padT+((max-v)/(max-min))*(h-padT-padB); const grid=[]; for(let i=0;i<5;i++) grid.push(min+((max-min)/4)*i); const fmtDate=d=>{const a=String(d).split('-');return a.length===3?`${a[2]}/${a[1]}`:d}; return `
${grid.map(v=>`${v.toFixed(0)}`).join('')} ${usable.map(s=>{const pts=s.data.map((d,i)=>({x:x(i),y:y(Number(d.value)),v:Number(d.value),date:d.date}));const path=pts.map((q,i)=>`${i?'L':'M'}${q.x.toFixed(1)},${q.y.toFixed(1)}`).join(' ');return `${pts.map(q=>``).join('')}`}).join('')} ${usable[0].data.map((d,i)=>`${fmtDate(d.date)}`).join('')}
${usable.map(s=>`
${s.name}Ultimo: ${s.data[s.data.length-1].value}${s.suffix||opts.unit||''}
`).join('')}
`; } function radarCompositionChart(p){ const v=(p.visits||[]).slice().reverse().find(x=>x.bodyFat||x.musclePct||x.bodyWater||x.visceralFat||x.bmi)||{}; const items=[ ['Grasso', Number(String(v.bodyFat||0).replace(',','.')), 45], ['Muscolo', Number(String(v.musclePct||0).replace(',','.')), 55], ['Acqua', Number(String(v.bodyWater||0).replace(',','.')), 70], ['Viscerale', Number(String(v.visceralFat||0).replace(',','.')), 20], ['BMI', Number(String(v.bmi||0).replace(',','.')), 35] ].filter(x=>x[1]>0); if(items.length<3) return '
Servono almeno 3 indicatori nella visita per mostrare il radar.
'; const w=560,h=360,cx=280,cy=180,r=132; const point=(i,scale=1)=>{const a=(-90+i*360/items.length)*Math.PI/180; return [cx+Math.cos(a)*r*scale, cy+Math.sin(a)*r*scale];}; const poly=items.map((it,i)=>point(i,Math.min(1,it[1]/it[2]))).map(p=>p.map(n=>n.toFixed(1)).join(',')).join(' '); return `
${[.25,.5,.75,1].map(s=>``).join('')} ${items.map((it,i)=>{const [x,y]=point(i,1.18);return `${it[0]}`}).join('')} ${items.map((it,i)=>{const [x,y]=point(i,Math.min(1,it[1]/it[2]));return ``}).join('')}
${items.map(it=>`
${it[1]}${it[0]}
`).join('')}
`; } function filterClinicalList(query){ const q=String(query||"").trim().toLowerCase(); document.querySelectorAll("#clinicalList .clinicalCondition").forEach(btn=>{ const name=btn.getAttribute("data-clinical-name")||""; btn.style.display = !q || name.includes(q) ? "flex" : "none"; }); } function toggleCondition(id){ const p=getP(state.selected); if(!p)return; p.conditions=p.conditions||[]; p.conditions=p.conditions.includes(id)?p.conditions.filter(x=>x!==id):p.conditions.concat([id]); save();renderDetail();showSection('anamnesi', document.querySelector('#detail .portalTab:nth-child(2)')); toast("Anamnesi aggiornata"); } function saveAnamnesis(){ const p=getP(state.selected); if(!p)return; const gv=id=>($(id)?.value||'').trim(); const gr=name=>document.querySelector(`input[name="${name}"]:checked`)?.value || ''; const gm=name=>Array.from(document.querySelectorAll(`input[name="${name}"]:checked`)).map(x=>x.value); p.anamnesis=gv('aAnamnesis'); p.lifestyle=gv('aLifestyle'); p.foodPrefs=gv('aFoodPrefs'); p.anamnesiData={ motivo:gv('ana_motivo'), obiettivo:gv('ana_obiettivo'), pesoObiettivo:gv('ana_pesoObiettivo'), motivazione:gr('motivazione'), professione:gv('ana_professione'), oreLavoro:gv('ana_oreLavoro'), sforzoLavoro:gr('sforzoLavoro'), vincoliLavorativi:gm('vincoliLavorativi'), pesoMin:gv('ana_pesoMin'), pesoMax:gv('ana_pesoMax'), pesoLong:gv('ana_pesoLong'), noteObiettivo:gv('ana_noteObiettivo'), nascita:gr('nascita'), pesoNascita:gv('ana_pesoNascita'), gravidanza:gr('gravidanza'), allattamento:gr('allattamento'), sviluppo:gr('sviluppo'), digestione:gr('digestione'), alvo:gr('alvo'), sonnoQualita:gr('sonnoQualita'), oreSonno:gv('ana_oreSonno'), sintomiGI:gv('ana_sintomiGI'), femNote:gv('ana_femNote'), femFertile:gr('femFertile'), femCiclo:gv('ana_femCiclo'), femDurataCiclo:gv('ana_femDurataCiclo'), femFrequenzaCiclo:gv('ana_femFrequenzaCiclo'), femFlusso:gv('ana_femFlusso'), femPms:gr('femPms'), femDolori:gr('femDolori'), femIntensitaDolore:gr('femIntensitaDolore'), femDescrDolore:gv('ana_femDescrDolore'), femContraccettivi:gr('femContraccettivi'), femGravidanzaFutura:gr('femGravidanzaFutura'), femAmenorrea:gr('femAmenorrea'), femGravidanze:gv('ana_femGravidanze'), femUltimaVisita:gv('ana_femUltimaVisita'), femDiagnosi:gv('ana_femDiagnosi'), pasti:gv('ana_pasti'), acqua:gv('ana_acqua'), fameNervosa:gr('fameNervosa'), appetito:gr('appetito'), foodPrefs:p.foodPrefs, esclusi:gv('ana_esclusi'), allergie:gv('ana_allergie'), dietaAttuale:gv('ana_dietaAttuale'), livelloAttivita:gr('livelloAttivita'), sport:gv('ana_sport'), freqSport:gv('ana_freqSport'), durataSport:gv('ana_durataSport'), obiettivoSport:gv('ana_obiettivoSport'), fumo:gr('fumo'), caffe:gv('ana_caffe'), vino:gr('vino'), birra:gr('birra'), cocktail:gr('cocktail'), superalcolici:gr('superalcolici'), quandoAlcol:gv('ana_quandoAlcol'), motivoAlcol:gv('ana_motivoAlcol'), stupefacenti:gm('stupefacenti'), farmaci:gv('ana_farmaci'), familiare:gv('ana_familiare') }; if(p.anamnesiData.obiettivo && !p.goal) p.goal=p.anamnesiData.obiettivo; if(p.anamnesiData.pesoObiettivo) p.targetWeight=Number(p.anamnesiData.pesoObiettivo)||p.targetWeight; save();renderDetail();showSection('anamnesi', document.querySelector('#detail .portalTab:nth-child(2)')); toast("Anamnesi salvata"); } function applyProtocol(){ const p=getP(state.selected); if(!p)return; const selected=selectedConditions(p); p.notes = selected.length ? `Protocollo generato da anamnesi: ${selected.join(", ")}.\n\nIndicazioni: monitorare aderenza, acqua, routine e alimenti trigger.` : p.notes; save();renderDetail();showSection('piano', document.querySelector('#detail .portalTab:nth-child(5)')); toast("Protocollo applicato al piano"); } function toastOnly(msg){ toast(msg); } function avg(arr){return arr.length?arr.reduce((a,b)=>a+b,0)/arr.length:0} function openWhatsApp(){ const p=getP(state.selected); if(!p)return; let phone=String(p.phone||"").replace(/\D/g,""); if(phone && !phone.startsWith("39")) phone="39"+phone; const msg=encodeURIComponent(`Ciao ${p.name.split(" ")[0]} 👋 il tuo Buddy non riceve aggiornamenti da ${daysSince(p)} giorni. Come sta andando il percorso?`); if(!phone){toast("Numero telefono non valido");return} window.open(`https://wa.me/${phone}?text=${msg}`,"_blank"); } function openEmail(){ const p=getP(state.selected); if(!p)return; const body=encodeURIComponent(`Ciao ${p.name.split(" ")[0]},\n\nil tuo Buddy non riceve aggiornamenti da ${daysSince(p)} giorni. Quando riesci, aggiorna il tuo percorso.\n\nA presto!`); window.location.href=`mailto:${encodeURIComponent(p.email)}?subject=${encodeURIComponent("Reminder NutriBuddy")}&body=${body}`; } document.addEventListener("click", function(e){ if(e.target && e.target.closest && !e.target.closest(".patientMenu, .patientSettingsBtn")){ closePatientMenus(); } }); function reset(){ if(confirm("Vuoi cancellare tutti i dati demo?")){ localStorage.removeItem(KEY); state={patients:[],selected:null,current:null,dayOffset:0,log:[],libraryFiles:[],csvImportBatches:[],patientListMode:"active",patientSearch:""}; save();home();toast("Demo resettata"); } } function esc(s){return String(s??"").replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[m]))} function escAttr(s){return String(s??"").replace(/[\"']/g,m=>({'"':"%22","'":"%27"}[m]))} load(); return {auth,showAuthForm,requestPasswordReset,updatePasswordFromRecovery,signIn,signUp,signUpPatient,signOut,home,doctor,showCreate,patientLogin,createPatient,openCsvImportModal,closeCsvImportModal,downloadCsvTemplate,triggerCsvImportFile,handleCsvImportFile,confirmCsvImport,openCsvImportHistoryModal,closeCsvImportHistoryModal,deleteCsvImportBatch,openPatient,patientCheckin,select,showSection,updateCreateBMI,updateRegistryBMI,updateVisitBMI,saveVisit,savePlan,saveRegistry,triggerProfilePhotoUpload,uploadProfilePhoto,removeProfilePhoto,toggleDocs,filterClinicalList,toggleCondition,saveAnamnesis,applyProtocol,toastOnly,openWhatsApp,openEmail,toggleSingleDoc,reset,togglePatientMenu,closePatientMenus,deletePatient,openDeleteRequestModal,closeDeleteRequestModal,confirmDeleteRequest,archivePatient,restorePatient,setPatientListMode,setPatientSearch,applyPatientSearch,clearPatientSearch,openStudioDashboard,renderStudioDashboard,openFileLibrary,renderFileLibrary,openSuperAdminPanel,renderSuperAdminPanel,createNutritionistFromAdmin,generateTemporaryPassword,updateUserRole,selectSuperAdminPatientGroup,copyNutritionistCredentials,openNutritionistPicker,closeNutritionistPicker,filterNutritionistPicker,viewAsNutritionist,exitNutritionistView,openDeleteNutritionistModal,closeDeleteNutritionistModal,confirmDeleteNutritionist,approvePatientDeletion,closeSuperAdminDeleteModal,confirmSuperAdminHardDelete,rejectPatientDeletion,copySuperAdminSql,copyPatientInvite,triggerLibraryUpload,uploadLibraryFile,openLibraryFile,deleteLibraryFile,saveAssignedFiles,openAssignedFile,previewGoogleCalendarFromForm,openGoogleCalendarAppointment,triggerPatientFileUpload,uploadPatientFile,openPatientFile,toggleFileVisibility,deletePatientFile,toggleProtocolVisibility,previewProtocol,printProtocol}; })(); async function superAdminRefreshWithFeedback(btn){ const oldText = btn ? btn.textContent : ''; try{ if(btn){ btn.classList.add('refreshLoading'); btn.textContent = 'Aggiornamento...'; } if(typeof loadAdminData === 'function') await loadAdminData(); if(typeof renderSuperAdmin === 'function') await renderSuperAdmin(); else if(typeof openSuperAdmin === 'function') await openSuperAdmin(); if(typeof toast === 'function') toast('Lista aggiornata'); else alert('Lista aggiornata'); }catch(err){ console.error('Refresh Super Admin error', err); if(typeof toast === 'function') toast('Errore aggiornamento: ' + (err.message || 'request failed')); else alert('Errore aggiornamento: ' + (err.message || 'request failed')); }finally{ if(btn){ btn.classList.remove('refreshLoading'); btn.textContent = oldText || 'Aggiorna'; } } }