Connexion sécurisée à votre espace.

'; w.document.open(); w.document.write(h); w.document.close(); try { w.onload = function(){ w.print(); }; setTimeout(function(){ try{ w.print(); }catch(e){} }, 500); } catch(e) {} } async function updateOrderResponsible(id,userId){ if(!isAdmin()) return; const o=state.orders.find(x=>x.id===id); if(!o)return; const previous=o.responsible; const assignedName=userId?teamName(userId):''; o.responsible=userId||''; try{ const {data,error}=await sb.from('orders').update({responsible_id:userId||null}).eq('id',id).select('id,responsible_id').single(); if(error)throw error; o.responsible=data?.responsible_id||userId||''; render(); toast(userId?('Commande attribuée à '+assignedName+' ✓'):'Attribution retirée ✓'); await loadAll(); }catch(e){ o.responsible=previous; render(); toast('Erreur attribution : '+(e.message||e)); } } async function updateOrderStatus(id,status){ const o=state.orders.find(x=>x.id===id); if(!o)return; if(isInfographe() && o.responsible!==currentUser.id)return toast('Cette tâche ne vous est pas attribuée.'); const previous=o.status; o.status=status; try{await sb.from('orders').update({status}).eq('id',id);render();toast('État de la commande mis à jour ✓')} catch(e){o.status=previous;render();toast('Erreur état : '+(e.message||e))} } async function deleteOrder(id){if(!confirm('Supprimer cette commande ?'))return;try{await remove('orders',id);state.orders=state.orders.filter(o=>o.id!==id);render();toast('Commande supprimée')}catch(e){toast('Erreur : '+e.message)}} async function deleteInvoice(id){if(!confirm('Supprimer cette facture ?'))return;try{await remove('invoices',id);state.invoices=state.invoices.filter(f=>f.id!==id);render();toast('Facture supprimée')}catch(e){toast('Erreur : '+e.message)}} function printOrders(){window.print()}function printInvoiceList(){window.print()} function daysUntil(dateStr){ if(!dateStr)return null; const today=new Date(); today.setHours(0,0,0,0); const d=new Date(dateStr+"T00:00:00"); d.setHours(0,0,0,0); return Math.round((d-today)/86400000); } function requestBrowserNotifications(){ if(!("Notification" in window))return toast("Les notifications du navigateur ne sont pas disponibles ici."); Promise.resolve(Notification.requestPermission()).then(p=>toast(p==="granted"?"Notifications activées ✓":"Autorisation refusée.")).catch(()=>toast("Notifications non disponibles sur cet appareil.")); } async function showSafeNotification(title, options){ try{ if(!('Notification' in window) || Notification.permission !== 'granted') return false; if(!('serviceWorker' in navigator)) return false; const reg=await navigator.serviceWorker.ready; if(!reg || typeof reg.showNotification !== 'function') return false; await reg.showNotification(title, options); return true; }catch(e){ console.warn('Notification non disponible:', e); return false; } } function notifyUpcomingOrders(upcoming){ if(!("Notification" in window)||Notification.permission!=="granted")return; upcoming.forEach(o=>{ const key="sbdesign_notified_"+o.id+"_"+o.delivery; if(localStorage.getItem(key))return; const d=daysUntil(o.delivery); showSafeNotification("SBDesign Gestion — Livraison proche",{ body:`${o.no} • ${o.client} • livraison ${d===0?"aujourd'hui":"dans "+d+" jour"+(d>1?"s":"")} ${o.delivery_time?("à "+o.delivery_time):""}.` }).catch(()=>{}); localStorage.setItem(key,"1"); }); } function goToOrders(){document.querySelector('[data-page="orders"]').click();} function orderStatusClass(status){return status==='Livrée'?'paid':status==='Prête'?'ready':status==='En cours'?'info':status==='Annulée'?'late':'pending';} function focusStatusGroup(key){ const el=document.getElementById('statusGroup-'+key); if(el){el.scrollIntoView({behavior:'smooth',block:'start'});el.classList.add('status-focus');setTimeout(()=>el.classList.remove('status-focus'),1600);} } function renderOrderStatusDashboard(){ const today=new Date().toISOString().slice(0,10); const groups={late:[],waiting:[],progress:[],ready:[],delivered:[]}; state.orders.forEach(o=>{ const late=o.delivery&&o.delivery{const key=cls==='late'?'late':cls==='pending'?'waiting':cls==='info'?'progress':cls==='ready'?'ready':'delivered';return `
${title} (${arr.length})${arr.length?`
${arr.map(o=>``).join('')}
N°ClientServiceLivraisonÉtat
${esc(o.no)}${esc(o.client)}${esc(o.service)}${esc((o.delivery||'-')+(o.delivery_time?' à '+o.delivery_time:''))}
`:'

Aucune commande.

'}
`}; $('orderStatusLists').innerHTML=box('🔴 Commandes en retard',groups.late,'late')+box('🟡 Commandes en attente',groups.waiting,'pending')+box('🔵 Commandes en cours',groups.progress,'info')+box('🟢 Commandes prêtes',groups.ready,'ready')+box('⚫ Commandes déjà livrées',groups.delivered,'paid'); } function renderMyTasks(){ const tasks=isInfographe()?state.orders.filter(o=>o.responsible===currentUser.id):state.orders; const w=tasks.filter(o=>o.status==='En attente').length,p=tasks.filter(o=>o.status==='En cours'||o.status==='En production').length,r=tasks.filter(o=>o.status==='Prête').length,d=tasks.filter(o=>o.status==='Livrée').length; if($('myWaiting'))$('myWaiting').textContent=w;if($('myProgress'))$('myProgress').textContent=p;if($('myReady'))$('myReady').textContent=r;if($('myDelivered'))$('myDelivered').textContent=d; if($('myTasksBody'))$('myTasksBody').innerHTML=tasks.length?tasks.map(o=>`${esc(o.no)}${esc(o.client)}
${esc(o.phone||'')}${esc(o.service)}${esc((o.delivery||'-')+(o.delivery_time?' à '+o.delivery_time:''))}${money(o.total)}${money(o.advance)}${money(o.due)}${esc(teamName(o.responsible)||'À attribuer')}`).join(''):'Aucune tâche attribuée pour le moment.'; } function callClient(phone){const p=String(phone||'').replace(/[^0-9+]/g,'');if(!p)return toast('Aucun numéro pour ce client');window.location.href='tel:'+p;} function messageClientWhatsApp(phone,name){const p=normalizeWhatsApp(phone);if(!p)return toast('Aucun numéro WhatsApp pour ce client');const msg='Bonjour '+(name||'')+', nous vous contactons concernant votre commande SBDesign.';window.open('https://wa.me/'+p+'?text='+encodeURIComponent(msg),'_blank');} function showPeriodOrders(period){ const now=new Date(), today=now.toISOString().slice(0,10); const dayStart=new Date(now); dayStart.setHours(0,0,0,0); const weekStart=new Date(dayStart); const wd=weekStart.getDay()||7; weekStart.setDate(weekStart.getDate()-wd+1); const monthPrefix=today.slice(0,7); let arr=[],title=''; if(period==='day'){arr=state.orders.filter(o=>o.date===today);title="Commandes d'aujourd'hui";} else if(period==='week'){arr=state.orders.filter(o=>o.date&&new Date(o.date+'T00:00:00')>=weekStart);title='Commandes de cette semaine';} else {arr=state.orders.filter(o=>String(o.date||'').startsWith(monthPrefix));title='Commandes de ce mois';} const rows=arr.map(o=>`${esc(o.no)}${esc(o.client)}${esc(o.service)}${money(o.total)}${money(o.due)}${esc(creatorName(o.createdBy)||'Non renseigné')}${esc(o.status)}`).join('')||'Aucune commande sur cette période.'; openModal(`

📦 ${title}

${arr.length} commande(s) • Total ${money(arr.reduce((a,o)=>a+o.total,0))}

${rows}
N°ClientServiceTotalAvanceResteLivraisonResponsableCréée parStatut
`); } function viewMaillotPhoto(id){const o=state.orders.find(x=>x.id===id);if(!o||!o.photo_maillot)return toast('Aucune photo du maillot');openModal(`

📷 Photo du maillot — ${esc(o.no)}

${esc(o.client)}

`)} function renderOrderPeriodStats(){ const now=new Date(); const today=now.toISOString().slice(0,10); const dayStart=new Date(now); dayStart.setHours(0,0,0,0); const weekStart=new Date(dayStart); const wd=weekStart.getDay()||7; weekStart.setDate(weekStart.getDate()-wd+1); const monthPrefix=today.slice(0,7); const day=state.orders.filter(o=>o.date===today); const week=state.orders.filter(o=>o.date && new Date(o.date+'T00:00:00')>=weekStart); const month=state.orders.filter(o=>String(o.date||'').startsWith(monthPrefix)); const set=(id,v)=>{const el=$(id);if(el)el.textContent=v}; set('ordersDayCount',day.length+' commande'+(day.length>1?'s':'')); set('ordersDayAmount',money(day.reduce((a,o)=>a+o.total,0))); set('ordersWeekCount',week.length+' commande'+(week.length>1?'s':'')); set('ordersWeekAmount',money(week.reduce((a,o)=>a+o.total,0))); set('ordersMonthCount',month.length+' commande'+(month.length>1?'s':'')); set('ordersMonthAmount',money(month.reduce((a,o)=>a+o.total,0))); } function render(){ renderMyTasks(); if($('tirageBody')) { ensureTirageMonth(); renderTirage(); renderTirageExpenses(); } const sales=state.orders.reduce((s,o)=>s+o.total,0),exp=state.expenses.reduce((s,e)=>s+e.amount,0),fees=state.orders.reduce((s,o)=>s+(+o.fees||0),0),due=state.orders.reduce((s,o)=>s+o.due,0); $('kpiSales').textContent=money(sales);$('kpiExpenses').textContent=money(exp+fees);$('kpiOrderFees').textContent=money(fees);$('kpiProfit').textContent=money(sales-exp-fees);$('kpiProfitDetail').textContent='Dépenses : '+money(exp)+' • Frais commandes : '+money(fees);$('kpiDue').textContent=money(due); renderOrderPeriodStats(); applyRolePermissions(); const today=new Date().toISOString().slice(0,10); const upcoming=state.orders.filter(o=>{ const d=daysUntil(o.delivery); return d!==null && d>=0 && d<=3 && o.status!=='Prête' && o.status!=='Livrée' && o.status!=='Annulée'; }); const lateOrders=state.orders.filter(o=>o.delivery&&o.delivery{ const d=daysUntil(o.delivery); const near=d!==null&&d>=0&&d<=3&&o.status!=='Prête'&&o.status!=='Livrée'&&o.status!=='Annulée'; const deliveryLabel=near?(d===0?"Aujourd'hui":d===1?"Demain":"Dans "+d+" jours"):(o.delivery||'-'); const deliveryTime=o.delivery_time?(' à '+o.delivery_time):''; return `${esc(o.no)}${esc(o.client)}
${esc(o.phone||'')}${esc(o.service)}${money(o.total)}${money(o.advance)}${money(o.fees||0)}${money(o.due)}${esc(deliveryLabel+deliveryTime)}${isAdmin()?``:esc(teamName(o.responsible)||'À attribuer')}👤 ${esc(creatorName(o.createdBy)||'Non renseigné')}${o.photo_maillot?``:''} `; }).join('')||'Aucune commande.'; $('clientsBody').innerHTML=state.clients.map(c=>{const os=state.orders.filter(o=>(c.phone&&o.phone===c.phone)||(!c.phone&&o.client===c.name));return `${esc(c.name)}${esc(c.phone||'')}${os.length}${money(os.reduce((s,o)=>s+o.total,0))}${money(os.reduce((s,o)=>s+o.due,0))} `}).join('')||'Aucun client.'; $('cashBody').innerHTML=state.cash.map(x=>`${esc(x.date)}${x.type==='in'?'':'Sortie'}${esc(x.label)}${money(x.amount)}`).join('')||'Aucun mouvement.'; $('expensesBody').innerHTML=state.expenses.map(x=>`${esc(x.date)}${esc(x.category)}${esc(x.description)}${money(x.amount)}`).join('')||'Aucune dépense.'; const ci=state.cash.filter(x=>x.type==='in').reduce((s,x)=>s+x.amount,0),co=state.cash.filter(x=>x.type==='out').reduce((s,x)=>s+x.amount,0); $('cashIn').textContent=money(ci);$('cashOut').textContent=money(co);$('cashBalance').textContent=money(ci-co);$('cashCount').textContent=state.cash.length; $('recent').innerHTML=state.orders.slice(0,6).map(o=>`
${esc(o.no)} — ${esc(o.client)} ${money(o.total)}
`).join('')||'Aucune commande.'; let alertHtml=''; if(lateOrders.length) alertHtml+=`

⚠️ ${lateOrders.length} commande(s) en retard

`; if(upcoming.length){ alertHtml+=`

🔔 ${upcoming.length} commande(s) à livrer dans les 3 jours

`; alertHtml+=upcoming.slice(0,6).map(o=>{const d=daysUntil(o.delivery);return `
${esc(o.no)} — ${esc(o.client)} : ${d===0?'aujourd’hui':d===1?'demain':'dans '+d+' jours'} ${esc(o.delivery+(o.delivery_time?' à '+o.delivery_time:''))}
`}).join(''); notifyUpcomingOrders(upcoming); } if(!lateOrders.length&&!upcoming.length)alertHtml='

'; $('alerts').innerHTML=alertHtml+`

Reste à encaisser : ${money(due)}

`; renderOrderStatusDashboard(); $('invoicesBody').innerHTML=state.invoices.map(f=>`${esc(f.no)}${esc(f.client)}
${esc(f.phone||'')}${esc(f.service)}${money(f.total)}${money(f.paid)}${money(f.due)}${esc(f.date)}${esc(f.status)} `).join('')||'Aucune facture.'; } (async()=>{const {data}=await sb.auth.getSession();if(data.session)await startSession(data.session);else{$('loginScreen').style.display='flex';$('app').style.display='none'}})();