'use strict'; const PP=(()=>{ const TZ='Asia/Manila',STORE='picklePulseV500';const HOURS=Array.from({length:16},(_,i)=>8+i); function today(){let p=new Intl.DateTimeFormat('en-CA',{timeZone:TZ,year:'numeric',month:'2-digit',day:'2-digit'}).formatToParts(new Date()).reduce((a,x)=>(a[x.type]=x.value,a),{});return `${p.year}-${p.month}-${p.day}`} function addDays(d,n){let x=new Date(d+'T00:00:00+08:00');x.setDate(x.getDate()+n);return x.toISOString().slice(0,10)}function hourLabel(h){let x=h%12;if(!x)x=12;return `${x}:00 ${h>=12?'PM':'AM'}`}function dateLabel(d){return new Date(d+'T00:00:00+08:00').toLocaleDateString('en-US',{weekday:'short',month:'short',day:'numeric',timeZone:TZ})}function money(v){return '₱'+Number(v||0).toLocaleString()} function defs(){return{user:null,settings:{logo:'assets/pickle-pulse-logo.svg',rates:{regularPrime:500,regularNonPrime:400,opPrime:400,opNonPrime:350,event:400},primeStart:16,courts:{1:'Dink',2:'Volley',3:'Drive'},holidays:['2026-01-01','2026-02-17','2026-04-02','2026-04-03','2026-04-04','2026-04-09','2026-05-01','2026-06-12','2026-08-31','2026-11-01','2026-11-30','2026-12-08','2026-12-24','2026-12-25','2026-12-30','2026-12-31'],content:{heroTitle:'Pickle Pulse by MCI',heroText:'Book courts, join OP sessions, and host events at our covered pickleball facility.',badge:'3 Covered Courts',about:'A community pickleball hub for open plays, beginner clinics, inter-school activities, and events.',how1:'Login using Google, Apple, or Email.',how2:'Select schedules and available courts.',how3:'Submit request and upload proof of payment.',how4:'Admin verifies and confirms your booking.',opText:'OP Partners are approved by admin and must reserve at least 4 OP sessions per month using all 3 courts.',eventText:'Events require all 3 courts and at least 8 consecutive hours.',payment:'GCash / Bank details will be shown after request submission.',footer:'© Pickle Pulse by MCI'},slides:[{title:'Welcome to Pickle Pulse',text:'3 covered courts for your next game.',image:''},{title:'OP Partner Program',text:'Run open plays with prime-time priority.',image:''}],gallery:[],announcements:[]},users:[{id:'admin',name:'Super Admin',email:'admin@picklepulse.fun',roles:['booker','superAdmin']}],roles:{superAdmin:['all'],operations:['dashboard','bookings','calendar','op','events'],finance:['payments','reports'],marketing:['content','gallery','announcements'],opCoordinator:['op'],eventsCoordinator:['events']},bookings:[],master:[],proofs:[],audit:[],ui:{tab:'regular',weekStart:today(),admin:'dashboard',slide:0},builder:{pending:[],added:[],latest:''}}} let state=load();function load(){try{let raw=localStorage.getItem(STORE);if(raw)return merge(defs(),JSON.parse(raw))}catch(e){}return defs()}function merge(a,b){let o={...a,...b};o.settings={...a.settings,...(b.settings||{}),rates:{...a.settings.rates,...(b.settings?.rates||{})},courts:{...a.settings.courts,...(b.settings?.courts||{})},content:{...a.settings.content,...(b.settings?.content||{})},slides:b.settings?.slides||a.settings.slides,gallery:b.settings?.gallery||a.settings.gallery,announcements:b.settings?.announcements||a.settings.announcements};o.ui={...a.ui,...(b.ui||{})};o.builder={...a.builder,...(b.builder||{})};return o}function save(){localStorage.setItem(STORE,JSON.stringify(state))} function courts(){return[1,2,3].map(id=>({id,name:state.settings.courts[id]||`Court ${id}`}))}function courtName(id){return state.settings.courts[id]||`Court ${id}`}function isPrime(d,h){let day=new Date(d+'T00:00:00+08:00').getDay();return day===0||day===6||state.settings.holidays.includes(d)||h>=state.settings.primeStart}function rate(d,h,t='regular'){if(t==='event')return state.settings.rates.event;if(t==='op')return isPrime(d,h)?state.settings.rates.opPrime:state.settings.rates.opNonPrime;return isPrime(d,h)?state.settings.rates.regularPrime:state.settings.rates.regularNonPrime} function log(a,n=''){state.audit.push({time:new Date().toISOString(),action:a,note:n})}function id(p){return p+'-'+Date.now()}function key(d,h){return d+'|'+h} function occ(d,h){let o={};courts().forEach(c=>o[c.id]={status:'available'});state.master.forEach(i=>{if(i.date===d&&+i.hour===+h)o[i.court]={status:i.status,item:i}});state.builder.added.forEach(s=>{if(s.date===d&&+s.hour===+h)(s.courts||[]).forEach(c=>{if(o[c].status==='available')o[c]={status:'added'}})});return o} function slotInfo(d,h){let o=occ(d,h),v=Object.values(o),av=v.filter(x=>x.status==='available').length,sel=state.builder.pending.some(s=>s.date===d&&+s.hour===+h);if(sel)return{cls:'selected',main:'Selected',sub:'Current',av};if(av===3)return{cls:'available',main:'Available',sub:'3 left',av};if(av>0)return{cls:'partial',main:'Partial',sub:av+' left',av};if(v.some(x=>x.status==='reserved'))return{cls:'reserved',main:'Reserved',sub:'No courts',av};if(v.some(x=>x.status==='booked'))return{cls:'booked',main:'Booked',sub:'No courts',av};return{cls:'blocked',main:'Blocked',sub:'No courts',av}} function classify(){let list=state.builder.added,all3=list.length&&list.every(s=>(s.courts||[]).length===3),by={};list.forEach(s=>(by[s.date]??=[]).push(+s.hour));let ev=false;Object.values(by).forEach(a=>{let s=[...new Set(a)].sort((x,y)=>x-y),st=1;for(let i=1;i=8&&all3)ev=true}if(s.length>=8&&all3)ev=true});let op=list.filter(s=>(s.courts||[]).length===3).length>=16;if(ev)return{type:'event',label:'Event Candidate'};if(op)return{type:'op',label:'OP Partner Candidate'};return{type:'regular',label:'Regular Booking'}} function rows(){let c=classify(),r=[];state.builder.added.forEach(s=>(s.courts||[]).forEach(ct=>r.push({date:s.date,hour:s.hour,court:ct,rate:c.type==='event'?rate(s.date,s.hour,'event'):c.type==='op'?rate(s.date,s.hour,'op'):rate(s.date,s.hour)})));return r} function renderPublic(){let r=document.getElementById('publicRoot');if(!r)return;let c=state.settings.content,sl=state.settings.slides[state.ui.slide%state.settings.slides.length]||{};r.innerHTML=`
${c.badge}

${c.heroTitle}

${c.heroText}

Book Now
${sl.image?``:'Pickle Pulse Slideshow'}

${sl.title||''}

${sl.text||''}

Book

Book a Court

${c.about}

${tabs()}${regularPanel()}${opPanel()}${eventPanel()}
Gallery

Photos & Updates

`;bindPublic()} function tabs(){return`
${[['regular','Regular Booking'],['op','OP Partner'],['event','Events']].map(x=>``).join('')}
`} function cal(){if(state.ui.weekStartaddDays(state.ui.weekStart,i));return`
${['available','partial','selected','reserved','booked','blocked'].map(s=>``).join('')}
${days.map(d=>`
${dateLabel(d)}
`).join('')}
${HOURS.map(h=>`
${hourLabel(h)}
${days.map(d=>{let i=slotInfo(d,h);return``}).join('')}
`).join('')}
`} function regularPanel(){return`
${state.settings.content.how1} ${state.settings.content.how2}
${cal()}

Booker Information

${proofBox()}
`} function sched(s,l){return`
${dateLabel(s.date)} ${hourLabel(s.hour)}
${courts().map(c=>{let o=occ(s.date,s.hour)[c.id],sel=(s.courts||[]).includes(c.id),ok=o.status==='available'||o.status==='added';return``}).join('')}
`} function requirements(){let a=state.builder.added,p=state.builder.pending,ok=a.length>0,all=[...a,...p].every(s=>(s.courts||[]).length);return`
Selection Requirement Status
${ok?'✓':'☐'} Added schedule
${all?'✓':'☐'} Courts assigned
${classify().label}
`} function fee(){let rs=rows(),tot=rs.reduce((a,x)=>a+x.rate,0);return`
Total
${money(tot)}
${rs.length} court-hours
`} function proofBox(){let show=state.builder.latest?'':'hidden';return`

Upload Proof of Payment

`} function opPanel(){return`
${state.settings.content.opText}
`} function eventPanel(){return`
${state.settings.content.eventText}
`} function bindPublic(){document.querySelectorAll('[data-tab]').forEach(b=>b.onclick=()=>{state.ui.tab=b.dataset.tab;save();renderPublic()});document.querySelectorAll('[data-action]').forEach(b=>b.onclick=e=>{e.preventDefault();route(b.dataset.action,b)})} function route(a,b){if(a==='nextSlide'){state.ui.slide++;save();renderPublic()}if(a==='prevWeek'){state.ui.weekStart=addDays(state.ui.weekStart,-7);if(state.ui.weekStarts.key===k);x>=0?state.builder.pending.splice(x,1):state.builder.pending.push({key:k,date:d,hour:h,courts:[]});save();renderPublic()} function toggleCourt(l,k,c){let arr=l==='added'?state.builder.added:state.builder.pending,s=arr.find(x=>x.key===k);if(!s)return;s.courts=s.courts||[];s.courts.includes(c)?s.courts=s.courts.filter(x=>x!==c):s.courts.push(c);save();renderPublic()} function bulkCourt(c){state.builder.pending.forEach(s=>{s.courts=s.courts||[];if(!s.courts.includes(c))s.courts.push(c)});save();renderPublic()} function addSelected(){if(!state.builder.pending.length)return alert('Select schedule first.');if(state.builder.pending.some(s=>!s.courts.length))return alert('Select courts first.');state.builder.pending.forEach(s=>{let i=state.builder.added.findIndex(a=>a.key===s.key);i>=0?state.builder.added[i]=JSON.parse(JSON.stringify(s)):state.builder.added.push(JSON.parse(JSON.stringify(s)))});state.builder.pending=[];save();renderPublic()} function submitBooking(){let rs=rows();if(!rs.length)return alert('Add schedules first.');let n=document.getElementById('bookerName').value||'Guest',e=document.getElementById('bookerEmail').value||'guest@email.com',bid=id('PP');let bk={id:bid,type:classify().type,name:n,email:e,status:'reserved',paymentStatus:'awaiting proof',items:JSON.parse(JSON.stringify(state.builder.added)),total:rs.reduce((a,x)=>a+x.rate,0),proof:null,created:new Date().toISOString()};state.bookings.push(bk);bk.items.forEach(s=>(s.courts||[]).forEach(c=>state.master.push({id:bid+'-'+s.date+'-'+s.hour+'-'+c,requestId:bid,date:s.date,hour:s.hour,court:c,status:'reserved'})));state.builder.latest=bid;state.builder.added=[];save();renderPublic();document.getElementById('reservationResult').innerHTML=`
Reservation submitted: ${bid}. Upload proof below.
`;document.getElementById('proofBox').classList.remove('hidden')} function fileData(f){return new Promise((res,rej)=>{let r=new FileReader();r.onload=()=>res(r.result);r.onerror=rej;r.readAsDataURL(f)})}async function uploadProof(){let bk=state.bookings.find(x=>x.id===state.builder.latest),box=document.getElementById('proofAlert');if(!bk)return box.innerHTML='
Submit booking first.
';let f=document.getElementById('proofFile').files[0],ref=document.getElementById('paymentRef').value;if(!f||!ref)return box.innerHTML='
Choose file and enter reference.
';let data=await fileData(f);bk.proof={fileName:f.name,fileType:f.type,data,ref,method:document.getElementById('paymentMethod').value,uploaded:new Date().toISOString()};bk.status='payment submitted';bk.paymentStatus='proof uploaded';state.proofs.push({bookingId:bk.id,...bk.proof});save();box.innerHTML='
Proof uploaded. Admin can review.
'} function submitOP(){state.bookings.push({id:id('OP'),type:'op',name:document.getElementById('opName').value,email:document.getElementById('opEmail').value,status:'pending approval',total:0,created:new Date().toISOString()});save();document.getElementById('opAlert').innerHTML='
OP request submitted.
'} function submitEvent(){let st=+document.getElementById('eventStart').value,en=+document.getElementById('eventEnd').value;if(en-st<8)return document.getElementById('eventAlert').innerHTML='
Events require 8+ hours.
';state.bookings.push({id:id('EVT'),type:'event',org:document.getElementById('eventOrg').value,status:'reserved',total:(en-st)*3*state.settings.rates.event,created:new Date().toISOString()});save();document.getElementById('eventAlert').innerHTML='
Event request submitted.
'} function renderAdmin(){let r=document.getElementById('adminRoot');if(!r)return;let secs=['dashboard','bookings','payments','calendar','users','roles','content','gallery','settings','reports'];r.innerHTML=`
${adminContent()}
`;bindAdmin()} function adminContent(){let s=state.ui.admin;if(s==='dashboard')return`

Dashboard

Bookings${state.bookings.length}
Payments${state.proofs.length}
Revenue${money(state.bookings.reduce((a,b)=>a+(b.total||0),0))}
Users${state.users.length}
`;if(s==='bookings')return`

Bookings

${cards(state.bookings)}`;if(s==='payments')return`

Payments

${cards(state.bookings.filter(b=>b.proof))}`;if(s==='content')return contentAdmin();if(s==='gallery')return galleryAdmin();if(s==='settings')return settingsAdmin();if(s==='users')return usersAdmin();if(s==='roles')return rolesAdmin();if(s==='reports')return`

Reports

`;return`

${s}

Module ready for database integration.

`} function cards(arr){return`
${arr.map(b=>`
${b.id}${b.paymentStatus||b.status}

${b.name||b.org||''} ${b.email||''}

${money(b.total)}

`).join('')||'No records.'}
`} function contentAdmin(){let c=state.settings.content;return`

Website Content

${Object.keys(c).map(k=>``).join('')}

Homepage Slides

${state.settings.slides.map((s,i)=>`
`).join('')}`} function galleryAdmin(){return`

Gallery / Homepage Pictures

`} function settingsAdmin(){return`

Settings

${Object.keys(state.settings.rates).map(k=>``).join('')}${courts().map(c=>``).join('')}
`} function usersAdmin(){return`

Users

${state.users.map(u=>`
${u.name} • ${u.email} • ${u.roles.join(', ')}
`).join('')}`};function rolesAdmin(){let perms=['dashboard','bookings','payments','calendar','users','roles','content','gallery','settings','reports'];return`

Roles

${Object.keys(state.roles).map(r=>``).join('')}${perms.map(p=>`${Object.keys(state.roles).map(r=>``).join('')}`).join('')}
Permission${r}
${p}
`} function bindAdmin(){document.querySelectorAll('[data-sec]').forEach(b=>b.onclick=()=>{state.ui.admin=b.dataset.sec;save();renderAdmin()});document.querySelectorAll('[data-admin]').forEach(b=>b.onclick=e=>{e.preventDefault();adminRoute(b.dataset.admin,b)})} async function readFile(id){let f=document.getElementById(id).files[0];return f?await fileData(f):''} async function adminRoute(a,b){if(a==='uploadLogo'){let d=await readFile('logoUpload');if(d){state.settings.logo=d;document.querySelectorAll('.brand img').forEach(i=>i.src=d);save();renderAdmin();renderPublic()}}if(a==='saveContent'){Object.keys(state.settings.content).forEach(k=>{let el=document.getElementById('c_'+k);if(el)state.settings.content[k]=el.value});save();renderAdmin();renderPublic()}if(a==='saveSlides'){for(let i=0;istate.settings.rates[k]=+document.getElementById('rate_'+k).value);courts().forEach(c=>state.settings.courts[c.id]=document.getElementById('court_'+c.id).value);save();renderAdmin();renderPublic()}if(a==='review'){let bk=state.bookings.find(x=>x.id===b.dataset.id);document.getElementById('modalCard').innerHTML=`

${bk.id}

${bk.status}

${bk.proof?(bk.proof.fileType.startsWith('image/')?``:`Open PDF`):'No proof'}`;document.getElementById('modal').classList.add('open')}if(a==='approve'){let bk=state.bookings.find(x=>x.id===b.dataset.id);bk.status='booked';bk.paymentStatus='payment verified';state.master.forEach(i=>{if(i.requestId===bk.id)i.status='booked'});save();renderAdmin();renderPublic()}} function init(){let p=location.pathname.toLowerCase();if(document.getElementById('adminRoot')||p.includes('admin'))renderAdmin();else renderPublic()}document.addEventListener('DOMContentLoaded',init);return{state,renderPublic,renderAdmin}})();