Logo ufficiale SILK EXTENSIONMANAGER 1.0
GESTIONE CLIENTI & APPUNTAMENTI
MODALITÀ DEMO · Giulia Rossi è una cliente fittizia · Puoi modificare e aggiungere dati di prova

Apri nel browser per utilizzare il gestionale

Questa anteprima non esegue le funzioni interattive. Scarica il file e aprilo con un browser che consenta JavaScript e il salvataggio locale.

`);w.document.close()}function deleteQuote(id){if(confirm('Eliminare il preventivo?')){db.quotes=db.quotes.filter(q=>q.id!==id);save();render()}}function quoteMode(){let f=document.querySelector('#quoteForm');if(!f)return;if(f.elements.mode.value!=='full'){f.elements.catalogId.value='';f.elements.hairCost.value='0';f.elements.hair.value='0';}else{catalogPrice()}updateQuoteTotal()}function quoteInput(e){if(e.target.name==='mode'){quoteMode();return}if(e.target.name==='grams')catalogPrice();else if(['markup','hairCost'].includes(e.target.name))calcSale();else updateQuoteTotal()}function updateQuoteTotal(){let f=document.querySelector('#quoteForm');if(!f)return;document.querySelector('#quoteTotal').textContent=eur(Number(f.elements.hair.value||0)+Number(f.elements.labor.value||0));document.querySelector('#quoteMargin').textContent=eur(Number(f.elements.hair.value||0)-Number(f.elements.hairCost.value||0))}function clientsView(){let c=db.clients.find(x=>x.id===editId);return `

Clienti

${c?'Modifica cliente':'Nuova cliente'}

${field('Nome','nome',c?.nome,'text',true)}${field('Cognome','cognome',c?.cognome,'text',true)}${field('Telefono (con prefisso internazionale)','phone',c?.phone,'tel')}${field('Email','email',c?.email,'email')}
${formEnd()}

Anagrafica (${db.clients.length})

${db.clients.map(c=>``).join('')}
NomeTelefonoEmailAzioni
${esc(c.nome+' '+c.cognome)}${esc(c.phone)}${esc(c.email)}
${quotesPanel()}` }function filterClients(s){document.querySelectorAll('#clientTable tr[data-search]').forEach(r=>r.style.display=r.dataset.search.includes(s.toLowerCase())?'':'none')}function clientSave(e){e.preventDefault();let f=new FormData(e.target),c={id:editId||uid(),nome:f.get('nome').trim(),cognome:f.get('cognome').trim(),phone:f.get('phone').trim(),email:f.get('email').trim()};if(editId)db.clients=db.clients.map(x=>x.id===editId?c:x);else db.clients.push(c);save();editId=null;render()}function removeClient(id){if(db.treatments.some(t=>t.clientId===id)||db.appointments.some(a=>a.clientId===id)||db.quotes.some(q=>q.clientId===id)){alert('Prima elimina gli appuntamenti e le schede associate.');return}if(confirm('Eliminare questa cliente?')){db.clients=db.clients.filter(c=>c.id!==id);save();render()}}function treatmentsView(){let t=db.treatments.find(x=>x.id===editId);return `

Storico applicazioni extension

${t?'Modifica trattamento':'Registra trattamento'}

