Dashboard

Live CRM · Hawaii time
Connecting…
Search your clients and vehicles, open their profiles, and review services, subscriptions and scheduled work. Editing workflows are the next phase.
Employees—active workforce
Active TCL—subscriptions
Warranties—records
Clients / Vehicles—— vehicles

Operational Picture

Current Connection

Source: Supabase canonical CRM

Mode: Authenticated account

Mode: Browse and review

Planner caveat: Exact Shop/Mobile overrides still live in Airtable.

Employees

IDNameTitleStatus
Select an employee.

Client Snapshot

Click a metric or a zone to filter the client directory below.

Total Clients by Zone

ClientVehicle(s)LocationTCLWarranties

Lead Dashboard

Inquiries remain leads until their first service is completed and the transaction is finalized.

Leads

Due Work Snapshot

Includes services due in the selected period and services completed during that period, even if originally due earlier. No future cadence projections.
Planner tables cover all dates; snapshot reporting is controlled only by the reference period above.
DueClient / VehicleObligationCadence / WarrantyZoneStatus
Next DueClient / VehiclePackageCadencePriceLast ServiceStatus
Next DueClient / VehicleWarrantyLocationInstalledTCL InteractionStatus
Live route candidates derived from canonical due dates + client routing zones. Exact appointment decisions and Shop/Mobile overrides are still in Airtable.
DueClient / VehicleSourceArea / ZoneStatus
PlannedDueClient / VehicleSourceCadenceAppointment

TCL Subscriptions

Locations
Client / VehiclePackageCadencePriceContract StartLocationLast ServiceNext DueStatus

Services

Set up services and packages.

Your Warranty Products

Loading warranties…

Warranty Ledger

Client / VehicleWarranty NumberTypeInstallation DateTermExpiration DateNext Compliance DateStatus

Vehicles

October 2026

Scheduled services$0
ConfirmedPlanned / unconfirmed

Schedule

Employee shifts

