TEST PREVIEW ONLY — jobs are stored in this browser. Cloud sync is not connected to the installed Supabase database. Do not enter real customer information.
MASKERADESite Survey • Pricing • Quotations
Local edition

Maskerade Design Ltd — Site Survey & Quotation

');w.document.close();w.focus();w.print();} const v72PriorRender=renderV5; function v72RenderApp(){v72PriorRender();if(tab==='Jobs'){const app=$('app');app.insertAdjacentHTML('beforeend','
');v72Render();}} // V7.4 safe unified navigation pilot: cloud survey/costing inside the main tabs. // Local records remain untouched; this is NOT offline or full data synchronisation. let v74CloudActive=null; function v74OpenCloud(id,mode){ const j=v72CloudJobs.find(x=>x.id===id); if(!j)return alert('Refresh cloud jobs and try again.'); if(!String(j.reference||'').startsWith('TEST-'))return alert('Only fictional TEST- jobs are supported.'); v74CloudActive=id; v73Editing={...j,survey_data:structuredClone(j.survey_data||{}),costing_data:structuredClone(j.costing_data||{})}; v73Mode=mode;tab=mode==='survey'?'Survey':'Costing';render(); } function v74BackToJobs(){v74CloudActive=null;v73Editing=null;tab='Jobs';render();} function v74SelectLocal(id,mode){v74CloudActive=null;v73Editing=null;active=id;tab=mode;render();} const v74BaseRender=v72RenderApp; function v74Render(){ if(v74CloudActive&&(tab==='Survey'||tab==='Costing')){ const nav=$('nav');nav.innerHTML=tabs.map(t=>``).join(''); v73Mode=tab==='Survey'?'survey':'costing'; const j=v72CloudJobs.find(x=>x.id===v74CloudActive); if(!j){v74BackToJobs();return;} $('app').innerHTML=`

V7.4 — Cloud ${esc(tab)} pilot

FICTIONAL TEST DATA ONLY. This form saves to Supabase. Photos, RAMS, fitters, issued quotations, offline changes and existing local jobs are NOT synchronised.

`+v73Form(); return; } v74BaseRender(); if(tab==='Jobs'){ const panel=$('v72CloudPanel'); if(panel){ // Keep the tested cloud management controls but show a single consolidated job directory. const localCount=db.jobs.length; const cards=v72CloudJobs.map(j=>`
${esc(j.reference||'No reference')} — ${esc(j.title||'Untitled')}
Cloud · ${esc(j.status)} · ${esc(j.site_address||'No site address')}
`).join(''); panel.insertAdjacentHTML('beforebegin',`

Cloud jobs in job directory (${v72CloudJobs.length})

${localCount} browser-local job(s) are listed above. Cloud records are listed below and remain separate until a tested migration is available.