${!db.clients.length?'

Inserisci prima una cliente nella sezione Clienti.

':''}
${field('Data','date',t?.date||new Date().toISOString().slice(0,10),'date',true)}${field('Lunghezza (cm)','length',t?.length,'number')}${field('Peso applicato (g)','weight',t?.weight,'number')}${field('Colore','color',t?.color)}${field('Prezzo (€)','price',t?.price,'number')}${field('Foto cliente (link o riferimento)','photo',t?.photo)}${field('Prossima manutenzione','next',t?.next,'date')}
${formEnd()}

Storico trattamenti

${db.treatments.slice().sort((a,b)=>b.date.localeCompare(a.date)).map(t=>``).join('')}
DataClienteTecnicaLunghezzaPesoColorePrezzoFotoAzioni
${dateIT(t.date)}${esc(clientName(t.clientId))}${esc(t.tech)}${esc(t.length)} cm${esc(t.weight)} g${esc(t.color)}${eur(t.price)}${t.photo?`${esc(t.photo).slice(0,28)}`:'—'}
`}function treatmentSave(e){e.preventDefault();let f=new FormData(e.target),t=Object.fromEntries(f.entries());t.id=editId||uid();if(editId)db.treatments=db.treatments.map(x=>x.id===editId?t:x);else db.treatments.push(t);save();editId=null;render()}function agendaView(){let a=db.appointments.find(x=>x.id===editId);return `

Agenda appuntamenti

${a?'Modifica appuntamento':'Nuovo appuntamento'}

${field('Data','date',a?.date,'date',true)}${field('Ora','time',a?.time,'time',true)}${field('Servizio','service',a?.service,'text',true)}
${formEnd()}

Tutti gli appuntamenti

${db.appointments.slice().sort((a,b)=>(a.date+a.time).localeCompare(b.date+b.time)).map(a=>``).join('')}
DataOraClienteServizioWhatsAppAzioni
${dateIT(a.date)}${esc(a.time)}${esc(clientName(a.clientId))}${esc(a.service)}${wa(a)}
`}function appointmentSave(e){e.preventDefault();let a=Object.fromEntries(new FormData(e.target).entries());a.id=editId||uid();if(editId)db.appointments=db.appointments.map(x=>x.id===editId?a:x);else db.appointments.push(a);save();editId=null;render()}function wa(a){let c=db.clients.find(x=>x.id===a.clientId);if(!c?.phone)return 'Telefono mancante';let phone=c.phone.replace(/\D/g,'');if(phone.startsWith('00'))phone=phone.slice(2);if(phone.length===10)phone='39'+phone;let msg=`Ciao ${c.nome}, ti ricordiamo il tuo appuntamento SILK EXTENSION del ${dateIT(a.date)} alle ${a.time} per ${a.service}. A presto!`;return `Prepara messaggio`}function accountsView(){let income=db.treatments.reduce((s,t)=>s+Number(t.price||0),0),out=db.expenses.reduce((s,x)=>s+Number(x.amount||0),0);return `

Contabilità

Riepilogo gestionale, non contabilità fiscale. I prezzi dei trattamenti sono considerati ricavi registrati.

Ricavi
${eur(income)}
Spese
${eur(out)}
Saldo
${eur(income-out)}

Aggiungi spesa

${field('Data','date',new Date().toISOString().slice(0,10),'date',true)}${field('Descrizione','description','','text',true)}${field('Importo (€)','amount','','number',true)}
${formEnd('Registra spesa')}

Spese registrate

${db.expenses.map(x=>``).join('')}
DataDescrizioneImporto
${dateIT(x.date)}${esc(x.description)}${eur(x.amount)}
`}function expenseSave(e){e.preventDefault();let x=Object.fromEntries(new FormData(e.target).entries());x.id=uid();db.expenses.push(x);save();render()}function del(type,id){if(confirm('Eliminare questa registrazione?')){db[type]=db[type].filter(x=>x.id!==id);save();editId=null;render()}}function backupView(){return `

Backup e dati

Salva i tuoi dati

Esporta una copia JSON completa, oppure un CSV delle clienti. Conserva il backup in una posizione sicura.

Ripristina backup

Il ripristino sostituisce tutti i dati locali presenti in questa app.

Informazioni sulla sicurezza

Questa versione funziona offline nel browser e memorizza i dati esclusivamente sul dispositivo. Non offre accesso con password, sincronizzazione cloud o invio automatico dei messaggi. Evita di registrare dati sensibili finché non sarà disponibile la versione protetta.

`}function download(name,contents,type){let a=document.createElement('a'),url=URL.createObjectURL(new Blob([contents],{type}));a.href=url;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(url),1000)}function exportJSON(){download('SILK_backup_'+new Date().toISOString().slice(0,10)+'.json',JSON.stringify(db,null,2),'application/json')}function exportCSV(){let cols=['nome','cognome','phone','email'],lines=[['Nome','Cognome','Telefono','Email'],...db.clients.map(c=>cols.map(k=>c[k]||''))];download('SILK_clienti.csv','\ufeff'+lines.map(r=>r.map(v=>'"'+String(v).replace(/"/g,'""')+'"').join(';')).join('\r\n'),'text/csv;charset=utf-8')}async function importJSON(e){let f=e.target.files[0];if(!f)return;try{let x=JSON.parse(await f.text());if(!['clients','treatments','appointments','expenses'].every(k=>Array.isArray(x[k])))throw Error('Formato non valido');if(confirm('Sostituire tutti i dati locali con il backup selezionato?')){db=x;db.quotes=db.quotes||[];db.catalog=db.catalog||[];save();render()}}catch(err){alert('Backup non valido: '+err.message)}}render();