+Number(row.price||0).toFixed(2)+(Number(row.time_required_seconds)>0?' · '+Math.round(Number(row.time_required_seconds)/60)+' min':' · Production time not set')+''; }).join(''); $('apptCreateChoices').insertAdjacentElement('beforebegin',summary); $('apptCreateMessage').textContent='Edit location, date or time below. Both vehicles and their services will stay unchanged.'; } $('apptCreateContinue').disabled=false; (async()=>{try{ const db=window.hitideSupabase; const [lineResult,serviceResult]=await Promise.all([ db.from('crm_appointment_services').select('service_id,service_name,production_minutes,price').eq('planner_id',editing.id), db.from('crm_service_builder_drafts').select('id,service_name,area,category,status,availability,reference_minutes,reference_price').order('service_name').limit(500) ]); if(lineResult.error)throw lineResult.error;if(serviceResult.error)throw serviceResult.error; catalog=(serviceResult.data||[]).filter(x=>x.service_name); const lines=lineResult.data||[]; for(const line of lines){const found=catalog.find(x=>x.id===line.service_id);selectedServices.push({...(found||{}),id:line.service_id||null,service_name:line.service_name||found?.service_name||'Service',reference_minutes:line.production_minutes??found?.reference_minutes,reference_price:line.price??found?.reference_price,_existingSnapshot:true});} if(!lines.length){ // Legacy/imported appointments store their agreement and price on crm_planner, not always in crm_appointment_services. // Show the original booking without inventing/replacing its service lines during staff/date/location edits. const linked=state.tcl.find(t=>t.id===editing.tcl_subscription_id); const originalName=editing.name||((editing.service_source==='TCL'&&linked)?'TCL '+(linked.service_package||'Subscription'):editing.service_source==='Warranty Maintenance'?'Coating Maintenance':'Previously booked service'); selectedServices.push({id:null,service_name:originalName,reference_minutes:editing.time_required_seconds==null?null:Number(editing.time_required_seconds)/60,reference_price:editing.price??null,_existingSnapshot:true,_plannerFallback:true}); $('apptCreateMessage').textContent='Original appointment details preserved. Staff, date and location can be edited without replacing its services.'; } editServicesLoaded=true; for(const assignment of state.appointmentResources.filter(a=>a.appointment_group_id===(editing.appointment_group_id||editing.id)))selectedStaff.add(assignment.resource_id); renderSelectedServices();renderSelectedStaff(); }catch(error){$('apptCreateMessage').textContent='Could not load existing services: '+error.message;$('apptCreateContinue').disabled=true;}})(); } if(repeatTclContext&&!editing){const ctx=repeatTclContext;const c=clients.find(x=>x.id===ctx.clientId);if(c){client.value=c.id;clientSearch.value=c.full_name;clientRefresh();vehicle.value=ctx.vehicleId;location.value=ctx.location;addressRefresh();if(ctx.addressId&&$('apptCreateAddress'))$('apptCreateAddress').value=ctx.addressId;$('apptCreateDate').value=ctx.date;$('apptCreateTime').value=ctx.startTime;mode='tcl';sourceRefresh();}} $('apptCreateContinue').onclick=async()=>{ const button=$('apptCreateContinue'),message=$('apptCreateMessage'); if(editing&&!editServicesLoaded && state.planner.filter(x=>(x.appointment_group_id||x.id)===(editing.appointment_group_id||editing.id)).length<=1){message.textContent='Existing appointment services are still loading. Try again in a moment.';return;} const date=$('apptCreateDate').value,startTime=$('apptCreateTime').value; if(editing){ const group=state.planner.filter(x=>(x.appointment_group_id||x.id)===(editing.appointment_group_id||editing.id)); if(group.length>1){ const addressId=location.value==='mobile'?(document.getElementById('apptCreateAddress')?.value||null):null; if(!date||!startTime){message.textContent='Select an appointment date and time.';return;} if(location.value==='mobile'&&!addressId){message.textContent='Select a client service address for the mobile appointment.';return;} // A grouped appointment's metadata edits do not replace or modify its existing vehicle services. const ids=group.map(x=>x.id); const payload={planned_service_date:date,planned_start_time:startTime,service_mode_override:location.value==='mobile'?'Mobile':'Shop',service_address_id:addressId}; button.disabled=true;message.textContent='Updating all vehicles in this appointment…'; const {data,error}=await window.hitideSupabase.from('crm_planner').update(payload).in('id',ids).select('id,planned_service_date,planned_start_time,service_mode_override,service_address_id'); if(error||!data||data.length!==ids.length){message.textContent='Could not confirm changes for all vehicles: '+(error?.message||'Incomplete update')+'. Please refresh and inspect the appointment.';button.disabled=false;return;} for(const saved of data){const local=state.planner.find(x=>x.id===saved.id);if(local)Object.assign(local,saved);} document.getElementById('drawer').classList.remove('open'); renderCalendar();renderPlanners(); return; } } await ratesLoad;if(!ratesReady){message.textContent='Cannot price this appointment while the service pricing table is unavailable.';return;} if(!date||!startTime||!client.value||!vehicle.value){message.textContent='Select a date, start time, client, and vehicle.';return;} const maintenance=mode==='warranty'?selectedMatchingService:null; const tcl=mode==='tcl'?activeTcl():null;const tclItem=tcl?[{id:subscriptionDrafts.find(s=>norm(s.service_name)===norm('TCL '+tcl.service_package))?.id||null,service_name:'TCL '+(tcl.service_package||'Subscription'),reference_price:tcl.contract_price,reference_minutes:(tcl.production_time_override_seconds??tcl.historical_time_required_seconds)/60}]:[];const items=[...(maintenance?[{...maintenance,reference_minutes:maintenanceTimeOverride??maintenance.reference_minutes,reference_price:maintenancePriceOverride??maintenance.reference_price}]:tclItem),...selectedServices]; if(mode==='tcl'&&!tcl){message.textContent='Select an active TCL subscription before saving.';return;} if(mode==='warranty'&&(!maintenance||!state.warranties.some(w=>w.current_holder_id===client.value&&w.vehicle_id===vehicle.value&&w.compliance_required))){message.textContent='Choose a vehicle with a maintenance-eligible warranty and a Coating Maintenance service before saving.';return;} if(!items.length&&(!editing||editServicesChanged)){message.textContent='Select at least one service before saving the appointment.';return;} const incomplete=(!editing||editServicesChanged)&&items.some(s=>priceOf(s)===null); if(incomplete){message.textContent='Set a price for every selected service. Production time is optional.';return;} const repeatTcl=false; const addressId=location.value==='mobile'?(document.getElementById('apptCreateAddress')?.value||null):null; if(location.value==='mobile'&&!addressId){message.textContent='Select a client service address for a mobile appointment.';return;} const primaryItems=items.filter(s=>!s._extraTcl);const totalMinutes=editing&&!editServicesChanged?Number(editing.time_required_seconds||0)/60:primaryItems.reduce((n,s)=>n+(minsOf(s)??0),0); const totalPrice=editing&&!editServicesChanged?editing.price:primaryItems.reduce((n,s)=>n+priceOf(s),0); const existing=editing||(selectedSource?state.planner.find(p=>p.id===selectedSource):null); const linkedTcl=mode==='tcl'?activeTcl():null; const linkedWarranty=mode==='warranty'?state.warranties.find(w=>w.current_holder_id===client.value&&w.vehicle_id===vehicle.value&&w.compliance_required):null; const address=state.clientAddresses.find(a=>a.id===addressId); const appointmentId=existing?.id||crypto.randomUUID(); const groupId=existing?.appointment_group_id||appointmentId; const fields={client_id:client.value,vehicle_id:vehicle.value,planned_service_date:date,planned_start_time:startTime,appointment_status:(editing||creatingMockup)?(document.getElementById('apptCreateStatus')?.value||editing?.appointment_status||'Unscheduled'):'Confirmed',service_mode:location.value==='shop'?'Shop':'Mobile',service_address_id:addressId,...(!editing||editServicesChanged?{price:totalPrice,time_required_seconds:Math.round(totalMinutes*60)}:{})}; button.disabled=true;message.textContent=creatingMockup?'Saving mockup…':'Creating appointment…'; try{ const db=window.hitideSupabase; let row; if(existing){ const related=state.planner.filter(x=>(x.appointment_group_id||x.id)===groupId); const result=await db.from('crm_planner').update(editing?{...fields,...(editServicesChanged?{name:primaryItems.map(s=>s.service_name).join(' + '),price:totalPrice,time_required_seconds:totalMinutes*60}:{}),tcl_subscription_id:editing.vehicle_id===vehicle.value?editing.tcl_subscription_id:null,warranty_id:editing.vehicle_id===vehicle.value?editing.warranty_id:null,service_source:editing.vehicle_id===vehicle.value?editing.service_source:'Manual Appointment'}:fields).in('id',related.map(x=>x.id)).select('*'); if(result.error)throw result.error; if((result.data||[]).length!==related.length)throw Error('Appointment was not fully updated. Please refresh and review before retrying.'); for(const r of result.data){const at=state.planner.findIndex(x=>x.id===r.id);if(at!==-1)state.planner[at]=r;} row=result.data.find(r=>r.id===appointmentId)||result.data[0]; }else{ const payload={...fields,id:appointmentId,appointment_group_id:groupId,airtable_record_id:'crm-appointment-'+appointmentId, name:primaryItems.map(s=>s.service_name).join(' + '),service_source:mode==='tcl'?'TCL':mode==='warranty'?'Warranty Maintenance':'Manual Appointment', tcl_subscription_id:linkedTcl?.id||null,warranty_id:linkedWarranty?.id||null,operational_obligation:false,service_completed:false, effective_routing_area:location.value==='shop'?'Shop':(address?.routing_area||address?.city||null),effective_routing_zone:location.value==='shop'?'Zone 0':(address?.routing_zone||null)}; const result=await db.from('crm_planner').insert(payload).select('*').single(); if(result.error)throw result.error; row=result.data;state.planner.push(row); } const warnings=[]; if(!editing&&mode==='tcl'&&additionalTclIds.size){ for(const extraId of additionalTclIds){ const sub=state.tcl.find(t=>t.id===extraId&&t.client_id===client.value&&t.contract_status==='Active'&&t.canonical_active); if(!sub)throw Error('An additional subscription is no longer active.'); const additionalMinutes=(sub.production_time_override_seconds??sub.historical_time_required_seconds)/60; if(sub.contract_price==null||!Number.isFinite(additionalMinutes))throw Error('Additional TCL agreement has no confirmed price or time.'); const extraRowId=crypto.randomUUID(); const extraServiceName='TCL '+(sub.service_package||'Subscription'); const extraPayload={...fields,id:extraRowId,appointment_group_id:groupId,airtable_record_id:'crm-appointment-'+extraRowId,vehicle_id:sub.vehicle_id,name:extraServiceName,price:Number(sub.contract_price),time_required_seconds:Math.round(additionalMinutes*60),service_source:'TCL',tcl_subscription_id:sub.id,warranty_id:null,operational_obligation:false,service_completed:false,effective_routing_area:location.value==='shop'?'Shop':(address?.routing_area||address?.city||null),effective_routing_zone:location.value==='shop'?'Zone 0':(address?.routing_zone||null)}; const extraResult=await db.from('crm_planner').insert(extraPayload).select('*').single();if(extraResult.error)throw Error('Additional TCL booking could not be saved: '+extraResult.error.message); state.planner.push(extraResult.data); const draft=subscriptionDrafts.find(s=>norm(s.service_name)===norm(extraServiceName)); const lineResult=await db.from('crm_appointment_services').insert({planner_id:extraRowId,service_id:draft?.id||null,service_name:extraServiceName,production_minutes:additionalMinutes,price:sub.contract_price}); if(lineResult.error)warnings.push('Additional TCL service details: '+lineResult.error.message); } } const lines=items.filter(s=>!s._extraTcl).map(s=>({planner_id:row.id,service_id:s.id,service_name:s.service_name,production_minutes:minsOf(s),price:priceOf(s)})); if(editing&&editServicesChanged){const old=await db.from('crm_appointment_services').delete().eq('planner_id',row.id);if(old.error)throw Error('Existing service lines could not be replaced: '+old.error.message);} if(lines.length&&(!editing||editServicesChanged)){const result=await db.from('crm_appointment_services').insert(lines);if(result.error)throw Error('Service details could not be saved: '+result.error.message);} if(editing){const old=await db.from('crm_appointment_resource_assignments').delete().eq('appointment_group_id',groupId);if(old.error)throw Error('Existing staff assignments could not be updated: '+old.error.message);state.appointmentResources=state.appointmentResources.filter(a=>a.appointment_group_id!==groupId);} if(selectedStaff.size){const result=await db.from('crm_appointment_resource_assignments').insert([...selectedStaff].map(resource_id=>({appointment_group_id:groupId,resource_id})));if(result.error)warnings.push('Staff assignment: '+result.error.message);else state.appointmentResources.push(...[...selectedStaff].map(resource_id=>({appointment_group_id:groupId,resource_id})));} calendarCursor=new Date(Number(date.slice(0,4)),Number(date.slice(5,7))-1,1); setCalendarMode(creatingMockup?'mock':'live');renderPlanners();renderCalendar(); document.getElementById('drawer')?.classList.remove('open'); message.textContent=''; if(repeatTcl)openCalendarAppointmentStart(null,{clientId:client.value,vehicleId:vehicle.value,date,startTime,location:location.value,addressId}); if(warnings.length)alert((creatingMockup?'Mockup saved in Mock Calendar':'Appointment created in Live Calendar')+', but some details need attention: '+warnings.join(' · ')); }catch(err){message.textContent='Appointment could not be created: '+(err.message||err);} finally{button.disabled=false;} }; } document.getElementById('calendarCreateAppointment').onclick=()=>openCalendarAppointmentStart(); window.openCalendarAppointmentEdit=openCalendarAppointmentStart; window.openCalendarAppointmentStart=openCalendarAppointmentStart; document.getElementById('calendarPrev').onclick=()=>{calendarCursor=new Date(calendarCursor.getFullYear(),calendarCursor.getMonth()-1,1);renderCalendar();}; document.getElementById('calendarNext').onclick=()=>{calendarCursor=new Date(calendarCursor.getFullYear(),calendarCursor.getMonth()+1,1);renderCalendar();}; document.getElementById('calendarToday').onclick=()=>{calendarCursor=new Date(2026,9,1);renderCalendar();}; document.querySelectorAll('.calendarModeBtn').forEach(b=>b.onclick=()=>setCalendarMode(b.dataset.calendarMode)); document.getElementById('calendarOverlayLive').onchange=renderCalendar; window.addEventListener('hitide-auth-ready',connect); const cMap=()=>new Map(state.clients.map(x=>[x.id,x])),vMap=()=>new Map(state.vehicles.map(x=>[x.id,x]));function renderAll(){renderDashboard();renderEmployees();renderClients();renderTcl();renderWarranties();renderPlanners();renderCalendar()} let calendarMode='live',calendarCursor=new Date(2026,9,1); function calendarMonthKey(d){return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0');} function calendarPlannerMode(p){const m=p.service_mode_override||p.service_mode||'';return String(m).toLowerCase()==='shop'?'Shop':'Mobile';} function calendarLocation(p){return calendarPlannerMode(p)==='Shop'?'Shop':(p.effective_routing_area||'Mobile');} function calendarGroupRows(p){const gid=p.appointment_group_id||p.id;return state.planner.filter(x=>(x.appointment_group_id||x.id)===gid);} function calendarDurationSeconds(p){return calendarGroupRows(p).reduce((sum,x)=>sum+Number(x.time_required_seconds||0),0);} function durationLabel(seconds){const mins=Math.round(Number(seconds||0)/60),h=Math.floor(mins/60),m=mins%60;if(!mins)return 'No production time';if(h&&m)return h+'h '+m+'m';if(h)return h+'h';return m+'m';} function endTimeLabel(start,seconds){if(!start||!seconds)return '';const m=String(start).match(/^(\d{1,2}):(\d{2})/);if(!m)return '';const total=Number(m[1])*60+Number(m[2])+Math.round(Number(seconds)/60);const hh=Math.floor(total/60)%24,mm=total%60,ap=hh>=12?'PM':'AM',h=hh%12||12;return h+':'+String(mm).padStart(2,'0')+' '+ap;} function uniqueCalendarGroups(rows){const seen=new Set();return rows.filter(p=>{const k=p.appointment_group_id||p.id;if(seen.has(k))return false;seen.add(k);return true;});} function plannerTimeLabel(v){if(!v)return 'No time set';const m=String(v).match(/^(\d{1,2}):(\d{2})/);if(!m)return String(v);let hh=Number(m[1]),min=m[2],ap=hh>=12?'PM':'AM';hh=hh%12||12;return hh+':'+min+' '+ap;} function appointmentTitleForPlanner(plannerId){const p=state.planner.find(x=>x.id===plannerId);if(!p)return 'Appointment';const rows=calendarGroupRows(p),c=cMap().get(p.client_id);const label=x=>{if(x.tcl_subscription_id){const t=state.tcl.find(z=>z.id===x.tcl_subscription_id);return 'TCL '+(t?.service_package||'Service');}if(x.warranty_id){const w=state.warranties.find(z=>z.id===x.warranty_id);return w?.maintenance_service_group||((w?.warranty_service_type||'').toLowerCase()==='coating'?'Coating Maintenance':(w?.product_name||w?.warranty_service_type||'Warranty Maintenance'));}return x.service_source||x.name||'Service';};const counts=rows.map(label).reduce((m,n)=>(m[n]=(m[n]||0)+1,m),{});const summary=Object.entries(counts).map(([n,count])=>n+(count>1?' ×'+count:'')).join(' + ');const preferred=(c?.preferred_name||c?.full_name||'Appointment').trim().split(/\s+/)[0];return preferred+' // '+summary;} function calendarRowHtml(p,kind,overlay){ const group=calendarGroupRows(p),duration=calendarDurationSeconds(p),start=p.planned_start_time?plannerTimeLabel(p.planned_start_time):'',end=endTimeLabel(p.planned_start_time,duration),timeText=start?(start+(end?'–'+end:'')):'No time set'; const city=String(p.service_mode_override||p.service_mode||'').toLowerCase()==='shop'?'Shop':(state.clientAddresses.find(a=>a.id===p.service_address_id&&a.is_active!==false)?.city||state.clientAddresses.find(a=>a.client_id===p.client_id&&a.is_primary&&a.is_active!==false)?.city||state.clientAddresses.find(a=>a.client_id===p.client_id&&a.is_active!==false)?.city||'City needed'); if(kind!=='calendarEventLive'){ const vehicles=[...new Set(group.map(x=>vehicleLabel(vMap().get(x.vehicle_id))))]; return ''; } const done=group.length>0&&group.every(x=>x.service_completed===true||x.appointment_status==='Completed'||Boolean(x.completed_at)); const badge=(symbol,cls,label)=>''+symbol+''; let icons=done?badge('🤙','liveCalendarDone','Service complete'):''; const warrantyIds=new Set(group.map(x=>String(x.warranty_id||'')).filter(Boolean)); if(warrantyIds.size){ const serviceIds=new Set(state.warrantyItems.filter(x=>warrantyIds.has(String(x.warranty_id||''))&&x.service_id).map(x=>String(x.service_id))); if(serviceIds.size){ const invoiceIds=new Set(state.invoiceLines.filter(x=>x.service_id&&serviceIds.has(String(x.service_id))&&group.some(g=>String(g.vehicle_id)===String(x.vehicle_id))).map(x=>String(x.invoice_id))); const invoices=state.invoices.filter(x=>invoiceIds.has(String(x.id))&&String(x.client_id)===String(p.client_id)); const quoteIds=new Set(state.quoteLines.filter(x=>x.service_id&&serviceIds.has(String(x.service_id))&&group.some(g=>String(g.vehicle_id)===String(x.vehicle_id))).map(x=>String(x.quote_id))); if(state.quotes.some(x=>quoteIds.has(String(x.id))&&String(x.client_id)===String(p.client_id)))icons+=badge('✒','liveCalendarQuote','Quote linked'); if(invoices.length){ const paid=invoices.every(x=>['paid','settled'].includes(String(x.status||'').toLowerCase())); icons+=badge('$',paid?'liveCalendarPaid':'liveCalendarInvoice',paid?'Invoice paid':'Invoice created'); } } } const mobile=String(p.service_mode_override||p.service_mode||'').toLowerCase()!=='shop'; return ''; } const calendarFoldedWeeks=new Set(); function calendarWeekKey(year,month,week){return year+'-'+String(month+1).padStart(2,'0')+'-week-'+week;} function calendarWeekArrow(year,month,week){ const folded=calendarFoldedWeeks.has(calendarWeekKey(year,month,week)); return ''; } document.addEventListener('click',function(e){ const b=e.target.closest('[data-fold-week]');if(!b)return; e.preventDefault();e.stopPropagation(); const key=calendarWeekKey(calendarCursor.getFullYear(),calendarCursor.getMonth(),Number(b.dataset.foldWeek)); if(calendarFoldedWeeks.has(key))calendarFoldedWeeks.delete(key);else calendarFoldedWeeks.add(key); renderCalendar(); }); document.addEventListener('click',function(e){ const cell=e.target.closest('#calendarGrid .calendarCell[data-calendar-week]'); if(!cell||e.target.closest('button,a,input,select,textarea,[role="button"]'))return; const week=Number(cell.dataset.calendarWeek); const key=calendarWeekKey(calendarCursor.getFullYear(),calendarCursor.getMonth(),week); if(calendarFoldedWeeks.has(key))calendarFoldedWeeks.delete(key);else calendarFoldedWeeks.add(key); renderCalendar(); }); function calendarDailySummary(groups){ if(!groups.length)return ''; const rows=groups.flatMap(p=>calendarGroupRows(p)); const amount=rows.reduce((sum,p)=>sum+Number(p.price||0),0); const seconds=rows.reduce((sum,p)=>sum+Number(p.time_required_seconds||0),0); const mobile=groups.some(p=>calendarPlannerMode(p)!=='Shop'); const hours=seconds/3600; const time=Number.isInteger(hours)?String(hours):hours.toFixed(1); const allCompleted=rows.length>0&&rows.every(p=>p.service_completed===true||p.appointment_status==='Completed'||Boolean(p.completed_at)); return '
'+groups.length+''+(allCompleted?'🤙':'')+''+String.fromCharCode(36)+amount.toLocaleString('en-US',{maximumFractionDigits:0})+''+(mobile?'🚐 · ':'')+time+' production hr'+(hours===1?'':'s')+'
'; } function renderCalendar(){ const grid=document.getElementById('calendarGrid');if(!grid)return; const year=calendarCursor.getFullYear(),month=calendarCursor.getMonth(),first=new Date(year,month,1),days=new Date(year,month+1,0).getDate(),start=first.getDay(),overlay=document.getElementById('calendarOverlayLive')?.checked===true; document.getElementById('calendarMonthLabel').textContent=first.toLocaleDateString('en-US',{month:'long',year:'numeric'}); const monthRows=state.planner.filter(p=>p.planned_service_date&&String(p.planned_service_date).slice(0,7)===calendarMonthKey(first)&&(p.appointment_status==='Confirmed'||p.appointment_status==='Completed')); const monthlyAmount=monthRows.reduce((sum,p)=>sum+Number(p.price||0),0); const monthValue=document.getElementById('calendarMonthValue');if(monthValue)monthValue.textContent='$'+monthlyAmount.toLocaleString('en-US',{maximumFractionDigits:0}); const createButton=document.getElementById('calendarCreateAppointment');if(createButton)createButton.textContent=calendarMode==='mock'?'+ Create Mockup':'+ Create Appointment'; const panel=document.getElementById('calendarPanel');panel.classList.toggle('calendarLiveMode',calendarMode==='live');panel.classList.toggle('calendarMockMode',calendarMode==='mock'); document.querySelectorAll('.calendarModeBtn').forEach(b=>{const active=b.dataset.calendarMode===calendarMode;b.classList.toggle('active',active);b.classList.toggle('secondary',!active)}); document.getElementById('calendarOverlayWrap').style.display=calendarMode==='mock'?'inline-flex':'none'; document.getElementById('calendarOverlayLegend').style.display=calendarMode==='mock'&&overlay?'inline-flex':'none'; const heads=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].map(x=>'
'+x+'
').join(''); let cells=''; for(let i=0;i'+(i===0?calendarWeekArrow(year,month,0):'')+'';} for(let day=1;day<=days;day++){ const date=year+'-'+String(month+1).padStart(2,'0')+'-'+String(day).padStart(2,'0'); const live=uniqueCalendarGroups(state.planner.filter(p=>p.planned_service_date===date&&(p.appointment_status==='Confirmed'||p.appointment_status==='Completed'))).sort((a,b)=>String(a.planned_start_time||'99:99').localeCompare(String(b.planned_start_time||'99:99'))); const mock=uniqueCalendarGroups(state.planner.filter(p=>p.planned_service_date===date&&!['Confirmed','Completed','Declined'].includes(p.appointment_status))).sort((a,b)=>String(a.planned_start_time||'99:99').localeCompare(String(b.planned_start_time||'99:99'))); let rows=''; if(calendarMode==='live')rows=live.map(p=>calendarRowHtml(p,'calendarEventLive',false)).join(''); else{if(overlay)rows+=live.map(p=>calendarRowHtml(p,'calendarEventLive',true)).join('');rows+=mock.map(p=>calendarRowHtml(p,'calendarEventMock',false)).join('');} const wi=Math.floor((start+day-1)/7),folded=calendarFoldedWeeks.has(calendarWeekKey(year,month,wi)); const groups=calendarMode==='live'?live:overlay?[...live,...mock]:mock; cells+='
'+((start+day-1)%7===0?calendarWeekArrow(year,month,wi):'')+'
'+day+'
'+(folded?calendarDailySummary(groups):rows)+'
'; } const used=start+days,tail=(7-(used%7))%7; for(let i=0;i';} grid.innerHTML=heads+cells; } function setCalendarMode(mode){calendarMode=mode;renderCalendar();} function todayHawaii(){return new Intl.DateTimeFormat('en-CA',{timeZone:'Pacific/Honolulu',year:'numeric',month:'2-digit',day:'2-digit'}).format(new Date());} function renderDashboard(){const cm=cMap();const realClients=state.clients.filter(c=>!isTest(c.full_name)),realVehicles=state.vehicles.filter(v=>!isTest(cm.get(v.current_owner_id)?.full_name)&&!isTest(v.vehicle_external_id)),active=state.tcl.filter(t=>t.canonical_active&&t.contract_status==='Active'&&!isTest(cm.get(t.client_id)?.full_name)),wars=state.warranties.filter(w=>!isTest(cm.get(w.current_holder_id)?.full_name)&&!isTest(w.warranty_external_id));document.getElementById('dashEmployees').textContent=state.employments.filter(e=>e.employment_status==='Active').length;document.getElementById('dashTcl').textContent=active.length;document.getElementById('dashWarranties').textContent=wars.length;document.getElementById('dashClients').textContent=realClients.length;document.getElementById('dashVehicles').textContent=`${realVehicles.length} vehicles`;const overdueT=active.filter(t=>t.next_service_due&&t.next_service_duew.source_next_compliance_due&&w.source_next_compliance_due
Overdue TCL${overdueT}
Standalone Warranty Due${overdueW}
`} function renderEmployees(){const pm=new Map(state.people.map(p=>[p.id,p])),rm=new Map(state.resources.map(r=>[r.employment_id,r]));const body=document.getElementById('employeesBody');body.innerHTML=state.employments.map(e=>{const p=pm.get(e.person_id)||{};return `${esc(e.employee_code)}${esc([p.preferred_name||p.legal_first_name,p.legal_last_name].filter(Boolean).join(' '))}${esc(e.job_title||'—')}${esc(e.employment_status)}`}).join('');body.querySelectorAll('tr').forEach(r=>r.onclick=()=>{const e=state.employments.find(x=>x.id===r.dataset.id),p=pm.get(e.person_id)||{},res=rm.get(e.id)||{};document.getElementById('employeeDetail').innerHTML=`

${esc([p.preferred_name||p.legal_first_name,p.legal_last_name].filter(Boolean).join(' '))}

Employee ID
${esc(e.employee_code)}
Title
${esc(e.job_title||'—')}
Hire Date
${fmtDate(e.hire_date)}
Employment
${esc(e.employment_type||'—')}
Schedule
${esc((e.typical_work_days||[]).join(', ')||'—')} · ${esc(e.default_shift_start||'—')}–${esc(e.default_shift_end||'—')}
Resource
${esc(res.resource_code||'—')} · ${res.scheduling_active?'Schedulable':'Not scheduled'}
`});body.querySelector('tr')?.click()} // City -> Zone snapshot from TCL SANDBOX / Zones Mapping (20 canonical routing areas). // Do not infer a zone when the address city is absent or has no approved mapping. const CRM_ROUTING_AREA_ZONES=Object.freeze({ 'lihue':'Zone 3','hanamaulu':'Zone 3','puhi':'Zone 3', 'kapaa':'Zone 2','kapahi':'Zone 2','wailua':'Zone 2','anahola':'Zone 2', 'princeville':'Zone 1','hanalei':'Zone 1','kilauea':'Zone 1', 'lawaii':'Zone 4','koloa':'Zone 4','poipu':'Zone 4','kalaheo':'Zone 4', 'eleele':'Zone 5','kekaha':'Zone 5','waimea':'Zone 5','hanapepe':'Zone 5','kaumakani':'Zone 5', 'shop':'Shop' }); function crmNormalizedRoutingCity(value){return String(value||'').normalize('NFD').replace(/[\u0300-\u036f]/g,'').toLowerCase().replace(/[^a-z0-9]/g,'').trim();} function crmDisplayCity(value){const city=String(value||'').trim();return crmNormalizedRoutingCity(city)==='kapaa'?"Kapa'a":city;} let clientMetricChoice='total',clientCityChoice='',clientGeoView='zone'; function renderClients(){ const q=(document.getElementById('clientSearch').value||'').toLowerCase(),zone=document.getElementById('clientZone').value,visibility=document.getElementById('clientVisibility').value,byOwner=new Map(); state.vehicles.forEach(v=>{const a=byOwner.get(v.current_owner_id)||[];a.push(v);byOwner.set(v.current_owner_id,a)}); const tc=new Map(),wc=new Map(); state.tcl.filter(t=>t.canonical_active&&t.contract_status==='Active').forEach(t=>tc.set(t.client_id,(tc.get(t.client_id)||0)+1)); state.warranties.forEach(w=>wc.set(w.current_holder_id,(wc.get(w.current_holder_id)||0)+1)); const activeAddresses=state.clientAddresses.filter(a=>a.is_active!==false), addressMap=new Map(); for(const a of activeAddresses){const list=addressMap.get(a.client_id)||[];list.push(a);addressMap.set(a.client_id,list)} const clientLocation=c=>{ const aa=addressMap.get(c.id)||[];const primary=aa.find(a=>a.is_primary&&a.street_address)||aa.find(a=>a.street_address)||aa.find(a=>a.is_primary)||aa[0]; const tcl=state.tcl.filter(t=>t.client_id===c.id&&t.contract_status==='Active'&&t.canonical_active); const shop=tcl.some(t=>String(t.service_location_mode||'').toLowerCase()==='shop') || (!primary?.street_address&&!c.street_address&&state.planner.some(p=>p.client_id===c.id&&String(p.service_mode_override||p.service_mode||'').toLowerCase()==='shop')); const city=crmDisplayCity(primary?.city||c.city||''); const z=primary?.routing_zone??c.routing_zone;const area=primary?.routing_area||c.routing_area; const mappedZone=CRM_ROUTING_AREA_ZONES[crmNormalizedRoutingCity(city)]; const explicitZone=(z!=null&&z!=='')?(String(z).toLowerCase().startsWith('zone')?String(z):'Zone '+String(z)):null; const areaZone=CRM_ROUTING_AREA_ZONES[crmNormalizedRoutingCity(area)]; const noAddress=!primary?.street_address&&!c.street_address; const zone=mappedZone||explicitZone||areaZone||(shop&&noAddress?'Shop':'Unassigned'); return {zone,city:city||((zone==='Shop')?'Shop':'Unassigned')}; }; const eligible=state.clients.filter(c=>!isTest(c.full_name)).filter(c=>visibility==='all'||(visibility==='deleted'?!!c.deleted_at:(visibility==='hidden'?c.is_hidden===true&&!c.deleted_at:!c.is_hidden&&!c.deleted_at))); const matchesMetric=c=>clientMetricChoice==='warranties'?(wc.get(c.id)||0)>0:clientMetricChoice==='tcl'?(tc.get(c.id)||0)>0:true; const metrics=[['total','Total Clients',eligible.length],['warranties','Clients with Warranties',eligible.filter(c=>(wc.get(c.id)||0)>0).length],['tcl','Clients with TCL',eligible.filter(c=>(tc.get(c.id)||0)>0).length]]; const snap=document.getElementById('clientSnapshotMetrics');snap.innerHTML=metrics.map(([key,label,count])=>{const selected=clientMetricChoice===key;const bg=selected?'#205c42':'#ddefe3',fg=selected?'#ffffff':'#205c42';return ''}).join(''); const metricPopulation=eligible.filter(matchesMetric).filter(c=>{const vs=byOwner.get(c.id)||[];return !q||[c.full_name,c.email,c.phone,...vs.flatMap(v=>[vehicleLabel(v),v.license_plate])].join(' ').toLowerCase().includes(q)}); const groupCounts=(field)=>{const m=new Map();for(const c of metricPopulation){const key=clientLocation(c)[field];m.set(key,(m.get(key)||0)+1)}return [...m].sort((a,b)=>a[0]==='Shop'?-1:b[0]==='Shop'?1:a[0]==='Unassigned'?1:b[0]==='Unassigned'?-1:field==='zone'?parseInt(a[0].slice(5))-parseInt(b[0].slice(5)):a[0].localeCompare(b[0]))}; const barSection=(title,groups,attr,active,filterValue)=>{const max=Math.max(1,...groups.map(x=>x[1]));return '
'+title+''+metricPopulation.length+' clients · click to filter
'+groups.map(([name,count])=>{const selected=active===name;const bg=selected?'#1d2a21':'#ddefe3',fg=selected?'#ffffff':'#205c42',track=selected?'#355847':'#c2dfcf',fill=selected?'#59a286':'#397f67';return ''}).join('')+'
'}; document.getElementById('clientZoneBreakdown').innerHTML=barSection('Clients by Zone',groupCounts('zone'),'data-client-zone',zone); document.getElementById('clientCityBreakdown').innerHTML=barSection('Clients by Location',groupCounts('city'),'data-client-city',clientCityChoice); document.querySelectorAll('[data-client-geo-view]').forEach(b=>{const on=b.dataset.clientGeoView===clientGeoView;b.classList.toggle('selected',on);b.setAttribute('aria-pressed',String(on));}); // In focused geography mode, keep the selected bar accessible as the toggle, // while retracting the remaining dashboard rows so Clients & Vehicles moves up. const analysisMode=true; document.getElementById('clients').classList.toggle('client-analysis-mode',analysisMode); const geoFocus=''; // Geographic dimension is controlled by the toggle above the chart. const zoneBox=document.getElementById('clientZoneBreakdown'); const cityBox=document.getElementById('clientCityBreakdown'); const snapshot=document.getElementById('clientSnapshot'); document.getElementById('clientSnapshotMetrics').style.display=geoFocus?'none':''; snapshot.querySelector('p.muted').style.display=geoFocus?'none':''; zoneBox.style.display=clientGeoView==='zone'?'':'none'; cityBox.style.display=clientGeoView==='city'?'':'none'; for(const [box,attr,active] of [[zoneBox,'data-client-zone',zone],[cityBox,'data-client-city',clientCityChoice]]){ box.querySelectorAll('button['+attr+']').forEach(button=>{ button.style.display=geoFocus&&button.getAttribute(attr)!==active?'none':''; }); } snapshot.style.padding=geoFocus?'12px 16px':''; const effectiveZone=q?'':zone,effectiveCity=q?'':clientCityChoice; const metricTitle=clientMetricChoice==='warranties'?'Clients with Warranties':clientMetricChoice==='tcl'?'Clients with TCL':'Total Clients'; document.getElementById('clientDirectoryTitle').textContent=metricTitle+' by '+(clientGeoView==='city'?'Location':'Zone'); const rows=metricPopulation.filter(c=>(!effectiveZone||clientLocation(c).zone===effectiveZone)&&(!effectiveCity||clientLocation(c).city===effectiveCity)); document.getElementById('clientCount').textContent=rows.length; document.getElementById('clientsBody').innerHTML=rows.map(c=>`${esc(c.full_name)}${c.deleted_at?' · Deleted':(c.is_hidden?' · Hidden':'')}
${esc(c.phone||c.email||'')}
${(byOwner.get(c.id)||[]).map(v=>esc(vehicleLabel(v))).join('
')||'—'}${esc(clientLocation(c).city)} · ${esc(clientLocation(c).zone)}${tc.get(c.id)||0}${wc.get(c.id)||0}`).join('')||'No matches'; } let tclSort={key:'nextDue',dir:'asc'}; function tclPlannerRecord(t){return state.planner.filter(p=>p.tcl_subscription_id===t.id).sort((a,b)=>String(b.planned_service_date||b.next_service_due||'').localeCompare(String(a.planned_service_date||a.next_service_due||'')))[0]||null;} function tclServiceMode(t){const p=tclPlannerRecord(t),mode=t.service_location_mode||p?.service_mode_override||p?.service_mode||'';if(String(mode).toLowerCase()==='shop')return 'Shop';if(String(mode).toLowerCase()==='mobile')return 'Mobile';return !state.clientAddresses.some(a=>a.client_id===t.client_id&&a.is_active!==false&&a.street_address)?'Shop':'Mobile';} function tclLocation(t){const p=tclPlannerRecord(t);if(tclServiceMode(t)==='Shop')return 'Shop';const a=state.clientAddresses.find(a=>a.id===(t.service_address_id||p?.service_address_id));return a?.routing_area||a?.city||p?.effective_routing_area||cMap().get(t.client_id)?.routing_area||'Location needed';} function updateTclLocationOptions(){const box=document.getElementById('tclLocationChecks');if(!box)return;const prev=new Set([...document.querySelectorAll('.tcl-location-check:checked')].map(x=>x.value));const hadAny=document.querySelectorAll('.tcl-location-check').length>0;const vals=[...new Set(state.tcl.map(t=>tclLocation(t)).filter(Boolean))].sort((a,b)=>a.localeCompare(b));box.innerHTML=vals.map(x=>'').join('');} function tclSortValue(t,key){const c=cMap().get(t.client_id),v=vMap().get(t.vehicle_id);if(key==='client')return ((c?.full_name||'')+' '+vehicleLabel(v)).toLowerCase();if(key==='package')return String(t.service_package||'').toLowerCase();if(key==='cadence')return String(t.frequency||'').toLowerCase();if(key==='price')return t.contract_price==null?null:Number(t.contract_price);if(key==='contractStart')return t.subscription_date||null;if(key==='location')return String(tclLocation(t)||'').toLowerCase();if(key==='lastService')return t.last_tcl_service_date||null;if(key==='nextDue')return t.next_service_due||null;if(key==='status')return String(t.contract_status||'').toLowerCase();return '';} function tclCompare(a,b){const av=tclSortValue(a,tclSort.key),bv=tclSortValue(b,tclSort.key);if(av==null&&bv==null)return 0;if(av==null)return 1;if(bv==null)return -1;let cmp=0;if(typeof av==='number'&&typeof bv==='number')cmp=av-bv;else cmp=String(av).localeCompare(String(bv),undefined,{numeric:true,sensitivity:'base'});return tclSort.dir==='asc'?cmp:-cmp;} function updateTclSortHeaders(){document.querySelectorAll('[data-tcl-sort]').forEach(th=>{const base=th.dataset.tclLabel||th.textContent.replace(/s[▲▼]$/,'');th.dataset.tclLabel=base;th.textContent=base+(th.dataset.tclSort===tclSort.key?(tclSort.dir==='asc'?' ▲':' ▼'):'');th.style.cursor='pointer';});} function renderTcl(){updateTclLocationOptions();const cm=cMap(),vm=vMap(),q=(document.getElementById('tclSearch').value||'').toLowerCase(),st=document.getElementById('tclStatus').value,cad=document.getElementById('tclCadence').value,mobile=document.getElementById('tclMobileIncluded')?.checked!==false,shop=document.getElementById('tclShopIncluded')?.checked!==false;const selectedLocs=new Set([...document.querySelectorAll('.tcl-location-check:checked')].map(x=>x.value));const rows=state.tcl.filter(t=>!isTest(cm.get(t.client_id)?.full_name)&&!isTest(t.subscription_external_id)).filter(t=>!window.hitideTargetTclId||t.id===window.hitideTargetTclId).filter(t=>!st||t.contract_status===st).filter(t=>!cad||t.frequency===cad).filter(t=>window.hitideTargetTclId?t.id===window.hitideTargetTclId:(!tclPlannerRecord(t)||selectedLocs.has(tclLocation(t)))).filter(t=>window.hitideTargetTclId||((tclServiceMode(t)==='Mobile'&&mobile)||(tclServiceMode(t)==='Shop'&&shop))).filter(t=>{const c=cm.get(t.client_id),v=vm.get(t.vehicle_id);return !q||[c?.full_name,vehicleLabel(v),t.subscription_external_id,t.id,t.service_package,t.frequency,tclLocation(t),tclServiceMode(t)].join(' ').toLowerCase().includes(q)}).sort(tclCompare);document.getElementById('tclCount').textContent=rows.length;document.getElementById('tclBody').innerHTML=rows.map(t=>{const c=cm.get(t.client_id),v=vm.get(t.vehicle_id);return ''+esc(c?.full_name||'Unknown')+'
'+esc(vehicleLabel(v))+'
'+esc(t.subscription_external_id||'')+'
'+esc(t.service_package)+''+esc(t.frequency)+''+(t.contract_price!=null?'$'+Number(t.contract_price).toFixed(0):'—')+''+fmtDate(t.subscription_date)+''+esc(tclLocation(t))+''+fmtDate(t.last_tcl_service_date)+''+fmtDate(t.next_service_due)+''+esc(t.contract_status)+''}).join('')||'No matches';updateTclSortHeaders();} function warrantyExpirationDate(w){if(!w?.installation_date||!w?.warranty_term_months)return null;const d=new Date(w.installation_date+'T12:00:00');d.setMonth(d.getMonth()+Number(w.warranty_term_months));return d.toISOString().slice(0,10);} function renderWarranties(){const cm=cMap(),vm=vMap(),q=(document.getElementById('warrantySearch').value||'').toLowerCase(),st=document.getElementById('warrantyStatus').value;const rows=state.warranties.filter(w=>!isTest(cm.get(w.current_holder_id)?.full_name)&&!isTest(w.warranty_external_id)).filter(w=>!window.hitideTargetWarrantyId||w.id===window.hitideTargetWarrantyId).filter(w=>!st||window.hitideWarrantyComputedStatus(w)===st).filter(w=>{const c=cm.get(w.current_holder_id),v=vm.get(w.vehicle_id);return !q||[c?.full_name,vehicleLabel(v),w.product_name,w.warranty_external_id,w.manufacturer_warranty_number].join(' ').toLowerCase().includes(q)}).sort((a,b)=>(a.installation_date||'9999').localeCompare(b.installation_date||'9999'));document.getElementById('warrantyCount').textContent=rows.length;document.getElementById('warrantyBody').innerHTML=rows.map(w=>{const c=cm.get(w.current_holder_id),v=vm.get(w.vehicle_id),months=Number(w.warranty_term_months||0),term=months?(months%12===0?((months/12)+' year'+(months===12?'':'s')):((Math.round((months/12)*10)/10)+' years')):'—';return ''+esc(c?.full_name||'Unknown')+'
'+esc(vehicleLabel(v))+'
'+esc(w.manufacturer_warranty_number||w.warranty_external_id||'—')+''+esc(w.warranty_service_type||'—')+''+fmtDate(w.installation_date)+''+esc(term)+''+fmtDate(warrantyExpirationDate(w))+''+fmtDate(w.source_next_compliance_due)+''+esc(window.hitideWarrantyComputedStatus(w)||'—')+''}).join('')||'No matches'} function dueOK(date,mode){if(!mode)return true;if(!date)return false;const today=new Date(todayHawaii()+'T12:00:00-10:00'),d=new Date(date+'T12:00:00-10:00'),days=(d-today)/86400000;if(mode==='overdue')return days<0;return days>=0&&days<=Number(mode)} function plannerRange(){const value=document.getElementById('plannerReferenceMonth')?.value||'2026-10',parts=value.split('-').map(Number),year=parts[0],month=parts[1]-1,period=document.getElementById('plannerPeriod')?.value||'month';let start,end;const ymd=d=>[d.getFullYear(),String(d.getMonth()+1).padStart(2,'0'),String(d.getDate()).padStart(2,'0')].join('-');if(period==='week'){const d=new Date(year,month,1),day=(d.getDay()+6)%7;start=new Date(year,month,1-day);end=new Date(start.getFullYear(),start.getMonth(),start.getDate()+7);}else if(period==='month'){start=new Date(year,month,1);end=new Date(year,month+1,1);}else if(period==='quarter'){const m=Math.floor(month/3)*3;start=new Date(year,m,1);end=new Date(year,m+3,1);}else if(period==='half'){const m=Math.floor(month/6)*6;start=new Date(year,m,1);end=new Date(year,m+6,1);}else{start=new Date(year,0,1);end=new Date(year+1,0,1);}return {start:ymd(start),end:ymd(end),period};} function plannerWindowContains(date){const r=plannerRange(),key=String(date||'').slice(0,10);return !!key&&key>=r.start&&keyString(p.service_mode_override||(p.service_address_id?'Mobile':p.service_mode)||'').trim().toLowerCase();const type=p=>p.service_source==='Warranty Maintenance'?'Coating maintenance':'TCL';const cadence=p=>p.cadence||state.tcl.find(t=>t.id===p.tcl_subscription_id)?.frequency||'Unspecified';const shown=rows.filter(p=>choice.kind==='total'||(choice.kind==='warranty'&&p.service_source==='Warranty Maintenance')||(choice.kind==='tcl'&&p.service_source==='TCL')||(choice.kind==='shop'&&mode(p)==='shop')||(choice.kind==='mobile'&&mode(p)==='mobile')||(choice.kind==='package'&&p.service_source==='TCL'&&pack(p)===choice.value)||(choice.kind==='cadence'&&p.service_source==='TCL'&&cadence(p)===choice.value));const completed=x=>x.service_completed===true||['completed','complete','done'].includes(String(x.appointment_status||'').toLowerCase());shown.sort((a,b)=>Number(completed(a))-Number(completed(b)));plannerSnapshotVisibleRows=shown;const location=p=>{if(mode(p)==='shop')return 'Shop';const addresses=state.clientAddresses.filter(a=>a.client_id===p.client_id&&a.is_active!==false&&a.street_address);const selected=addresses.find(a=>a.id===p.service_address_id)||addresses.find(a=>a.is_primary)||addresses[0];if(selected)return selected.city||'City needed';return 'City needed';};const status=p=>p.appointment_status||((p.operational_obligation===false)?'Not operational':'Unscheduled');const label=choice.kind==='total'?'All due':choice.kind==='warranty'?'Coating maintenance':choice.kind==='tcl'?'TCL':choice.kind==='shop'?'Shop':choice.kind==='mobile'?'Mobile':choice.value;results.innerHTML='
'+esc(label)+' — '+shown.length+' recordsScrollable due list
'+shown.map(p=>{const client=state.clients.find(c=>c.id===p.client_id),vehicle=state.vehicles.find(v=>v.id===p.vehicle_id);return ''}).join('')+'
Due datePlanned service dateClient / VehicleServiceLocationStatus
'+fmtDate(p.next_service_due||p.planned_service_date)+''+fmtDate(p.planned_service_date)+''+esc(client?.full_name||'Unknown')+'
'+esc(vehicleLabel(vehicle))+'
'+esc(type(p))+''+esc(location(p))+''+esc(status(p))+'
'; } function plannerSnapshotRender(rows){const r=plannerRange(),all=rows.length,warranty=rows.filter(p=>p.service_source==='Warranty Maintenance'),tcl=rows.filter(p=>p.service_source==='TCL');const subById=new Map(state.tcl.map(t=>[t.id,t]));const pack=p=>{const sub=subById.get(p.tcl_subscription_id);return String(sub?.service_package||p.service_package||p.package||'Unspecified')};const count=(list,test)=>list.filter(test).length;const mode=p=>String(p.service_mode_override||p.service_mode||'').toLowerCase();const metrics=[['Total due',all],['Coating maintenance',warranty.length],['TCL',tcl.length],['Shop',count(rows,p=>mode(p)==='shop')],['Mobile',count(rows,p=>mode(p)==='mobile')]];document.getElementById('plannerPeriodCaption').textContent='Due '+r.start+' through '+new Date(r.end+'T12:00:00').toLocaleDateString('en-US',{timeZone:'UTC',month:'short',day:'numeric',year:'numeric'})+' (end exclusive in filtering)';document.getElementById('plannerSnapshotMetrics').innerHTML=metrics.map((m,i)=>'').join('');const packages=[...new Set(tcl.map(pack))].sort();const cadences=[...new Set(tcl.map(p=>p.cadence||subById.get(p.tcl_subscription_id)?.frequency||'Unspecified'))].sort();const pills=(items,fn)=>items.map(n=>''+esc(n)+': '+fn(n)+'').join('');document.getElementById('plannerSnapshotBreakdown').innerHTML='
TCL packages '+(packages.length?pills(packages,n=>count(tcl,p=>pack(p)===n)):'No TCL due')+'
TCL frequencies '+(cadences.length?pills(cadences,n=>count(tcl,p=>(p.cadence||subById.get(p.tcl_subscription_id)?.frequency||'Unspecified')===n)):'No TCL due')+'
';plannerSnapshotList(rows,pack)} let plannerOverdueChoice='total'; function renderPlannerOverdue(){const box=document.getElementById('plannerOverdue');if(!box)return;const range=plannerRange(),hide=document.getElementById('hideTests')?.checked;const cm=cMap(),vm=vMap();const overdue=state.planner.filter(p=>{const due=String(p.next_service_due||p.planned_service_date||'').slice(0,10);const done=p.service_completed===true||['completed','complete','done'].includes(String(p.appointment_status||'').toLowerCase());const client=cm.get(p.client_id);return due&&dueString(a.next_service_due||a.planned_service_date||'').localeCompare(String(b.next_service_due||b.planned_service_date||'')));const byType=t=>overdue.filter(p=>p.service_source===t);const tcl=byType('TCL');const mode=p=>String(p.service_mode_override||p.service_mode||'').toLowerCase();const sub=p=>state.tcl.find(t=>t.id===p.tcl_subscription_id);const pack=p=>sub(p)?.service_package||p.service_package||p.package||'Unspecified';const freq=p=>p.cadence||sub(p)?.frequency||'Unspecified';const metric=[['Total overdue','total',overdue.length],['Coating maintenance','warranty',byType('Warranty Maintenance').length],['TCL','tcl',tcl.length],['Shop','shop',overdue.filter(p=>mode(p)==='shop').length],['Mobile','mobile',overdue.filter(p=>mode(p)==='mobile').length]];const entries=overdue.filter(p=>plannerOverdueChoice==='total'||(plannerOverdueChoice==='warranty'&&p.service_source==='Warranty Maintenance')||(plannerOverdueChoice==='tcl'&&p.service_source==='TCL')||(plannerOverdueChoice==='shop'&&mode(p)==='shop')||(plannerOverdueChoice==='mobile'&&mode(p)==='mobile'));const packages=[...new Set(tcl.map(pack))].sort(),frequencies=[...new Set(tcl.map(freq))].sort();const badges=(values,get)=>values.map(v=>''+esc(v)+': '+tcl.filter(p=>get(p)===v).length+'').join('')||'None';const location=p=>mode(p)==='shop'?'Shop':(p.effective_routing_area||cm.get(p.client_id)?.city||'Location unknown');const status=p=>p.appointment_status||'Unscheduled';box.innerHTML='

Overdue Work

Uncompleted services due before '+esc(range.start)+' — prior periods carry forward
'+metric.map(m=>'').join('')+'
TCL packages '+badges(packages,pack)+'
TCL frequencies '+badges(frequencies,freq)+'
'+esc(metric.find(m=>m[1]===plannerOverdueChoice)?.[0]||'Overdue')+' — '+entries.length+' records
'+entries.map(p=>'').join('')+'
Due datePlanned service dateClient / VehicleServiceLocationStatus
'+fmtDate(p.next_service_due||p.planned_service_date)+''+fmtDate(p.planned_service_date)+''+esc(cm.get(p.client_id)?.full_name||'Unknown')+'
'+esc(vehicleLabel(vm.get(p.vehicle_id)))+'
'+esc(p.service_source==='Warranty Maintenance'?'Coating maintenance':'TCL')+''+esc(location(p))+''+esc(status(p))+'
';} function renderPlannersBase(){const cm=cMap(),vm=vMap(),q=(document.getElementById('plannerSearch').value||'').toLowerCase(),due=document.getElementById('plannerDue').value,hide=document.getElementById('hideTests').checked;const rows=state.planner.filter(function(p){const c=cm.get(p.client_id),v=vm.get(p.vehicle_id),date=p.next_service_due||p.planned_service_date;return !(hide&&(isTest(c&&c.full_name)||isTest(p.name)))&&(!q||[(c&&c.full_name)||'',vehicleLabel(v),p.name||'',p.service_source||'',p.warranty_number||''].join(' ').toLowerCase().includes(q))&&dueOK(date,due)}).sort(function(a,b){return (a.next_service_due||a.planned_service_date||'9999').localeCompare(b.next_service_due||b.planned_service_date||'9999')});const snapshotRows=state.planner.filter(p=>{const c=cm.get(p.client_id);return !(hide&&(isTest(c?.full_name)||isTest(p.name)))&&(plannerWindowContains(p.next_service_due||p.planned_service_date)||((p.service_completed===true||['completed','complete','done'].includes(String(p.appointment_status||'').toLowerCase()))&&plannerWindowContains(p.planned_service_date)))}).sort((a,b)=>String(a.next_service_due||a.planned_service_date||'9999').localeCompare(String(b.next_service_due||b.planned_service_date||'9999')));plannerSnapshotRender(snapshotRows);renderPlannerOverdue();const warrantyById=new Map(state.warranties.map(w=>[w.id,w]));const warrantyDue=p=>warrantyById.get(p.warranty_id)?.source_next_compliance_due||p.next_service_due||null;const warrantyName=p=>warrantyById.get(p.warranty_id)?.product_name||'—';const warrantyStatus=p=>{const w=warrantyById.get(p.warranty_id);return w?(window.hitideWarrantyComputedStatus(w)||'—'):'—'};const st=function(p){return p.appointment_status==='Client Contacted'&&String(p.airtable_record_id||'').startsWith('crm-appointment-')?'Mockup — Unconfirmed':(p.appointment_status||'Unscheduled')};const src=function(p){return p.service_source==='Warranty Maintenance'?'Warranty Maintenance':'TCL'};const tcl=rows.filter(function(p){return p.service_source==='TCL'});const war=rows.filter(function(p){return p.service_source==='Warranty Maintenance'});document.getElementById('tclPlannerBody').innerHTML=tcl.map(function(p){const c=cm.get(p.client_id),v=vm.get(p.vehicle_id);return ''+fmtDate(p.next_service_due)+'
'+esc(plannerTimeLabel(p.planned_start_time))+'
'+esc((c&&c.full_name)||'Unknown')+'
'+esc(vehicleLabel(v))+'
'+esc(src(p))+''+esc(p.cadence||'—')+''+(p.price!=null?'$'+Number(p.price).toFixed(0):'—')+''+fmtDate(p.last_service_date)+''+esc(st(p))+''}).join('');document.getElementById('warrantyPlannerBody').innerHTML=war.map(function(p){const c=cm.get(p.client_id),v=vm.get(p.vehicle_id);return ''+fmtDate(warrantyDue(p))+'
'+'Appointment: '+fmtDate(p.planned_service_date)+'
'+esc((c&&c.full_name)||'Unknown')+'
'+esc(vehicleLabel(v))+'
'+esc(warrantyName(p))+''+esc(String(p.service_mode_override||p.service_mode||'').toLowerCase()==='shop'?'Shop':(p.effective_routing_area||c?.city||'—'))+''+fmtDate(warrantyById.get(p.warranty_id)?.installation_date)+''+esc(p.cadence||'—')+''+esc(warrantyStatus(p))+''}).join('');document.getElementById('masterPlannerBody').innerHTML=rows.map(function(p){const c=cm.get(p.client_id),v=vm.get(p.vehicle_id);return ''+fmtDate(p.next_service_due||p.planned_service_date)+'
'+esc(plannerTimeLabel(p.planned_start_time))+'
'+esc((c&&c.full_name)||'Unknown')+'
'+esc(vehicleLabel(v))+'
'+esc(src(p))+''+esc(p.cadence||p.warranty_number||'—')+''+esc(p.effective_routing_zone||p.effective_routing_area||'—')+''+esc(st(p))+''}).join('');document.getElementById('mobilePlannerBody').innerHTML=rows.filter(function(p){return String(p.service_mode_override||p.service_mode||'').toLowerCase()==='mobile'}).map(function(p){const c=cm.get(p.client_id),v=vm.get(p.vehicle_id);return ''+fmtDate(p.next_service_due||p.planned_service_date)+'
'+esc(plannerTimeLabel(p.planned_start_time))+'
'+esc((c&&c.full_name)||'Unknown')+'
'+esc(vehicleLabel(v))+'
'+esc(src(p))+''+esc(p.effective_routing_area||'—')+' · '+esc(p.effective_routing_zone||'—')+''+esc(st(p))+''}).join('');document.getElementById('shopPlannerBody').innerHTML=rows.filter(function(p){return String(p.service_mode_override||p.service_mode||'').toLowerCase()==='shop'}).map(function(p){const c=cm.get(p.client_id),v=vm.get(p.vehicle_id);return ''+fmtDate(p.planned_service_date)+'
'+esc(plannerTimeLabel(p.planned_start_time))+'
'+fmtDate(p.next_service_due)+''+esc((c&&c.full_name)||'Unknown')+'
'+esc(vehicleLabel(v))+'
'+esc(src(p))+''+esc(p.cadence||'—')+''+esc(st(p))+''}).join('')} // Persist planner view state through appointment saves and rerenders. function plannerSortCell(value){const raw=String(value||'').trim(),money=raw.replace(/[$,]/g,'');if(raw&&/^\$?[0-9,.]+$/.test(raw)&&!isNaN(Number(money)))return {t:'n',v:Number(money)};const date=Date.parse(raw);if(raw&&/\d/.test(raw)&&!isNaN(date))return {t:'d',v:date};return {t:'s',v:raw.toLowerCase()};} function restorePlannerTableSort(table){const tbody=table?.querySelector('tbody');if(!tbody)return;const ths=[...table.querySelectorAll('thead th')],keyPrefix=(tbody.id||table.closest('.section')?.id||'table')+':';const sorts=window.__crmTableSort||{};for(let idx=0;idx tr')].filter(r=>!r.querySelector('.empty'));rows.sort((ra,rb)=>{const a=plannerSortCell(ra.children[idx]?.innerText||ra.children[idx]?.textContent||''),b=plannerSortCell(rb.children[idx]?.innerText||rb.children[idx]?.textContent||'');const c=a.t===b.t&&(a.t==='n'||a.t==='d')?a.v-b.v:String(a.v).localeCompare(String(b.v),undefined,{numeric:true,sensitivity:'base'});return dir==='asc'?c:-c;});rows.forEach(r=>tbody.appendChild(r));ths.forEach(th=>{if(!th.dataset.baseLabel)th.dataset.baseLabel=th.textContent.replace(/\s[▲▼]$/,'');th.textContent=th.dataset.baseLabel;});ths[idx].textContent=ths[idx].dataset.baseLabel+(dir==='asc'?' ▲':' ▼');} } function renderPlanners(){ const area=document.getElementById('planners');if(!area)return renderPlannersBase(); const scrolls=[...area.querySelectorAll('.scroll, [style*="overflow:auto"], [style*="overflow: auto"]')].map(el=>({el,id:el.id,parentId:el.closest('#plannerSnapshotResults, #plannerOverdue')?.id||'',top:el.scrollTop,left:el.scrollLeft})); const focused=document.activeElement,focusId=focused?.id||null; renderPlannersBase(); area.querySelectorAll('table').forEach(restorePlannerTableSort); for(const x of scrolls){let el=x.el;if(!el.isConnected&&x.id)el=document.getElementById(x.id);if(!el?.isConnected&&x.parentId)el=document.querySelector('#'+x.parentId+' [style*=\"overflow:auto\"]');if(!el?.isConnected)continue;el.scrollTop=x.top;el.scrollLeft=x.left;} if(focusId){const el=document.getElementById(focusId);if(el&&document.activeElement!==el&&el!==focused)el.focus({preventScroll:true});} } document.addEventListener('click',e=>{const b=e.target.closest('[data-overdue-kind]');if(!b)return;plannerOverdueChoice=b.dataset.overdueKind;renderPlannerOverdue();});document.addEventListener('click',e=>{const b=e.target.closest('[data-snapshot-kind]');if(!b)return;plannerSnapshotChoice={kind:b.dataset.snapshotKind,value:b.dataset.snapshotValue||''};renderPlanners();});['plannerPeriod','plannerReferenceMonth'].forEach(id=>document.getElementById(id)?.addEventListener('change',renderPlanners));document.getElementById('clientSearch').addEventListener('input',()=>{if(document.getElementById('clientSearch').value.trim()){document.getElementById('clientZone').value='';clientCityChoice='';}renderClients();});['clientZone','clientVisibility'].forEach(id=>document.getElementById(id).addEventListener('input',renderClients));document.addEventListener('click',e=>{const geoView=e.target.closest('[data-client-geo-view]');if(geoView){clientGeoView=geoView.dataset.clientGeoView;renderClients();return;}const metric=e.target.closest('[data-client-metric]');if(metric){clientMetricChoice=metric.dataset.clientMetric;renderClients();return;}const zone=e.target.closest('[data-client-zone]');if(zone){const el=document.getElementById('clientZone');el.value=el.value===zone.dataset.clientZone?'':zone.dataset.clientZone;if(el.value)clientCityChoice='';renderClients();return;}const city=e.target.closest('[data-client-city]');if(city){clientCityChoice=clientCityChoice===city.dataset.clientCity?'':city.dataset.clientCity;if(clientCityChoice)document.getElementById('clientZone').value='';renderClients();}});document.getElementById('clientSnapshotReset').addEventListener('click',()=>{clientMetricChoice='total';clientCityChoice='';document.getElementById('clientZone').value='';renderClients();});['tclSearch','tclStatus','tclCadence','tclMobileIncluded','tclShopIncluded'].forEach(id=>document.getElementById(id).addEventListener('input',renderTcl));document.addEventListener('change',e=>{if(e.target.classList.contains('tcl-location-check'))renderTcl();});['warrantySearch','warrantyStatus'].forEach(id=>document.getElementById(id).addEventListener('input',renderWarranties));['plannerSearch','plannerDue','hideTests'].forEach(id=>document.getElementById(id).addEventListener('input',renderPlanners));document.querySelectorAll('#plannerTabs .tab').forEach(tab=>tab.onclick=()=>{document.querySelectorAll('#plannerTabs .tab').forEach(x=>x.classList.remove('active'));tab.classList.add('active');document.querySelectorAll('.plannerView').forEach(x=>x.classList.remove('active'));document.getElementById(tab.dataset.view).classList.add('active')});document.getElementById('drawerClose').onclick=()=>document.getElementById('drawer').classList.remove('open');