${v72CloudJobs.length?'':'

Use Refresh cloud jobs below to load test records.

'}
${cards}`); // Avoid duplicate cloud cards in the diagnostic panel. const diagnostic=panel.querySelector('.card:last-child'); if(diagnostic&&diagnostic.querySelector('h3')?.textContent.startsWith('Saved cloud jobs'))diagnostic.remove(); } // Local job buttons explicitly switch out of cloud mode. document.querySelectorAll('#app button[onclick^="active="]').forEach(b=>{ const match=b.getAttribute('onclick').match(/active='([^']+)';tab='(Survey|Costing)'/); if(match)b.setAttribute('onclick',`v74SelectLocal('${match[1]}','${match[2]}')`); }); } } render=v74Render; render(); ');w.document.close();w.focus();w.print()}catch(e){alert('Print preview failed: '+e.message)}} // V8.4 scheduling pilot: stored only in existing cloud costing_data JSONB. // No production release, real staff dispatch, notifications or calendar integration. const v84Stages=['Awaiting artwork','Materials ordered','In production','Quality check','Ready for installation','Completed']; const v84PriorControls=v83Controls; v83Controls=function(c){return v84PriorControls(c)+v84Controls(c);}; function v84Controls(c){const s=c.v84_schedule||{},stage=v84Stages.includes(s.stage)?s.stage:v84Stages[0];return `

V8.4 — Production scheduling & progress (test only)

Fictional planning only. No production authorisation, staff assignment, purchase order, calendar booking or customer notification is created. Status is a manual test indicator.

Use Save cloud costing below to save this schedule. Changing the stage does not change quotation approval or production readiness.

`;} function v84Collect(){const val=id=>$(id)?.value?.trim()||'';const hours=val('v84Hours');if(hours!==''&&(!Number.isFinite(Number(hours))||Number(hours)<0||Number(hours)>10000))throw Error('Enter valid fitting hours.');const start=val('v84Start'),finish=val('v84Finish');if(start&&finish&&finish

V8.4 Test production schedule

SIMULATION ONLY — NOT AUTHORISED FOR PRODUCTION OR FITTING.

Job: ${esc(v73Editing.reference)} · Stage: ${esc(x.stage)} · Priority: ${esc(x.priority)}

Workshop: ${esc(x.start||'Not scheduled')} to ${esc(x.finish||'Not scheduled')}
Fitting: ${esc(x.fit_date||'Not scheduled')} · ${esc(x.hours||'0')} estimated hours

Workshop team: ${esc(x.staff||'Unassigned')}
Fitters: ${esc(x.fitters||'Unassigned')}
Assistants: ${esc(x.assistants||'Unassigned')}
Equipment: ${esc(x.equipment||'Not specified')}

Notes: ${esc(x.notes||'None')}

`}catch(err){e.textContent='Preview failed: '+err.message}} const v84PriorRender=render; render=function(){v84PriorRender();if(tab==='Jobs'&&$('app')){const rows=v72CloudJobs.filter(j=>String(j.reference||'').startsWith('TEST-')).map(j=>{const s=j.costing_data?.v84_schedule||{};return `${esc(j.reference||'')}${esc(s.stage||'Not scheduled')}${esc(s.priority||'—')}${esc(s.start||'—')}${esc(s.finish||'—')}${esc(s.fit_date||'—')}`}).join('');$('app').insertAdjacentHTML('beforeend',`

V8.4 — Cloud production schedule (test only)

Fictional TEST- jobs only. Refresh cloud jobs to update this view. Stages are manually entered and do not authorise production.

${rows||''}
Test jobStagePriorityStartFinishFitting
No loaded cloud test jobs. Sign in and refresh cloud jobs.
`);}} // V8.5 calendar and workload pilot. Read-only view of existing v84_schedule records. let v85View='month',v85Offset=0,v85Priority='All',v85Stage='All'; function v85Date(d){return d?new Date(d+'T12:00:00'):null} function v85ISO(d){return [d.getFullYear(),String(d.getMonth()+1).padStart(2,'0'),String(d.getDate()).padStart(2,'0')].join('-')} function v85Add(d,n){const x=new Date(d);x.setDate(x.getDate()+n);return x} function v85Names(s){return String(s||'').split(/[,;\n]+/).map(x=>x.trim().toLowerCase()).filter(Boolean)} function v85Jobs(){return v72CloudJobs.filter(j=>String(j.reference||'').startsWith('TEST-')).map(j=>({j,s:j.costing_data?.v84_schedule||{}})).filter(({s})=>(v85Priority==='All'||s.priority===v85Priority)&&(v85Stage==='All'||s.stage===v85Stage))} function v85Dates(s){let a=[];if(s.start){let st=v85Date(s.start),end=v85Date(s.finish||s.start);if(st&&end&&end>=st){for(let d=st,n=0;d<=end&&n<92;d=v85Add(d,1),n++)a.push({date:v85ISO(d),kind:'Workshop'})}}if(s.fit_date)a.push({date:s.fit_date,kind:'Fitting'});return a} function v85Events(){return v85Jobs().flatMap(({j,s})=>v85Dates(s).map(e=>({...e,j,s}))) } function v85Conflicts(events){let warnings=[];for(let i=0;ibb.includes(x));if(overlap.length)warnings.push(`${a.date}: ${overlap.join(', ')} appears on ${a.j.reference} and ${b.j.reference}`);else if(a.kind==='Fitting'&&b.kind==='Fitting')warnings.push(`${a.date}: two fitting jobs are scheduled on the same date (${a.j.reference} and ${b.j.reference}); check staff and equipment availability`)}return [...new Set(warnings)]} function v85SetView(x){v85View=x;v85Offset=0;v85Paint()} function v85Move(n){v85Offset+=n;v85Paint()} function v85SetFilter(k,v){if(k==='priority')v85Priority=v;else v85Stage=v;v85Paint()} function v85Paint(){const host=$('v85Calendar');if(!host)return;const today=new Date();today.setHours(12,0,0,0);const base=v85View==='month'?new Date(today.getFullYear(),today.getMonth()+v85Offset,1,12):v85Add(today,v85Offset*7);const start=v85View==='month'?v85Add(base,-(base.getDay()+6)%7):v85Add(base,-(base.getDay()+6)%7);const days=v85View==='month'?42:7;const last=v85ISO(v85Add(start,days-1));const events=v85Events().filter(e=>e.date>=v85ISO(start)&&e.date<=last);const byDay={};events.forEach(e=>(byDay[e.date]??=[]).push(e));const conflicts=v85Conflicts(events);const head=v85View==='month'?base.toLocaleDateString('en-GB',{month:'long',year:'numeric'}):`${v85ISO(start)} to ${v85ISO(v85Add(start,6))}`;const tiles=Array.from({length:days},(_,i)=>{const d=v85Add(start,i),date=v85ISO(d),es=byDay[date]||[];return `
${d.getDate()}
${es.slice(0,5).map(e=>``).join('')}${es.length>5?`+${es.length-5} more`:''}
`}).join('');const workload={};events.forEach(e=>{const names=v85Names(e.kind==='Workshop'?e.s.staff:[e.s.fitters,e.s.assistants].filter(Boolean).join(','));names.forEach(n=>{const key=n+'|'+e.date+'|'+e.kind;workload[key]=true})});const counts={};Object.keys(workload).forEach(k=>{const [n,,kind]=k.split('|');counts[n]??={workshop:0,fitting:0};counts[n][kind.toLowerCase()]++});host.innerHTML=`
${esc(head)}
${['Mon','Tue','Wed','Thu','Fri','Sat','Sun'].map(x=>`
${x}
`).join('')}${tiles}

Potential scheduling conflicts

${conflicts.length?`
${conflicts.map(x=>`

${esc(x)}

`).join('')}
`:'

No overlapping dates or named staff conflicts detected in the currently filtered, loaded test jobs. This is not a guarantee of availability.

'}

Named staff workload (visible calendar period)

Counts are scheduled days, not working hours. Names are manually typed, and availability is not verified.

${Object.keys(counts).length?`
${Object.entries(counts).map(([n,c])=>``).join('')}
Fictional staff nameWorkshop daysFitting days
${esc(n)}${c.workshop}${c.fitting}
`:'

No named staff assigned to the loaded test schedules.

'}

Calendar reads saved cloud schedules only. Refresh cloud jobs to see changes saved elsewhere. It does not make bookings, check leave, or approve production.

`;} const v85PriorRender=render; render=function(){v85PriorRender();if(tab==='Jobs'&&$('app')){$('app').insertAdjacentHTML('beforeend',`

V8.5 — Production calendar & staff workload (test only)

Fictional test jobs only. No real scheduling, staffing or production authorisation.

`);v85Paint()}}