Proof›My Day
,x.external_ref||x.receivable_number,x.title||x.engagement_name||'Inspection services')+''+statusChip(x.status)+''+esc(x.client_name||'—')+''+esc(x.due_on||'—')+''+money(x.balance)+' / '+money(x.amount)+''+(x.status==='draft'?' ':['open','partial','overdue'].includes(x.status)?(!x.sent_at?' ':'Published ')+'':'—')+'').join('')+''; } if(at==='payables'){ const importable=(state.ops.vendorBills||[]).filter(v=>!A.payables.some(p=>p.vendor_bill_id===v.id)); accountingBody='

Accounts Payable

Vendor bills flow through approval before posting and payment.

'+(importable.length?'':'')+'
'+ '
PayableStatusVendorDue / BalanceAction
'+ A.payables.map(x=>'
'+mainCell('$',x.payable_number,x.work_order_code||x.external_ref||'Vendor payable')+''+statusChip(x.status)+''+esc(x.partner_name||'—')+''+esc(x.due_on||'—')+''+money(x.balance)+' / '+money(x.amount)+''+(x.status==='draft'?'':x.status==='approved'?'':['open','partial','overdue'].includes(x.status)?'':'—')+'
').join('')+'
'; } if(at==='statements'){ const s=A.statements||{},is=s.income_statement||{},bs=s.balance_sheet||{},cf=s.cash_flow||{}; accountingBody='
'+metric('REVENUE',money(is.revenue||0),'statement period')+metric('EXPENSES',money(is.expenses||0),'statement period')+metric('NET INCOME',money(is.net_income||0),'statement period')+metric('CASH CHANGE',money(cf.net_change||0),'statement period')+'
'+ '

Income statement

'+esc(s.period?.start||'')+' → '+esc(s.period?.end||'')+'
'+ (is.accounts||[]).map(x=>'
'+esc(x.name)+''+money(x.account_type==='revenue'?x.net_credit:x.net_debit)+'
').join('')+'
'+ '

Balance sheet

Posted through '+esc(s.period?.end||'')+'
'+ [...(bs.assets||[]),...(bs.liabilities||[]),...(bs.equity||[])].map(x=>'
'+esc(x.name)+''+money(x.balance)+'
').join('')+'
'; } if(at==='profitability'){ accountingBody='

Profitability

Posted ledger revenue and expense contribution by client/engagement.

'+ '
EngagementClientRevenueExpenseContribution
'+ A.profitability.map(x=>'
'+mainCell('↗',x.engagement_name,x.engagement_code)+''+esc(x.client_name||'—')+''+money(x.revenue)+''+money(x.expenses)+''+money(x.contribution)+''+(x.margin_pct==null?'—':Number(x.margin_pct).toFixed(1)+'% margin')+'
').join('')+'
'; } if(at==='integrations'){ accountingBody='

Finance integrations

Connected accounting and bank services.

'+ '
ProviderStatusTypeLast syncAuthority
'+ A.connectors.map(x=>'
'+mainCell('◇',x.provider,x.connector_type)+''+statusChip(x.status)+''+esc(x.connector_type)+''+esc(x.last_sync_at?new Date(x.last_sync_at).toLocaleString():'Never')+''+esc(JSON.stringify(x.authoritative_scope||{}))+'
').join('')+'
'+ '

Bank feed

Imported transactions are reconciliation evidence, not silently posted ledger entries.

'+ '
TransactionStatusDateDirectionAmount
'+ A.bank.map(x=>'
'+mainCell('$',x.description||x.external_transaction_ref,x.provider||'bank feed')+''+statusChip(x.status)+''+esc(x.transaction_date)+''+esc(x.direction)+''+money(x.amount)+'
').join('')+'
'; } body=subbar+accountingBody; } return '
BUSINESS OPERATIONS · F1.0

'+esc(d.organization?.name||'Proof Business')+'

Run the business around every inspection—from sale through vendor work, accounting and renewal.

'+tabbar+body; } async function createReceivablePreview(){ const client=state.business.clients[0];if(!client){show('Create a client first.');return} const amount=Number(prompt('Receivable amount','18900'));if(!Number.isFinite(amount)||amount<=0)return; const due=prompt('Due date',new Date(Date.now()+30*86400000).toISOString().slice(0,10)); const engagement=state.business.engagements[0]; try{ await api('/v1/accounting/receivables',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({client_id:client.id,engagement_id:engagement?.id||null,amount,due_on:due,currency:'USD',authoritative_source:'proof'})}); show('Draft receivable created.');await refresh();state.accounting.tab='receivables';render('business'); }catch(e){show('Receivable failed: '+e.message)} } async function postReceivablePreview(id){ if(!confirm('Post this receivable to the accounting ledger?'))return; try{await api('/v1/accounting/receivables/'+id+'/post',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({posted_by_name:'Tevye Harper'})});show('Receivable posted.');await refresh();state.accounting.tab='receivables';render('business')}catch(e){show('Posting failed: '+e.message)} } async function payReceivablePreview(id){ const x=state.accounting.receivables.find(r=>r.id===id);if(!x)return; const amount=Number(prompt('Payment amount',String(x.balance||0)));if(!Number.isFinite(amount)||amount<=0)return; try{await api('/v1/accounting/receivables/'+id+'/payments',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({amount,received_by_name:'Tevye Harper',method:'Payment'})});show('Client payment posted.');await refresh();state.accounting.tab='receivables';render('business')}catch(e){show('Payment failed: '+e.message)} } async function importVendorBillPreview(){ const bill=(state.ops.vendorBills||[]).find(v=>!state.accounting.payables.some(p=>p.vendor_bill_id===v.id));if(!bill){show('No unimported vendor bill.');return} try{await api('/v1/accounting/vendor-bills/'+bill.id+'/payable',{method:'POST',body:JSON.stringify({})});show('Vendor bill imported to A/P.');await refresh();state.accounting.tab='payables';render('business')}catch(e){show('Import failed: '+e.message)} } async function approvePayablePreview(id){ try{await api('/v1/accounting/payables/'+id+'/approve',{method:'POST',body:JSON.stringify({approved_by_name:'Tevye Harper'})});show('Payable approved.');await refresh();state.accounting.tab='payables';render('business')}catch(e){show('Approval failed: '+e.message)} } async function postPayablePreview(id){ if(!confirm('Post this approved payable to the ledger?'))return; try{await api('/v1/accounting/payables/'+id+'/post',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({posted_by_name:'Tevye Harper'})});show('Payable posted.');await refresh();state.accounting.tab='payables';render('business')}catch(e){show('Posting failed: '+e.message)} } async function payPayablePreview(id){ const x=state.accounting.payables.find(p=>p.id===id);if(!x)return; const amount=Number(prompt('Payment amount',String(x.balance||0)));if(!Number.isFinite(amount)||amount<=0)return; if(!confirm('Record and post this vendor payment?'))return; try{await api('/v1/accounting/payables/'+id+'/payments',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({amount,paid_by_name:'Tevye Harper',method:'Payment'})});show('Vendor payment posted.');await refresh();state.accounting.tab='payables';render('business')}catch(e){show('Payment failed: '+e.message)} } async function createClientPreview(){ const name=prompt('Client name','New Restaurant Group');if(!name)return; const code=prompt('Client code','CLIENT-'+Math.floor(Math.random()*9000+1000));if(!code)return; try{ await api('/v1/business/clients',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({name,code,owner_name:'Tevye Harper'})}); show('Client created.');await refresh(); }catch(e){show('Could not create client: '+e.message)} } async function createOpportunityPreview(){ const name=prompt('Opportunity name','Annual Food Safety Program');if(!name)return; const value=prompt('Estimated value','25000');if(value===null)return; const client=state.business.clients[0]; try{ await api('/v1/business/opportunities',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({name,code:'OPP-'+Date.now().toString().slice(-6),client_id:client?.id||null,stage:'inquiry',source:'direct',estimated_value:Number(value)||0,probability:25,owner_name:'Tevye Harper'})}); show('Opportunity created.');await refresh(); }catch(e){show('Could not create opportunity: '+e.message)} } async function buildEstimatePreview(){ const o=state.business.opportunities.find(x=>x.id===state.business.selectedOpportunity);if(!o)return; const service=state.business.services.find(s=>s.category==='inspection')||state.business.services[0];if(!service){show('Add a service before building an estimate.');return} const quantityRaw=prompt('Quantity for '+service.name,'1');if(quantityRaw===null)return; const quantity=Number(quantityRaw);if(!Number.isFinite(quantity)||quantity<=0){show('Enter a valid quantity.');return} const priceRaw=prompt('Unit price',String(service.base_price||0));if(priceRaw===null)return; const unitPrice=Number(priceRaw);if(!Number.isFinite(unitPrice)||unitPrice<0){show('Enter a valid price.');return} try{ await api('/v1/business/opportunities/'+o.id+'/estimates',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({actor_name:'Tevye Harper',valid_until:new Date(Date.now()+30*24*60*60*1000).toISOString().slice(0,10),lines:[{service_id:service.id,description:service.name,quantity,unit_price:unitPrice}]})}); show('Estimate created.');await refresh();state.business.tab='pipeline';render('business'); }catch(e){show('Estimate failed: '+e.message)} } async function publishProposalPreview(){ const o=state.business.opportunities.find(x=>x.id===state.business.selectedOpportunity);if(!o?.estimate_id)return; try{ await api('/v1/business/estimates/'+o.estimate_id+'/proposal',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({actor_name:'Tevye Harper',title:o.name})}); show('Proposal published.');await refresh();state.business.tab='pipeline';render('business'); }catch(e){show('Proposal failed: '+e.message)} } async function acceptProposalPreview(){ const o=state.business.opportunities.find(x=>x.id===state.business.selectedOpportunity);if(!o?.proposal_id)return; try{ await api('/v1/business/proposals/'+o.proposal_id+'/accept',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({accepted_by_name:'Client Contact'})}); show('Proposal acceptance recorded. Contract execution is still required.');await refresh();state.business.tab='pipeline';render('business'); }catch(e){show('Acceptance failed: '+e.message)} } async function convertOpportunityPreview(){ const o=state.business.opportunities.find(x=>x.id===state.business.selectedOpportunity);if(!o)return; try{ await api('/v1/business/opportunities/'+o.id+'/convert',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({actor_name:'Tevye Harper',owner_name:'Tevye Harper',starts_on:new Date().toISOString().slice(0,10),cadence:'monthly'})}); show('Draft contract, engagement and inspection program created.');await refresh();state.business.tab='contracts';render('business'); }catch(e){show('Conversion failed: '+e.message)} } async function executeContractPreview(id){ if(!confirm('Record this contract as executed and activate the linked engagement/program?'))return; try{ await api('/v1/business/contracts/'+id+'/execute',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({executed_by_name:'Tevye Harper',starts_on:new Date().toISOString().slice(0,10)})}); show('Contract executed. Delivery activated.');await refresh();state.business.tab='contracts';render('business'); }catch(e){show('Execution failed: '+e.message)} } function dateKey(v){const d=new Date(v);return Number.isNaN(d.getTime())?'':d.toISOString().slice(0,10)} function shortDate(v){const d=new Date(v);return Number.isNaN(d.getTime())?'—':new Intl.DateTimeFormat([], {weekday:'short',month:'short',day:'numeric'}).format(d)} function scheduleView(){ const ops=state.ops; const mode=ops.scheduleMode||'week'; const resourceFilter=ops.resourceFilter||'all'; const events=(ops.schedule||[]).filter(e=>resourceFilter==='all'||e.resource_id===resourceFilter||e.assignee_name===resourceFilter); const resources=ops.resources||[]; const now=new Date(),todayKey=now.toISOString().slice(0,10); const todayCount=events.filter(e=>dateKey(e.starts_at)===todayKey).length; const conflicts=state.signals.filter(s=>s.signal_type==='schedule_conflict').length; const vendorCount=events.filter(e=>e.assignee_type==='vendor').length; const inspectorCount=events.filter(e=>e.assignee_type==='inspector').length; const modes=['day','week','month','resources']; const modeButtons=modes.map(x=>'').join(''); const resourceOptions=''+resources.map(r=>'').join(''); function eventCard(e){ return '
'+ '
'+esc(e.event_type)+(e.territory_code?' · '+esc(e.territory_code):'')+'

'+esc(e.title)+'

'+statusChip(e.status)+'
'+ '
'+esc(fmtTime(e.starts_at))+'–'+esc(fmtTime(e.ends_at))+' · '+esc(e.assignee_name||'Unassigned')+'
'+ '
'+esc(e.location_name||e.client_name||'')+(e.travel_minutes!=null?' · '+esc(e.travel_minutes)+' min travel':'')+'
'+ '
'; } let calendar=''; if(mode==='day'){ const dayEvents=events.filter(e=>dateKey(e.starts_at)===todayKey); calendar='

Today

'+esc(shortDate(todayKey+'T12:00:00'))+' · resource-aware operating day
'+ (dayEvents.map(eventCard).join('')||'
No work scheduled today.
')+'
'; } if(mode==='week'){ const startDate=new Date(now);startDate.setHours(0,0,0,0);startDate.setDate(startDate.getDate()-startDate.getDay()); const days=[];for(let i=0;i<7;i++){const d=new Date(startDate);d.setDate(startDate.getDate()+i);days.push(d)} calendar='

Operating week

Inspections, vendor work and follow-ups share one calendar.
'+ days.map(d=>{const k=d.toISOString().slice(0,10),list=events.filter(e=>dateKey(e.starts_at)===k);return '
'+esc(shortDate(d))+'
'+list.map(eventCard).join('')+(list.length?'':'
Open
')+'
'}).join('')+ '
'; } if(mode==='month'){ const year=now.getFullYear(),month=now.getMonth(),first=new Date(year,month,1),last=new Date(year,month+1,0),startPad=first.getDay(); const cells=[]; for(let i=0;i'); for(let day=1;day<=last.getDate();day++){ const d=new Date(year,month,day),k=d.toISOString().slice(0,10),list=events.filter(e=>dateKey(e.starts_at)===k); cells.push('
'+day+''+(list.length?''+list.length+'':'')+'
'+list.slice(0,3).map(e=>'
'+esc(fmtTime(e.starts_at))+' '+esc(e.title)+'
').join('')+(list.length>3?'
+'+(list.length-3)+' more
':'')+'
'); } calendar='

'+esc(new Intl.DateTimeFormat([], {month:'long',year:'numeric'}).format(now))+'

Portfolio calendar view.
'+['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].map(x=>'
'+x+'
').join('')+cells.join('')+'
'; } if(mode==='resources'){ calendar='

Resource board

Inspector/vendor capacity, availability and assigned work.
'+ resources.map(r=>{ const assigned=events.filter(e=>e.resource_id===r.id||e.assignee_name===r.name); const av=(r.availability||[]).slice(0,3).map(a=>shortDate(a.starts_at)+' '+fmtTime(a.starts_at)+'–'+fmtTime(a.ends_at)).join(' · '); return '
'+mainCell(r.resource_type==='vendor'?'◇':'◉',r.name,(r.skills||[]).join(', '))+'
'+statusChip(r.status)+'
Availability: '+esc(av||'Not configured')+'
'+assigned.slice(0,6).map(eventCard).join('')+'
'; }).join('')+'
'; } return '
SCHEDULE · F0.9

Operations Calendar

Resource-aware scheduling for inspections, recurring programs, vendor visits and follow-up work.

'+ '
'+metric('TODAY',todayCount,'scheduled items')+metric('INSPECTOR',inspectorCount,'inspection assignments')+metric('VENDOR',vendorCount,'vendor events')+metric('CONFLICTS',conflicts,'Current signals')+'
'+ '
'+modeButtons+'
'+ calendar; } async function rescheduleEventPreview(id){ const e=state.ops.schedule.find(x=>x.id===id);if(!e)return; const start=prompt('New start',new Date(e.starts_at).toISOString().slice(0,16));if(!start)return; const end=prompt('New end',new Date(e.ends_at).toISOString().slice(0,16));if(!end)return; const options=state.ops.resources.map((r,i)=>(i+1)+'. '+r.name+' · '+r.resource_type).join('\n'); const pick=prompt('Resource number (blank keeps current):\n'+options,''); let resource=null;if(pick){resource=state.ops.resources[Number(pick)-1]||null} try{ await api('/v1/schedule/events/'+id+'/reschedule',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({starts_at:start,ends_at:end,resource_id:resource?.id||e.resource_id||null,assignee_name:resource?.name||e.assignee_name||null,allow_outside_availability:true})}); show('Event rescheduled.');await refresh();render('schedule'); }catch(err){show(err.body?.error==='schedule_conflict'?'Schedule conflict detected.':'Reschedule failed: '+err.message)} } async function createScheduleSeriesPreview(){ const name=prompt('Recurring series name','Monthly Food Safety Inspection');if(!name)return; const frequency=prompt('Frequency: daily, weekly, monthly','monthly')||'monthly'; const count=Number(prompt('Number of occurrences','12')||12); const start=prompt('First start','2026-10-05T09:00:00');if(!start)return; const location=state.locations[0],resource=state.ops.resources.find(r=>r.resource_type==='inspector')||state.ops.resources[0]; try{ await api('/v1/schedule/series',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({name,event_type:'inspection',frequency,interval:1,count,first_start:start,duration_minutes:90,location_id:location?.id||null,resource_id:resource?.id||null,assignee_type:resource?.resource_type||'inspector',assignee_name:resource?.name||null})}); show('Recurring series created.');await refresh();render('schedule'); }catch(e){show('Series failed: '+e.message)} } async function createAvailabilityPreview(){ if(!state.ops.resources.length){show('No schedule resources available.');return} const options=state.ops.resources.map((r,i)=>(i+1)+'. '+r.name+' · '+r.resource_type).join('\n'); const pick=prompt('Resource:\n'+options,'1');if(!pick)return; const resource=state.ops.resources[Number(pick)-1];if(!resource)return; const start=prompt('Available from','2026-10-05T08:00:00');if(!start)return; const end=prompt('Available until','2026-10-05T17:00:00');if(!end)return; try{ await api('/v1/schedule/resources/'+resource.id+'/availability',{method:'POST',body:JSON.stringify({starts_at:start,ends_at:end,availability_type:'available'})}); show('Availability added.');await refresh();state.ops.scheduleMode='resources';render('schedule'); }catch(e){show('Availability failed: '+e.message)} } function reportsView(){ const r=state.reporting,tab=r.tab||'reports'; const tabs=['reports','templates','recommendations','haccp']; if(r.composer)tabs.splice(1,0,'composer'); const labels={reports:'Reports',composer:'Composer',templates:'Templates',recommendations:'Suggested Fixes',haccp:'HACCP'}; const tabbar='
'+tabs.map(t=>'').join('')+'
'; let body=''; if(tab==='reports'){ const rows=r.reports.map(x=>'
'+mainCell('▤',x.title,x.code+' · '+(x.template_name||x.report_type))+''+statusChip(x.status)+''+esc(x.mode||'live')+' · v'+esc(x.current_version||0)+''+esc(x.location_name||x.client_name||'—')+'
').join(''); const locButtons=state.locations.slice(0,4).map(l=>'').join(''); body='

Reports + complete history

Create, compose, lock, publish and share evidence-backed inspection records.

'+locButtons+'
'+ '
ReportStatusModeScopeExport
'+rows+'
'; } if(tab==='composer'){ const c=r.composer; if(!c){body='

No report selected.

'} else{ const locked=c.status==='locked'||c.status==='published'; const brand=c.brand_profile||r.brands.find(x=>x.id===c.brand_profile_id)||null; const sections=(c.sections||[]).map((s,i)=>'
'+esc(i+1)+''+esc(s.title)+''+esc(s.section_key)+' · position '+esc(s.position)+''+esc(s.content?.source||'custom')+''+(locked?'Locked':'Editable')+''+(!locked?'':'')+'
').join(''); const shares=(c.shares||[]).map(s=>'
'+esc(s.audience_label||'Shared viewer')+''+statusChip(s.revoked_at?'revoked':'active')+''+esc(s.expires_at?new Date(s.expires_at).toLocaleString():'No expiry')+''+esc(s.created_by_name||'—')+''+(s.revoked_at?'—':'')+'
').join(''); const versions=(c.versions||[]).map(v=>'
v'+esc(v.version)+' · '+esc(v.title)+''+statusChip(v.mode)+''+esc(new Date(v.created_at).toLocaleDateString())+''+esc(v.created_by_name||'—')+''+esc(new Date(v.created_at).toLocaleString())+'
').join(''); body='
REPORT

'+esc(c.title)+'

'+esc(c.report_type)+' · '+esc(c.location_name||c.client_name||'')+' · '+esc(c.mode||'live')+'

'+ '
'+metric('STATUS',String(c.status||'draft').toUpperCase(),'status')+metric('MODE',String(c.mode||'live').toUpperCase(),'live / hybrid / locked')+metric('VERSION','v'+String(c.current_version||0),'saved version')+metric('SECTIONS',String((c.sections||[]).length),'sections')+'
'+ '

Composition

Arrange report sections, choose sharing settings, and lock a version before sharing.
'+ '
Brand: '+esc(brand?.name||'None')+' · Sharing: '+esc(c.audience_policy?.classification||'client_safe')+'
'+ '
'+ (!locked?'':'')+ ((c.current_version||0)>0&&c.status!=='published'?'':'')+ ((c.current_version||0)>0?'':'')+ ''+ '
'+ '
✦
Avery Reporting
Drafting help

Avery can prepare summaries, organize evidence and suggest report structure. Locking, publishing and sensitive sharing retain explicit human authority.

'+ '

Sections

Arrange the sections included in this report.

'+ '
SectionSourceStateOrder
'+sections+'
'+ '

Locked versions

Each version is saved as a fixed record.

'+ '
VersionModeSavedLocked byCreated
'+versions+'
'+ '

External shares

External viewers receive a specific saved version, not unrestricted app access.

'+ '
AudienceStatusExpiresCreated byAction
'+shares+'
'; } } if(tab==='templates'){ body='

Report templates

Versioned templates define audience, sections and history behavior.

'+ '
TemplateStatusTypeVersionAudience
'+ r.templates.map(t=>'
'+mainCell('▤',t.name,t.code)+''+statusChip(t.status)+''+esc(t.report_type)+'v'+esc(t.version)+''+esc(t.audience_mode)+'
').join('')+'
'+ '

Brand profiles

Reusable client-facing identity applied during report composition.

'+ '
BrandStatusCompanyAccentFooter
'+ r.brands.map(b=>'
'+mainCell('B',b.name,b.code)+''+statusChip(b.status)+''+esc(b.company_name||'—')+''+esc(b.accent_hex||'—')+''+esc(b.footer_text||'—')+'
').join('')+'
'; } if(tab==='recommendations'){ body='

Suggested fixes

Human and Avery-drafted improvements remain advisory until reviewed and accepted.

'+ '
SuggestionStatusCategory / sourceLocationReview
'+ r.recommendations.map(x=>{ let action='—'; if(x.status==='draft')action=''; else if(x.status==='suggested')action=''; else if(x.status==='accepted')action=''; return '
'+mainCell('→',x.title,x.recommendation_text)+''+statusChip(x.status)+''+esc(x.category)+''+esc(x.source_type||'human')+' · '+esc(x.authority_mode)+''+esc(x.location_name||'—')+''+action+'
'; }).join('')+'
'; } if(tab==='haccp'){ const selected=r.haccpPlan; if(selected){ const steps=selected.steps||[],monitoring=selected.monitoring||[],versions=selected.versions||[]; const ccpCount=steps.filter(x=>x.is_ccp).length; const unresolved=monitoring.filter(x=>x.deviation_detected&&!['resolved','verified'].includes(x.status)).length; const locked=selected.status==='approved'; const sourceLabel=(refs)=>Array.isArray(refs)&&refs.length?refs.map(x=>x.label||x.title||x.source_id).join(', '):'No source attached'; const stepRows=steps.map((s,i)=>'
'+ '
'+esc(s.code)+' · STEP '+(i+1)+'

'+esc(s.name)+'

'+(s.is_ccp?'CCP · ':'')+esc((s.hazards||[]).join(', ')||'No hazards recorded')+'
'+statusChip(s.is_ccp?'ccp':'configured')+'
'+ '
'+ '
CRITICAL LIMITS
'+esc(JSON.stringify(s.critical_limits||{}))+'
'+ '
MONITORING
'+esc(s.monitoring_procedure||'Not configured')+(s.monitoring_frequency?' · '+esc(s.monitoring_frequency):'')+'
'+ '
CORRECTIVE ACTION
'+esc(s.corrective_action_procedure||'Not configured')+'
'+ '
VERIFICATION
'+esc(s.verification_procedure||'Not configured')+'
'+ '
'+ '
Sources: '+esc(sourceLabel(s.source_refs))+'
'+ '
'+ (!locked?'':'')+ ''+ '
').join(''); const monitoringRows=monitoring.map(m=>'
'+mainCell(m.deviation_detected?'!':'✓',m.step_name,esc(JSON.stringify(m.observed_value||{})),m.deviation_detected?'red':'')+ ''+statusChip(m.deviation_detected?'review':'done')+''+esc(m.captured_by_name||'—')+''+esc(new Date(m.captured_at).toLocaleString())+''+esc(m.corrective_action_id?'Action linked':'No action')+'
').join(''); const versionRows=versions.map(v=>'
v'+esc(v.version)+''+statusChip(v.status)+''+esc(new Date(v.created_at).toLocaleDateString())+''+esc(v.approved_by_name||v.created_by_name||'—')+''+esc(new Date(v.created_at).toLocaleString())+'
').join(''); body='
HACCP PLAN

'+esc(selected.name)+'

'+esc(selected.location_name||'No location')+' · '+esc(selected.code)+' · v'+esc(selected.version)+'

'+ '
'+metric('STATUS',String(selected.status||'draft').toUpperCase(),'plan state')+metric('STEPS',String(steps.length),'process steps')+metric('CCPs',String(ccpCount),'configured control points')+metric('DEVIATIONS',String(unresolved),'unresolved monitoring')+'
'+ '

Plan review

Critical limits and procedures come from your approved plan and referenced sources.
'+ '
'+esc(selected.effective_on||'Not set')+'Effective date
'+ '
'+esc(selected.review_due_on||'Not set')+'Review due
'+ '
'+esc(selected.approved_by_name||'Approval pending')+''+esc(selected.approved_at?new Date(selected.approved_at).toLocaleString():'Draft may be edited')+'
'+ '
'+esc((selected.inspection_links||[]).length)+' inspectionsPlan-to-inspection relationships
'+ '
'+ (!locked?'':'')+ '
'+ '
✦
Avery HACCP
Drafting help · review required

Avery can organize plan steps, surface missing controls, compare monitoring history and draft advisory improvements. It does not invent critical limits or approve plans.

'+ '

Process plan

Hazards, controls, monitoring, corrective action, verification, and source references.

'+ (stepRows||'
No process steps yet.
')+ '

Monitoring history

Observed values are records. Deviation flags require human review and do not themselves constitute a regulatory conclusion.

'+ '
Step / observationStateCaptured byTimeAction
'+monitoringRows+'
'+ '

Approved versions

Approved versions keep their source references and history.

'+ '
VersionStatusSavedApproved byCreated
'+versionRows+'
'; }else{ const templates=r.haccpTemplates.map(t=>'
'+esc(t.code)+' · V'+esc(t.version)+'

'+esc(t.name)+'

'+esc(t.description||'')+'
'+statusChip(t.status)+'
'+esc((t.steps||[]).length)+' template steps · critical limits are configurable, not assumed.
').join(''); const plans=r.haccpPlans.map(p=>'
'+mainCell('H',p.name,p.code+' · '+(p.location_name||'No location'))+''+statusChip(p.status)+''+esc(p.template_name||'Custom')+'v'+esc(p.version)+''+esc(p.approved_by_name||'Approval pending')+'
').join(''); const sources=r.haccpSources.map(s=>'
'+mainCell('§',s.title,s.source_type+(s.jurisdiction?' · '+s.jurisdiction:''))+''+statusChip(s.verification_status)+''+esc(s.edition||'—')+''+esc(s.effective_on||'—')+''+esc(s.verified_by_name||'Configuration required')+'
').join(''); const library=r.haccpLibrary.map(x=>'
'+esc(x.category)+' · '+esc(x.code)+'

'+esc(x.title)+'

'+esc(x.recommendation_text)+'
Authority: '+esc(x.authority_mode)+' · sources '+esc((x.source_refs||[]).length)+'
').join(''); const approved=r.haccpPlans.filter(x=>x.status==='approved').length,draft=r.haccpPlans.filter(x=>x.status!=='approved').length; const due=state.signals.filter(x=>x.signal_type==='haccp_plan_review_due').length,deviations=state.signals.filter(x=>x.signal_type==='haccp_monitoring_deviation').length; body='
'+metric('APPROVED',approved,'active plan versions')+metric('DRAFT / REVIEW',draft,'plans needing work')+metric('REVIEWS DUE',due,'Current signals')+metric('DEVIATIONS',deviations,'unresolved monitoring')+'
'+ '

HACCP plans + templates

Create reusable frameworks and location-specific, versioned plans.

'+ '
'+templates+'
'+ '

Location plans

Open a plan to edit process steps, monitoring, sources, and approval.

'+ '
PlanStatusTemplateVersionApproval
'+plans+'
'+ '

Guidance sources

Jurisdiction, edition, effective date and verification state travel with guidance.

'+ '
SourceStatusEditionEffectiveVerified by
'+sources+'
'+ '

Advisory library

Reusable suggestions remain advisory and require human review before acceptance.

'+ '
'+library+'
'; } } return '
REPORTING + COMPLIANCE · F1.1

Reports

Compose, freeze and share the complete evidence history without making the export the source of truth.

'+tabbar+body; } function normalizeComposerPositions(){ const c=state.reporting.composer;if(!c)return; (c.sections||[]).forEach((s,i)=>s.position=(i+1)*10); } async function openReportComposer(id){ try{ state.reporting.composer=await api('/v1/reports/'+id+'/composer'); state.reporting.selectedReport=id; state.reporting.tab='composer'; render('reports'); }catch(e){show('Could not open report: '+e.message)} } function moveReportSection(index,delta){ const c=state.reporting.composer;if(!c)return; const j=index+delta;if(j<0||j>=c.sections.length)return; const tmp=c.sections[index];c.sections[index]=c.sections[j];c.sections[j]=tmp;normalizeComposerPositions();render('reports'); } async function saveReportComposition(){ const c=state.reporting.composer;if(!c)return; try{ await api('/v1/reports/'+c.id+'/compose',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({title:c.title,mode:c.mode==='locked'?'live':c.mode,audience_policy:c.audience_policy||{},brand_profile_id:c.brand_profile_id||null,sections:(c.sections||[]).map(s=>({section_key:s.section_key,title:s.title,position:s.position,content:s.content||{}}))})}); show('Report composition saved.');await openReportComposer(c.id);await refresh();state.reporting.tab='composer';state.reporting.composer=await api('/v1/reports/'+c.id+'/composer');render('reports'); }catch(e){show('Could not save composition: '+e.message)} } async function lockReportPreview(){ const c=state.reporting.composer;if(!c)return; if(!confirm('Lock this report version? This version becomes the shareable record.'))return; try{ await api('/v1/reports/'+c.id+'/lock',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({locked_by_name:'Tevye Harper'})}); show('Locked report version created.');await refresh();state.reporting.composer=await api('/v1/reports/'+c.id+'/composer');state.reporting.tab='composer';render('reports'); }catch(e){show('Lock failed: '+e.message)} } async function publishReportPreview(){ const c=state.reporting.composer;if(!c)return; try{ await api('/v1/reports/'+c.id+'/publish',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({published_by_name:'Tevye Harper'})}); show('Report published.');await refresh();state.reporting.composer=await api('/v1/reports/'+c.id+'/composer');state.reporting.tab='composer';render('reports'); }catch(e){show('Publish failed: '+e.message)} } async function shareReportPreview(){ const c=state.reporting.composer;if(!c)return; const audience=prompt('Share label / recipient','Client reviewer');if(audience===null)return; const days=prompt('Expire after how many days? Leave blank for no expiry.','14'); let expires=null;if(days&&Number(days)>0)expires=new Date(Date.now()+Number(days)*86400000).toISOString(); try{ const result=await api('/v1/reports/'+c.id+'/shares',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({audience_label:audience,expires_at:expires,created_by_name:'Tevye Harper'})}); const url=location.origin+'/shared/'+result.share.token; try{await navigator.clipboard.writeText(url);show('Share link copied: '+url)}catch{show('Share link: '+url)} state.reporting.composer=await api('/v1/reports/'+c.id+'/composer');render('reports'); }catch(e){show('Share failed: '+e.message)} } async function acknowledgeReportPreview(){ const c=state.reporting.composer;if(!c)return; const signer=prompt('Reviewer / signer name','Tevye Harper');if(!signer)return; const role=prompt('Role','Owner / Reviewer'); try{ await api('/v1/reports/'+c.id+'/acknowledgments',{method:'POST',body:JSON.stringify({signer_name:signer,signer_role:role,acknowledgment_type:'reviewed'})}); show('Acknowledgment recorded.');state.reporting.composer=await api('/v1/reports/'+c.id+'/composer');render('reports'); }catch(e){show('Acknowledgment failed: '+e.message)} } async function revokeReportSharePreview(id){ try{ await api('/v1/report-shares/'+id+'/revoke',{method:'POST',body:'{}'}); show('Share revoked.');const c=state.reporting.composer;state.reporting.composer=await api('/v1/reports/'+c.id+'/composer');render('reports'); }catch(e){show('Revoke failed: '+e.message)} } async function createReportBrandPreview(){ const name=prompt('Brand profile name','Client Report Brand');if(!name)return; const company=prompt('Company / report issuer','Proof Demo Inspections')||''; const accent=prompt('Accent hex','#2F5B4A')||'#2F5B4A'; try{ await api('/v1/report-branding',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({name,code:'BRAND-'+Date.now().toString().slice(-6),company_name:company,accent_hex:accent,footer_text:'Prepared with Proof.'})}); show('Report brand created.');await refresh();state.reporting.tab='templates';render('reports'); }catch(e){show('Brand failed: '+e.message)} } async function createSchedulePreview(){ const title=prompt('Event title','Site inspection / follow-up');if(!title)return; const assignee=prompt('Assignee name','Jordan Lee');if(assignee===null)return; const start=prompt('Start (ISO or local date/time)','2026-10-03T10:00:00');if(!start)return; const end=prompt('End (ISO or local date/time)','2026-10-03T11:30:00');if(!end)return; const location=state.locations[0]; try{ await api('/v1/schedule/events',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({title,event_type:'appointment',source_type:'manual',starts_at:start,ends_at:end,assignee_type:'inspector',assignee_name:assignee,location_id:location?.id||null})}); show('Schedule event created.');await refresh();render('schedule'); }catch(e){show(e.body?.error==='schedule_conflict'?'Schedule conflict detected.':'Could not create event: '+e.message)} } async function createWorkOrderPreview(){ const title=prompt('Work order title','Correct equipment issue');if(!title)return; const vendor=state.ops.vendors[0],location=state.locations[0],client=state.business.clients[0],action=state.actions[0],engagement=state.business.engagements[0]; const due=new Date(Date.now()+24*60*60*1000).toISOString(); try{ await api('/v1/work-orders',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({title,category:'service',priority:'normal',actor_name:'Tevye Harper',client_id:client?.id||null,location_id:location?.id||null,engagement_id:engagement?.id||null,corrective_action_id:action?.id||null,partner_id:vendor?.id||null,assigned_to_name:vendor?.name||null,due_at:due})}); show('Work order created.');await refresh();state.business.tab='workorders';render('business'); }catch(e){show('Could not create work order: '+e.message)} } async function generateInspectionReportPreview(){ if(!state.run){show('No active inspection run is available.');return} try{ await api('/v1/reports/generate',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({report_type:'inspection',inspection_run_id:state.run.id})}); show('Inspection report generated.');await refresh();state.reporting.tab='reports';render('reports'); }catch(e){show('Report generation failed: '+e.message)} } async function generateLocationHistoryPreview(locationId){ try{ await api('/v1/reports/generate',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({report_type:'location_history',location_id:locationId})}); show('Location history report generated.');await refresh();state.reporting.tab='reports';render('reports'); }catch(e){show('History report failed: '+e.message)} } async function downloadReportExport(reportId,format){ try{ const x=await api('/v1/reports/'+reportId+'/export?format='+encodeURIComponent(format)); const blob=new Blob([x.content],{type:x.content_type||'text/plain'}); const url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=x.filename||('proof-report.'+format);document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1000); }catch(e){show('Export failed: '+e.message)} } async function createReportTemplatePreview(){ const name=prompt('Report template name','Complete Inspection Report');if(!name)return; const code=prompt('Template code','RPT-'+Date.now().toString().slice(-6));if(!code)return; const type=prompt('Report type: inspection or location_history','inspection')||'inspection'; try{ await api('/v1/report-templates',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({name,code,report_type:type,audience_mode:'client',configuration:{sections:['summary','inspection_scope','checks','readings','findings','photos','corrective_actions','recommendations','history'],history_mode:'complete'}})}); show('Report template created.');await refresh();state.reporting.tab='templates';render('reports'); }catch(e){show('Template failed: '+e.message)} } async function createRecommendationPreview(){ const category=prompt('Suggestion category: menu, procedure, training, equipment, sanitation, supplier','procedure')||'procedure'; const title=prompt('Suggestion title',category==='menu'?'Review menu/process item':'Update operating procedure');if(!title)return; const text=prompt('Suggested fix','Describe the operational change, why it may help, and how the team should verify the result.');if(!text)return; const action=state.actions[0],location=state.locations.find(l=>l.id===action?.location_id)||state.locations[0],client=state.business.clients[0]; try{ await api('/v1/recommendations',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({title,recommendation_text:text,category,source_type:'human',source_name:'Tevye Harper',client_id:client?.id||null,location_id:location?.id||null,corrective_action_id:action?.id||null,finding_id:action?.finding_id||null})}); show('Advisory suggestion created.');await refresh();state.reporting.tab='recommendations';render('reports'); }catch(e){show('Suggestion failed: '+e.message)} } async function createHaccpTemplatePreview(){ const name=prompt('HACCP template name','Restaurant HACCP Plan');if(!name)return; const code=prompt('Template code','HACCP-'+Date.now().toString().slice(-6));if(!code)return; try{ await api('/v1/haccp/templates',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({name,code,description:'Configurable HACCP framework. Populate hazards, limits, monitoring and corrective actions from approved authoritative requirements.'})}); show('HACCP template created.');await refresh();state.reporting.tab='haccp';render('reports'); }catch(e){show('Template failed: '+e.message)} } async function addHaccpStepPreview(templateId){ const name=prompt('Process step name','Receiving / storage / preparation step');if(!name)return; const code=prompt('Step code','STEP-'+Date.now().toString().slice(-5));if(!code)return; try{ await api('/v1/haccp/templates/'+templateId+'/steps',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({name,code,monitoring:{procedure:'Configure from approved plan.'},corrective_actions:{procedure:'Configure from approved plan.'},verification:{procedure:'Configure from approved plan.'}})}); show('HACCP template step added.');await refresh();state.reporting.tab='haccp';render('reports'); }catch(e){show('Could not add step: '+e.message)} } async function createHaccpPlanPreview(templateId){ const location=state.locations[0],client=state.business.clients[0];if(!location){show('Create a location first.');return} const name=prompt('Plan name',location.name+' HACCP Plan');if(!name)return; try{ await api('/v1/haccp/templates/'+templateId+'/plans',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({name,location_id:location.id,client_id:client?.id||null})}); show('Draft HACCP plan created.');await refresh();state.reporting.tab='haccp';render('reports'); }catch(e){show('Could not create plan: '+e.message)} } async function openHaccpPlan(id){ try{ state.reporting.haccpPlan=await api('/v1/haccp/plans/'+id); state.reporting.tab='haccp'; render('reports'); }catch(e){show('Could not open HACCP plan: '+e.message)} } async function editHaccpPlanPreview(){ const p=state.reporting.haccpPlan;if(!p)return; const name=prompt('Plan name',p.name);if(!name)return; const effective=prompt('Effective date (YYYY-MM-DD)',p.effective_on||''); const review=prompt('Review due (YYYY-MM-DD)',p.review_due_on||''); try{ await api('/v1/haccp/plans/'+p.id,{method:'POST',body:JSON.stringify({name,effective_on:effective||null,review_due_on:review||null,plan_scope:p.plan_scope||{}})}); show('Plan settings saved.');await openHaccpPlan(p.id);await refresh();state.reporting.haccpPlan=await api('/v1/haccp/plans/'+p.id);state.reporting.tab='haccp';render('reports'); }catch(e){show('Plan update failed: '+e.message)} } async function addHaccpPlanStepPreview(){ const p=state.reporting.haccpPlan;if(!p)return; const name=prompt('Process step name','New process step');if(!name)return; const code=prompt('Step code','STEP-'+String((p.steps||[]).length+1).padStart(2,'0'));if(!code)return; try{ await api('/v1/haccp/plans/'+p.id+'/steps',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({name,code,source_refs:[]})}); show('Process step added.');state.reporting.haccpPlan=await api('/v1/haccp/plans/'+p.id);render('reports'); }catch(e){show('Could not add step: '+e.message)} } function parseJsonPrompt(label,current){ const raw=prompt(label,JSON.stringify(current||{}));if(raw===null)return null; try{return JSON.parse(raw||'{}')}catch{show('That field must be valid JSON.');return undefined} } async function editHaccpPlanStepPreview(stepId){ const p=state.reporting.haccpPlan,s=(p?.steps||[]).find(x=>x.id===stepId);if(!p||!s)return; const name=prompt('Step name',s.name);if(!name)return; const hazards=(prompt('Hazards — comma separated',(s.hazards||[]).join(', '))||'').split(',').map(x=>x.trim()).filter(Boolean); const controls=(prompt('Preventive controls — comma separated',(s.preventive_controls||[]).join(', '))||'').split(',').map(x=>x.trim()).filter(Boolean); const isCcp=confirm('Is this step a CCP? Click OK for yes, Cancel for no.'); const limits=parseJsonPrompt('Critical limits JSON. Enter only approved/configured limits; Proof will not invent them.',s.critical_limits||{}); if(limits===undefined)return; const monitoring=prompt('Monitoring procedure',s.monitoring_procedure||'')??s.monitoring_procedure; const frequency=prompt('Monitoring frequency / cadence',s.monitoring_frequency||'')??s.monitoring_frequency; const corrective=prompt('Corrective action procedure',s.corrective_action_procedure||'')??s.corrective_action_procedure; const verification=prompt('Verification procedure',s.verification_procedure||'')??s.verification_procedure; const sourceOptions=state.reporting.haccpSources.map((x,i)=>(i+1)+'. '+x.title+' · '+x.verification_status).join('\n'); const sourcePick=prompt('Attach source number (blank keeps current):\n'+sourceOptions,''); let refs=s.source_refs||[]; if(sourcePick){const src=state.reporting.haccpSources[Number(sourcePick)-1];if(src)refs=[{source_id:src.id,label:src.title,verification_status:src.verification_status}]} try{ await api('/v1/haccp/plans/'+p.id+'/steps/'+s.id,{method:'POST',body:JSON.stringify({name,hazards,preventive_controls:controls,is_ccp:isCcp,critical_limits:limits||{},monitoring_procedure:monitoring||null,monitoring_frequency:frequency||null,corrective_action_procedure:corrective||null,verification_procedure:verification||null,source_refs:refs})}); show('HACCP step saved.');state.reporting.haccpPlan=await api('/v1/haccp/plans/'+p.id);render('reports'); }catch(e){show('Step update failed: '+e.message)} } async function moveHaccpPlanStep(stepId,delta){ const p=state.reporting.haccpPlan;if(!p)return; const order=(p.steps||[]).map(x=>x.id),i=order.indexOf(stepId),j=i+delta;if(i<0||j<0||j>=order.length)return; [order[i],order[j]]=[order[j],order[i]]; try{ await api('/v1/haccp/plans/'+p.id+'/reorder',{method:'POST',body:JSON.stringify({step_ids:order})}); state.reporting.haccpPlan=await api('/v1/haccp/plans/'+p.id);render('reports'); }catch(e){show('Could not reorder plan: '+e.message)} } async function approveHaccpPlanPreview(){ const p=state.reporting.haccpPlan;if(!p)return; if(!confirm('Approve this HACCP plan version? Approved plans require a revision before further editing.'))return; try{ await api('/v1/haccp/plans/'+p.id+'/approve',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({approved_by_name:'Tevye Harper'})}); show('HACCP plan approved and version hash recorded.');await refresh();state.reporting.haccpPlan=await api('/v1/haccp/plans/'+p.id);state.reporting.tab='haccp';render('reports'); }catch(e){ if(e.body?.error==='ccp_steps_require_configured_controls_before_approval')show('Approval blocked: every CCP needs configured critical limits, monitoring, corrective action, and verification procedures.'); else show('Approval failed: '+e.message) } } async function reviseHaccpPlanPreview(){ const p=state.reporting.haccpPlan;if(!p)return; const name=prompt('Revision name',p.name);if(!name)return; try{ const x=await api('/v1/haccp/plans/'+p.id+'/revision',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({name})}); show('Draft revision created.');await refresh();state.reporting.haccpPlan=await api('/v1/haccp/plans/'+x.plan.id);state.reporting.tab='haccp';render('reports'); }catch(e){show('Revision failed: '+e.message)} } async function linkHaccpInspectionPreview(){ const p=state.reporting.haccpPlan;if(!p||!state.run){show('No active inspection run is available.');return} try{ await api('/v1/haccp/plans/'+p.id+'/inspections',{method:'POST',body:JSON.stringify({inspection_run_id:state.run.id,relationship:'governing'})}); show('Current inspection linked to HACCP plan.');state.reporting.haccpPlan=await api('/v1/haccp/plans/'+p.id);render('reports'); }catch(e){show('Inspection link failed: '+e.message)} } async function recordHaccpMonitoringPreview(stepId){ const p=state.reporting.haccpPlan,s=(p?.steps||[]).find(x=>x.id===stepId);if(!p||!s)return; const raw=prompt('Observed value / note','');if(raw===null)return; const deviation=confirm('Does the responsible human reviewer consider this a deviation from the approved plan? OK = yes, Cancel = no.'); const note=prompt('Monitoring note','')||null; try{ await api('/v1/haccp/plans/'+p.id+'/steps/'+s.id+'/monitoring',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({inspection_run_id:state.run?.id||null,observed_value:{value:raw},deviation_detected:deviation,captured_by_name:'Tevye Harper',notes:note})}); show(deviation?'Monitoring recorded and flagged for review.':'Monitoring record saved.'); state.reporting.haccpPlan=await api('/v1/haccp/plans/'+p.id);await refresh();state.reporting.haccpPlan=await api('/v1/haccp/plans/'+p.id);state.reporting.tab='haccp';render('reports'); }catch(e){show('Monitoring failed: '+e.message)} } async function createGuidanceSourcePreview(){ const title=prompt('Source title','Operator Approved HACCP Plan / SOP');if(!title)return; const type=prompt('Source type: internal_policy, regulation, code, client_standard','internal_policy')||'internal_policy'; const jurisdiction=prompt('Jurisdiction / scope (optional)','')||null; const edition=prompt('Edition / version (optional)','')||null; const effective=prompt('Effective date (optional, YYYY-MM-DD)','')||null; const url=prompt('Source URL (optional)','')||null; try{ await api('/v1/haccp/guidance-sources',{method:'POST',body:JSON.stringify({title,source_type:type,jurisdiction,edition,effective_on:effective,source_url:url,verification_status:'configuration_required',notes:'Verify this source before it is used for authoritative guidance.'})}); show('Guidance source added with verification required.');await refresh();state.reporting.tab='haccp';render('reports'); }catch(e){show('Could not add source: '+e.message)} } async function draftGuidanceRecommendationPreview(libraryItemId){ const location=state.locations[0],action=state.actions[0],client=state.business.clients[0]; try{ await api('/v1/haccp/recommendation-library/'+libraryItemId+'/recommendations',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({client_id:client?.id||null,location_id:location?.id||null,finding_id:action?.finding_id||null,corrective_action_id:action?.id||null,source_type:'avery_draft',source_name:'Avery'})}); show('Avery draft created. Human review is required before it becomes a suggested fix.');await refresh();state.reporting.tab='recommendations';render('reports'); }catch(e){show('Draft failed: '+e.message)} } async function reviewGuidanceRecommendationPreview(id,status){ try{ await api('/v1/haccp/recommendations/'+id+'/review',{method:'POST',body:JSON.stringify({status,reviewed_by_name:'Tevye Harper'})}); show('Recommendation '+status+'.');await refresh();state.reporting.tab='recommendations';render('reports'); }catch(e){show('Review failed: '+e.message)} } async function transitionWorkOrderPreview(id,transition){ const w=state.ops.workOrders.find(x=>x.id===id);if(!w)return; let payload={actor_name:'Tevye Harper'}; if(transition==='complete'){ const cost=prompt('Actual vendor/service cost',String(w.actual_cost||w.cost_estimate||0));if(cost===null)return; payload.actual_cost=Number(cost)||0; payload.note=prompt('Completion note','Work completed; verification required.')||'Work completed; verification required.'; }else if(transition==='verify'){ if(!confirm('Verify this work order and close the linked corrective action where applicable?'))return; payload.note='Work verified by Proof operator.'; }else{ payload.note='Work order '+transition+' recorded.'; } try{ await api('/v1/work-orders/'+id+'/'+transition,{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify(payload)}); show('Work order '+transition+' recorded.');await refresh();state.business.tab='workorders';render('business'); }catch(e){show('Work order update failed: '+e.message)} } async function createVendorSourcingPreview(){ const title=prompt('Vendor need','Source refrigeration repair provider');if(!title)return; const capability=prompt('Capability code','REFRIGERATION-REPAIR')||''; const location=state.locations[0],action=state.actions[0]; try{ await api('/v1/vendor-sourcing-requests',{method:'POST',body:JSON.stringify({title,capability_code:capability,location_id:location?.id||null,corrective_action_id:action?.id||null,description:'Prepared for Marketplace sourcing handoff.'})}); show('Vendor sourcing request created. Marketplace handoff is ready for the external sourcing adapter.'); }catch(e){show('Vendor sourcing failed: '+e.message)} } function documentStatusChip(v){ const x=String(v||'active'); const cls=x==='expired'?'blocked':x==='expiring'?'review':x==='archived'?'todo':'done'; return ''+esc(x.replaceAll('_',' '))+''; } function locations(){ if(state.locationDetail)return locationVault(); const rows=state.locations.map(l=>'
'+mainCell(l.open_critical_findings?'!':'✓',l.name,(l.city||'')+(l.region?' · '+l.region:''),l.open_critical_findings?'red':'')+''+statusChip(l.open_critical_findings?'critical':l.open_actions?'review':'done')+''+esc(l.open_critical_findings)+''+esc(l.open_actions)+''+esc(l.code)+'
').join(''); return '
LOCATIONS

Locations

'+esc(state.locations.length)+' locations · open a location for permits, certificates and compliance documents.

'+ '
LocationStatusCriticalOpenCode
'+rows+'
'; } function locationVault(){ const d=state.locationDetail||{},l=d.location||{},docs=d.documents||[]; const active=docs.filter(x=>x.display_status==='active').length,expiring=docs.filter(x=>x.display_status==='expiring').length,expired=docs.filter(x=>x.display_status==='expired').length; const cards=docs.map(x=>'
'+esc((x.document_type||'document').replaceAll('_',' '))+'

'+esc(x.name)+'

'+esc(x.issuer||'No issuer')+(x.document_number?' · #'+esc(x.document_number):'')+'

'+(x.expires_on?'Expires '+esc(x.expires_on):'No expiration date')+' · '+esc(x.file_name||'file')+'

'+documentStatusChip(x.display_status)+'
'+(x.status!=='archived'?'':'')+'
').join(''); return '
LOCATION · '+esc(l.code||'')+'

'+esc(l.name||'Location documents')+'

'+esc(l.city||'')+(l.region?' · '+esc(l.region):'')+' · permits, licenses, certificates and supporting records.

'+ '
'+docs.length+'Location vault
'+active+'Current records
'+expiring+'Within 45 days
'+expired+'Needs attention
'+ '

Compliance documents

Private files inherit Proof tenant isolation and storage controls.

'+ (cards?'
'+cards+'
':'

No documents yet

Add the location permit, license, insurance certificate or other compliance record.

')+ '

Add document

PDF, image, Word or Excel · up to 12 MB.

'+ '
'; } async function openLocationVault(id){ try{ state.locationDetail=await api('/v1/locations/'+id+'/documents'); render('locations'); }catch(e){show('Could not load location documents: '+e.message)} } async function uploadLocationDocument(){ const input=document.getElementById('vaultFile'),file=input?.files?.[0],locationId=state.locationDetail?.location?.id; if(!file||!locationId){show('Choose a document first.');return} const name=String(document.getElementById('vaultName')?.value||file.name).trim(); if(!name){show('Document name is required.');return} if(file.size>12*1024*1024){show('Document is larger than 12 MB.');return} try{ const dataUrl=await new Promise((resolve,reject)=>{const fr=new FileReader();fr.onload=()=>resolve(fr.result);fr.onerror=reject;fr.readAsDataURL(file)}); const result=await api('/v1/locations/'+locationId+'/documents',{method:'POST',headers:{'Idempotency-Key':crypto.randomUUID()},body:JSON.stringify({ file_name:file.name,document_type:document.getElementById('vaultType').value,name, document_number:document.getElementById('vaultNumber').value||null, issuer:document.getElementById('vaultIssuer').value||null, issued_on:document.getElementById('vaultIssued').value||null, expires_on:document.getElementById('vaultExpires').value||null, data_url:dataUrl,created_by_name:'Tevye Harper' })}); show('Document added to '+state.locationDetail.location.name+'.'); state.locationDetail=await api('/v1/locations/'+locationId+'/documents'); render('locations'); }catch(e){show('Document upload failed: '+e.message)} } async function archiveLocationDocument(id){ try{ await api('/v1/location-documents/'+id,{method:'POST',body:JSON.stringify({status:'archived'})}); state.locationDetail=await api('/v1/locations/'+state.locationDetail.location.id+'/documents'); show('Document archived.'); render('locations'); }catch(e){show('Could not archive document: '+e.message)} } function inspections(){ const rows=state.inspections.map(i=>'
'+mainCell(i.run_id?'✓':'○',i.template_name,(i.completed_checks??0)+' / '+(i.total_checks??'—')+' checks')+''+statusChip(i.run_status||i.status)+''+esc(i.location_code)+''+esc(fmtTime(i.scheduled_start))+''+esc(i.assigned_inspector_name)+'
').join(''); const library=state.templateLibrary.map(t=>'
'+esc((t.category||'template').replaceAll('_',' '))+'

'+esc(t.name)+'

'+esc(t.description||'Ready-made inspection starting point.')+'

'+(t.tags||[]).slice(0,4).map(x=>''+esc(x)+'').join('')+'
'+esc(t.section_count)+' sections · '+esc(t.check_count)+' checks · operational starting point
').join(''); const installed=state.inspectionTemplates.map(t=>'
'+mainCell('✓',t.name,t.description||((t.section_count||0)+' sections · '+(t.check_count||0)+' checks'))+''+statusChip(t.status)+''+esc(t.category||'custom')+''+esc(t.check_count||0)+' checks
').join(''); return '
INSPECTIONS

Inspections

'+esc(state.inspections.length)+' scheduled inspections · '+esc(state.inspectionTemplates.length)+' reusable templates.

'+(state.run?'':'')+'
'+ '
InspectionStatusLocationTimeInspector
'+rows+'
'+ '

Template library

Install a starting point, then customize it to your client program and jurisdiction.

'+ '
'+library+ '
IMPORT

Bring your own inspection

Import a Proof inspection-template JSON file. Proof validates sections, question types, scoring and conditional rules before creating anything.

'+ '

Your templates

Tenant-owned templates installed from Proof or imported from your existing program.

'+ '
TemplateStatusCategoryChecksPortable copy
'+installed+'
'; } async function installInspectionTemplate(key){ try{ const r=await api('/v1/inspection-template-library/'+encodeURIComponent(key)+'/install',{method:'POST',body:'{}'}); show('Template added · '+(r.stats?.sections||0)+' sections, '+(r.stats?.checks||0)+' checks.'); const mine=await api('/v1/inspection-templates');state.inspectionTemplates=mine.data||[];render('inspections'); }catch(e){ if(e.body?.error==='template_already_installed')show('That Proof template is already installed.'); else show('Could not add template: '+e.message); } } async function importTemplateJson(){ const input=document.getElementById('templateImportFile'),file=input?.files?.[0]; if(!file){show('Choose a Proof template JSON file first.');return} if(file.size>1024*1024){show('Template JSON must be 1 MB or smaller.');return} try{ const parsed=JSON.parse(await file.text()); const candidate=parsed.template?parsed.template:parsed; const validation=await api('/v1/inspection-templates/import/validate',{method:'POST',body:JSON.stringify({template:candidate})}); if(!validation.ok){show('Template needs changes: '+(validation.errors?.[0]||'invalid template'));return} const msg='Import “'+validation.preview.name+'” with '+validation.stats.sections+' sections and '+validation.stats.checks+' checks?'; if(!window.confirm(msg))return; const result=await api('/v1/inspection-templates/import',{method:'POST',body:JSON.stringify({template:candidate,source_name:file.name})}); show('Template imported · '+result.stats.checks+' checks.'); const mine=await api('/v1/inspection-templates');state.inspectionTemplates=mine.data||[];render('inspections'); }catch(e){show('Template import failed: '+(e.message||'Invalid JSON'))} } async function exportInspectionTemplate(id){ try{ const exported=await api('/v1/inspection-templates/'+id+'/export'); const blob=new Blob([JSON.stringify(exported,null,2)],{type:'application/json'}); const a=document.createElement('a');a.href=URL.createObjectURL(blob); a.download=(exported.template?.name||'proof-template').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'')+'.proof-template.json'; document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(a.href),1000); show('Portable template exported.'); }catch(e){show('Could not export template: '+e.message)} } function actions(){ const rows=state.actions.map((a,i)=>'
'+mainCell(a.severity==='critical'?'!':'↻',a.title,a.summary,a.severity==='critical'?'red':'amber')+''+statusChip(a.status)+''+esc(a.location_code)+''+esc(fmtTime(a.due_at))+''+esc(a.owner_name||'—')+'
').join(''); return '
CORRECTIVE ACTIONS

Corrective Actions

'+esc(state.actions.length)+' actions.

'+ '
ActionStatusLocationDueOwner
'+rows+'
'; } function run(){ const r=state.run; if(!r)return '
INSPECTION

No active run

Start a scheduled inspection to create a run.

'; const checks=(r.checks||[]).map(c=>{ const has=c.reading_id!=null; const label=has?(c.numeric_value+(c.unit?'°'+c.unit:'')):'+ Reading'; const bad=c.reading_status==='needs_review'; return '
'+esc(c.prompt)+''+esc(c.section_name)+(c.configured_limit?.max!=null?' · configured max '+c.configured_limit.max+'°'+(c.configured_limit.unit||'F'):'')+'
'; }).join(''); const done=r.completed_checks||0,total=r.total_checks||0,pct=total?Math.min(100,Math.round(done/total*100)):0; return '
INSPECTION RUN · '+esc(r.location_code)+'

'+esc(r.template_name)+'

'+esc(r.inspector_name)+' · In progress

'+statusChip(r.status)+'
'+ '

Cold holding

'+esc(done)+' of '+esc(total)+' checks complete · saved automatically
'+esc(done)+' / '+esc(total)+'
'+ '

Inspection checks

'+(r.checks?.some(x=>x.reading_status==='needs_review')?'Review required':'Active')+'
'+checks+'
'+ '
'; } async function addReading(checkId){ const check=(state.run?.checks||[]).find(x=>x.id===checkId); const existing=check?.numeric_value; const raw=window.prompt('Enter temperature in °F',existing??''); if(raw===null)return; const value=Number(raw); if(!Number.isFinite(value)){show('Enter a valid number.');return} try{ const body={check_id:checkId,numeric_value:value,unit:'F',equipment_id:check?.equipment_id||null}; const result=await api('/v1/inspection-runs/'+state.run.id+'/readings',{ method:'POST', headers:{'Idempotency-Key':crypto.randomUUID()}, body:JSON.stringify(body) }); show(result.review_required?'Reading saved · human review required.':'Reading saved.'); state.run=await api('/v1/inspection-runs/'+state.run.id); state.dashboard=await api('/v1/dashboard'); render('run'); }catch(e){show('Could not save reading: '+e.message)} } function render(v){ current=v||'day'; const titles={day:'My Day',locations:'Locations',inspections:'Inspections',actions:'Corrective Actions',schedule:'Schedule',business:'Business',reports:'Reports',run:'Routine Food Safety'}; crumb.textContent=titles[current]||'My Day'; if(state.loading)view.innerHTML=loadingView(); else if(state.error)view.innerHTML=errorView(); else view.innerHTML=current==='day'?day():current==='locations'?locations():current==='inspections'?inspections():current==='actions'?actions():current==='schedule'?scheduleView():current==='business'?business():current==='reports'?reportsView():run(); document.querySelectorAll('[data-view]').forEach(b=>{if(b.classList.contains('nav')||b.closest('.mobilebar'))b.classList.toggle('active',b.dataset.view===current)}); sidebar.classList.remove('open'); overlay.classList.remove('open'); bind(); } function bind(){ const retry=document.getElementById('retry');if(retry)retry.onclick=refresh; view.querySelectorAll('[data-view]').forEach(b=>b.addEventListener('click',()=>render(b.dataset.view))); view.querySelectorAll('[data-panel]').forEach(b=>b.addEventListener('click',openPanel)); view.querySelectorAll('[data-check]').forEach(b=>b.addEventListener('click',()=>addReading(b.dataset.check))); view.querySelectorAll('[data-install-template]').forEach(b=>b.addEventListener('click',()=>installInspectionTemplate(b.dataset.installTemplate))); view.querySelectorAll('[data-export-template]').forEach(b=>b.addEventListener('click',e=>{e.stopPropagation();exportInspectionTemplate(b.dataset.exportTemplate)})); const itj=document.getElementById('importTemplateJson');if(itj)itj.onclick=importTemplateJson; view.querySelectorAll('[data-location]').forEach(b=>b.addEventListener('click',()=>openLocationVault(b.dataset.location))); view.querySelectorAll('[data-open-document]').forEach(b=>b.addEventListener('click',e=>{e.stopPropagation();window.open(API+'/v1/file-objects/'+b.dataset.openDocument+'/content','_blank','noopener')})); view.querySelectorAll('[data-archive-document]').forEach(b=>b.addEventListener('click',e=>{e.stopPropagation();archiveLocationDocument(b.dataset.archiveDocument)})); const bl=document.getElementById('backLocations');if(bl)bl.onclick=()=>{state.locationDetail=null;render('locations')}; const uld=document.getElementById('uploadLocationDocument');if(uld)uld.onclick=uploadLocationDocument; const a=document.getElementById('askDay');if(a)a.onclick=askAveryDay; const r=document.getElementById('runAvery');if(r)r.onclick=()=>show('Avery can help with this inspection and its readings.'); const rr=document.getElementById('reviewRun');if(rr)rr.onclick=()=>show('Review this inspection for missing checks before completion.'); const rcp=document.getElementById('restoreCurrentPanels');if(rcp)rcp.onclick=()=>{state.current.hidden=[];state.current.order=[];state.current.pinned=[];saveCurrentLayout();render('day')}; const rcs=document.getElementById('refreshCurrentSurface');if(rcs)rcs.onclick=refreshCurrentSurface; view.querySelectorAll('[data-current-pin]').forEach(b=>b.addEventListener('click',e=>{e.stopPropagation();toggleCurrentList('pinned',b.dataset.currentPin)})); view.querySelectorAll('[data-current-hide]').forEach(b=>b.addEventListener('click',e=>{e.stopPropagation();toggleCurrentList('hidden',b.dataset.currentHide)})); view.querySelectorAll('[data-current-open]').forEach(b=>b.addEventListener('click',e=>{e.stopPropagation();openCurrentPanel(b.dataset.currentOpen)})); view.querySelectorAll('[data-current-avery]').forEach(b=>b.addEventListener('click',e=>{e.stopPropagation();askAverySignal(b.dataset.currentAvery)})); let draggedCurrent=null; view.querySelectorAll('[data-current-module]').forEach(card=>{ card.addEventListener('dragstart',()=>{draggedCurrent=card.dataset.currentModule;card.classList.add('dragging')}); card.addEventListener('dragend',()=>{card.classList.remove('dragging');view.querySelectorAll('.drop-target').forEach(x=>x.classList.remove('drop-target'));draggedCurrent=null}); card.addEventListener('dragover',e=>{e.preventDefault();card.classList.add('drop-target')}); card.addEventListener('dragleave',()=>card.classList.remove('drop-target')); card.addEventListener('drop',e=>{e.preventDefault();card.classList.remove('drop-target');moveCurrentModule(draggedCurrent,card.dataset.currentModule)}); }); view.querySelectorAll('[data-biztab]').forEach(b=>b.addEventListener('click',()=>{state.business.tab=b.dataset.biztab;render('business')})); view.querySelectorAll('[data-accttab]').forEach(b=>b.addEventListener('click',()=>{state.accounting.tab=b.dataset.accttab;render('business')})); const nar=document.getElementById('newReceivable');if(nar)nar.onclick=createReceivablePreview; view.querySelectorAll('[data-ar-post]').forEach(b=>b.addEventListener('click',()=>postReceivablePreview(b.dataset.arPost))); view.querySelectorAll('[data-ar-pay]').forEach(b=>b.addEventListener('click',()=>payReceivablePreview(b.dataset.arPay))); const ivb=document.getElementById('importVendorBill');if(ivb)ivb.onclick=importVendorBillPreview; view.querySelectorAll('[data-ap-approve]').forEach(b=>b.addEventListener('click',()=>approvePayablePreview(b.dataset.apApprove))); view.querySelectorAll('[data-ap-post]').forEach(b=>b.addEventListener('click',()=>postPayablePreview(b.dataset.apPost))); view.querySelectorAll('[data-ap-pay]').forEach(b=>b.addEventListener('click',()=>payPayablePreview(b.dataset.apPay))); view.querySelectorAll('[data-opp]').forEach(b=>b.addEventListener('click',()=>{state.business.selectedOpportunity=b.dataset.opp;render('business')})); view.querySelectorAll('[data-execute-contract]').forEach(b=>b.addEventListener('click',e=>{e.stopPropagation();executeContractPreview(b.dataset.executeContract)})); const nc=document.getElementById('newClient');if(nc)nc.onclick=createClientPreview; const no=document.getElementById('newOpp');if(no)no.onclick=createOpportunityPreview; const be=document.getElementById('buildEstimate');if(be)be.onclick=buildEstimatePreview; const pp=document.getElementById('publishProposal');if(pp)pp.onclick=publishProposalPreview; const ap=document.getElementById('acceptProposal');if(ap)ap.onclick=acceptProposalPreview; const co=document.getElementById('convertOpportunity');if(co)co.onclick=convertOpportunityPreview; const ba=document.getElementById('bizAvery');if(ba)ba.onclick=()=>show('Avery can summarize the business and suggest next steps.'); const wo=document.getElementById('newWorkOrder');if(wo)wo.onclick=createWorkOrderPreview; view.querySelectorAll('[data-wo-transition]').forEach(b=>b.addEventListener('click',()=>transitionWorkOrderPreview(b.dataset.woTransition,b.dataset.transition))); const sv=document.getElementById('sourceVendor');if(sv)sv.onclick=createVendorSourcingPreview; const se=document.getElementById('newScheduleEvent');if(se)se.onclick=createSchedulePreview; const ss=document.getElementById('newScheduleSeries');if(ss)ss.onclick=createScheduleSeriesPreview; const sa=document.getElementById('newAvailability');if(sa)sa.onclick=createAvailabilityPreview; view.querySelectorAll('[data-schedule-mode]').forEach(b=>b.addEventListener('click',()=>{state.ops.scheduleMode=b.dataset.scheduleMode;render('schedule')})); view.querySelectorAll('[data-reschedule-event]').forEach(b=>b.addEventListener('click',()=>rescheduleEventPreview(b.dataset.rescheduleEvent))); const srf=document.getElementById('scheduleResourceFilter');if(srf)srf.onchange=()=>{state.ops.resourceFilter=srf.value;render('schedule')}; view.querySelectorAll('[data-rpttab]').forEach(b=>b.addEventListener('click',()=>{state.reporting.tab=b.dataset.rpttab;render('reports')})); view.querySelectorAll('[data-open-report]').forEach(b=>b.addEventListener('click',e=>{if(e.target.closest('[data-export-report]'))return;openReportComposer(b.dataset.openReport)})); view.querySelectorAll('[data-section-up]').forEach(b=>b.addEventListener('click',()=>moveReportSection(Number(b.dataset.sectionUp),-1))); view.querySelectorAll('[data-section-down]').forEach(b=>b.addEventListener('click',()=>moveReportSection(Number(b.dataset.sectionDown),1))); view.querySelectorAll('[data-revoke-share]').forEach(b=>b.addEventListener('click',()=>revokeReportSharePreview(b.dataset.revokeShare))); const br=document.getElementById('backReports');if(br)br.onclick=()=>{state.reporting.tab='reports';render('reports')}; const rrn=document.getElementById('renameReport');if(rrn)rrn.onclick=()=>{const c=state.reporting.composer;const x=prompt('Report title',c.title);if(x){c.title=x;render('reports')}}; const trm=document.getElementById('toggleReportMode');if(trm)trm.onclick=()=>{const c=state.reporting.composer;c.mode=c.mode==='hybrid'?'live':'hybrid';render('reports')}; const crb=document.getElementById('changeReportBrand');if(crb)crb.onclick=()=>{const c=state.reporting.composer;const options=state.reporting.brands.map((x,i)=>(i+1)+'. '+x.name).join('\n');const pick=prompt('Choose brand by number:\n'+options,'1');if(pick){const x=state.reporting.brands[Number(pick)-1];if(x){c.brand_profile_id=x.id;c.brand_profile=x;render('reports')}}}; const ars=document.getElementById('addReportSection');if(ars)ars.onclick=()=>{const c=state.reporting.composer;const title=prompt('Section title','Executive Notes');if(!title)return;const key='custom_'+Date.now();c.sections.push({section_key:key,title,position:(c.sections.length+1)*10,content:{source:'custom',text:''}});render('reports')}; const src=document.getElementById('saveReportComposition');if(src)src.onclick=saveReportComposition; const lr=document.getElementById('lockReport');if(lr)lr.onclick=lockReportPreview; const pr=document.getElementById('publishReport');if(pr)pr.onclick=publishReportPreview; const shr=document.getElementById('shareReport');if(shr)shr.onclick=shareReportPreview; const ack=document.getElementById('ackReport');if(ack)ack.onclick=acknowledgeReportPreview; const gir=document.getElementById('generateInspectionReport');if(gir)gir.onclick=generateInspectionReportPreview; view.querySelectorAll('[data-generate-location]').forEach(b=>b.addEventListener('click',()=>generateLocationHistoryPreview(b.dataset.generateLocation))); view.querySelectorAll('[data-export-report]').forEach(b=>b.addEventListener('click',()=>downloadReportExport(b.dataset.exportReport,b.dataset.exportFormat))); const nrt=document.getElementById('newReportTemplate');if(nrt)nrt.onclick=createReportTemplatePreview; const nrb=document.getElementById('newReportBrand');if(nrb)nrb.onclick=createReportBrandPreview; const nr=document.getElementById('newRecommendation');if(nr)nr.onclick=createRecommendationPreview; const nht=document.getElementById('newHaccpTemplate');if(nht)nht.onclick=createHaccpTemplatePreview; view.querySelectorAll('[data-add-haccp-step]').forEach(b=>b.addEventListener('click',()=>addHaccpStepPreview(b.dataset.addHaccpStep))); view.querySelectorAll('[data-create-haccp-plan]').forEach(b=>b.addEventListener('click',()=>createHaccpPlanPreview(b.dataset.createHaccpPlan))); view.querySelectorAll('[data-open-haccp-plan]').forEach(b=>b.addEventListener('click',()=>openHaccpPlan(b.dataset.openHaccpPlan))); const bhp=document.getElementById('backHaccpPlans');if(bhp)bhp.onclick=()=>{state.reporting.haccpPlan=null;render('reports')}; const ehp=document.getElementById('editHaccpPlan');if(ehp)ehp.onclick=editHaccpPlanPreview; const ahs=document.getElementById('addHaccpPlanStep');if(ahs)ahs.onclick=addHaccpPlanStepPreview; view.querySelectorAll('[data-haccp-edit-step]').forEach(b=>b.addEventListener('click',()=>editHaccpPlanStepPreview(b.dataset.haccpEditStep))); view.querySelectorAll('[data-haccp-step-up]').forEach(b=>b.addEventListener('click',()=>moveHaccpPlanStep(b.dataset.haccpStepUp,-1))); view.querySelectorAll('[data-haccp-step-down]').forEach(b=>b.addEventListener('click',()=>moveHaccpPlanStep(b.dataset.haccpStepDown,1))); view.querySelectorAll('[data-haccp-monitor-step]').forEach(b=>b.addEventListener('click',()=>recordHaccpMonitoringPreview(b.dataset.haccpMonitorStep))); const apn=document.getElementById('approveHaccpPlan');if(apn)apn.onclick=approveHaccpPlanPreview; const rhp=document.getElementById('reviseHaccpPlan');if(rhp)rhp.onclick=reviseHaccpPlanPreview; const lhi=document.getElementById('linkHaccpInspection');if(lhi)lhi.onclick=linkHaccpInspectionPreview; const ngs=document.getElementById('newGuidanceSource');if(ngs)ngs.onclick=createGuidanceSourcePreview; view.querySelectorAll('[data-guidance-draft]').forEach(b=>b.addEventListener('click',()=>draftGuidanceRecommendationPreview(b.dataset.guidanceDraft))); view.querySelectorAll('[data-rec-review]').forEach(b=>b.addEventListener('click',()=>reviewGuidanceRecommendationPreview(b.dataset.recReview,b.dataset.recStatus))); const had=document.getElementById('haccpAveryDraft');if(had)had.onclick=()=>{const item=state.reporting.haccpLibrary[0];if(item)draftGuidanceRecommendationPreview(item.id);else show('No guidance library item is configured.')}; } function openPanel(){ const a=state.actions[0]; if(a){ const h=inspector.querySelector('.inspHead'); if(h){ const title=h.querySelector('h2'),p=h.querySelector('p'); if(title)title.textContent=a.summary||a.title; if(p)p.textContent=(a.location_name||a.location_code)+' · repeat count '+(a.repeat_count||0); } const blocks=inspector.querySelectorAll('.detail b'); if(blocks[2])blocks[2].textContent=a.owner_name||'Unassigned'; if(blocks[3])blocks[3].textContent=fmtTime(a.due_at); } inspector.classList.add('open');overlay.classList.add('open'); } function closePanel(){inspector.classList.remove('open');sidebar.classList.remove('open');overlay.classList.remove('open')} function sharedReportView(shared){ const r=shared.report||{},data=r.snapshot_data||{},brand=r.brand||{},sections=r.sections||[]; const sectionList=sections.map(s=>'
'+esc(s.section_key||'section')+'

'+esc(s.title||s.section_key||'Section')+'

Included in locked report composition.
').join(''); let summary=''; if(data.report_kind==='inspection'){ const i=data.inspection||{}; summary='
'+metric('LOCATION',i.location_name||'—','inspection scope')+metric('INSPECTOR',i.inspector_name||'—','performed by')+metric('FINDINGS',String((data.findings||[]).length),'recorded findings')+metric('ACTIONS',String((data.corrective_actions||[]).length),'corrective actions')+'
'+ '

Findings

Frozen from the locked report version.

'+ '
FindingSeverityStatusRepeat
'+ (data.findings||[]).map(x=>'
'+mainCell('!',x.summary,x.category||'finding',x.severity==='critical'?'red':'amber')+''+esc(x.severity)+''+statusChip(x.status)+''+esc(x.repeat_count||0)+'
').join('')+'
'; }else if(data.report_kind==='location_history'){ summary='
'+metric('LOCATION',data.location?.name||'—','history scope')+metric('INSPECTIONS',String((data.inspections||[]).length),'historical inspections')+metric('FINDINGS',String((data.findings||[]).length),'finding history')+metric('WORK ORDERS',String((data.work_orders||[]).length),'linked operational work')+'
'; } return '
'+ '
REPORT · V'+esc(r.version||0)+'

'+esc(r.title||'Shared report')+'

'+esc(brand.company_name||brand.name||'Proof')+'

'+statusChip('locked')+'
'+ summary+ '

Report composition

This is the shared report version.

'+ '
'+sectionList+'
'+ (brand.footer_text?'
'+esc(brand.footer_text)+'
':'')+ '
'; } async function loadSharedReport(token){ document.body.classList.add('shared'); sidebar.style.display='none'; const top=document.querySelector('.topbar');if(top)top.style.display='none'; const mobile=document.querySelector('.mobilebar');if(mobile)mobile.style.display='none'; inspector.style.display='none'; const sheet=document.querySelector('.sheet');if(sheet){sheet.style.margin='0';sheet.style.maxWidth='none';sheet.style.borderRadius='0'} try{ const shared=await api('/v1/public/reports/'+token); view.innerHTML=sharedReportView(shared); }catch(e){ view.innerHTML='

Report unavailable

This share may have expired or been revoked.

'; } } function show(t){toast.textContent=t;toast.classList.add('show');clearTimeout(window.t);window.t=setTimeout(()=>toast.classList.remove('show'),2200)} document.addEventListener('click',e=>{const v=e.target.closest('[data-view]');if(v&&!view.contains(v))render(v.dataset.view);const p=e.target.closest('[data-panel]');if(p&&!view.contains(p))openPanel()}); document.getElementById('menu').onclick=()=>{sidebar.classList.add('open');overlay.classList.add('open')}; document.getElementById('overlay').onclick=closePanel; document.getElementById('closeInspector').onclick=closePanel; document.getElementById('averyNav').onclick=()=>show('Avery is ready to help with your work.'); document.getElementById('averyTop').onclick=askAveryDay; document.getElementById('averyMobile').onclick=()=>show('Avery is ready to help in the field.'); document.getElementById('panelAvery').onclick=()=>show('Avery can summarize this item and suggest next steps.'); document.getElementById('panelAction').onclick=()=>{const selected=state.current?.selected;closePanel();if(selected?.module?.component_key==='proof.business-ops.signal')render('business');else if(selected?.module?.component_key==='proof.inspection.readiness')render('inspections');else render('actions')}; document.addEventListener('keydown',e=>{if(e.key==='Escape')closePanel()}); const sharedMatch=location.pathname.match(/^\/shared\/([A-Za-z0-9_-]+)$/); if(sharedMatch)loadSharedReport(sharedMatch[1]);else{render('day');refresh();}