1// Timer timesheet globale â avviabile dall'header "+ Nuovo" senza che lo 2// scadenziario-app sia montato. Replica la logica di 3// scadenziario-app/src/lib/timesheetActions.ts::startTimerEntry usando SOLO le 4// globali (window.db / window.auth / window.firebase / window.DbHelper), così la 5// scrittura su studios/{sid}/time_entries funziona da qualsiasi pagina. 6// ZERO Cloud Function. 7(function () { 8 'use strict'; 9 10 function db() { return window.db || null; } 11 function uid() { return (window.auth && window.auth.currentUser && window.auth.currentUser.uid) || null; } 12 function studioId() { 13 try { 14 if (window.DbHelper && typeof window.DbHelper.getStudioId === 'function') { 15 const s = window.DbHelper.getStudioId(); 16 if (s) return s; 17 } 18 } catch (_) {} 19 return uid(); 20 } 21 function toDateStr(d) { 22 return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); 23 } 24 async function resolveRate(sid, u) { 25 try { 26 const m = await db().collection('studios').doc(sid).collection('team').doc(u).get(); 27 return Number(m.data() && m.data().hourlyRate) || 0; 28 } catch (_) { return 0; } 29 } 30 31 // Avvia un timer (voce status:'running'). Idempotente: se ce n'è già uno in 32 // corso per l'utente, ritorna { already:true } senza crearne un altro. 33 async function start(opts) { 34 opts = opts || {}; 35 const sid = studioId(), u = uid(); 36 if (!db() || !sid || !u) return { error: 'no-session' }; 37 const col = db().collection('studios').doc(sid).collection('time_entries'); 38 const existing = await col.where('userId', '==', u).where('status', '==', 'running').limit(1).get(); 39 if (!existing.empty) return { already: true, id: existing.docs[0].id }; 40 41 const now = new Date(); 42 const rate = await resolveRate(sid, u); 43 let userName = (window.auth && window.auth.currentUser && window.auth.currentUser.displayName) || ''; 44 try { 45 const m = await db().collection('studios').doc(sid).collection('team').doc(u).get(); 46 const d = m.data(); 47 if (d && (d.name || d.displayName)) userName = d.name || d.displayName; 48 } catch (_) {} 49 50 const serverTs = (window.firebase && window.firebase.firestore && window.firebase.firestore.FieldValue 51 && window.firebase.firestore.FieldValue.serverTimestamp) 52 ? window.firebase.firestore.FieldValue.serverTimestamp() 53 : new Date(); 54 55 const ref = await col.add({ 56 userId: u, 57 userName: userName, 58 clientId: opts.clientId || null, 59 clientName: opts.clientName || null, 60 deadlineId: opts.deadlineId || null, 61 deadlineTitle: opts.deadlineTitle || null, 62 dateStr: toDateStr(now), 63 note: opts.note || '', 64 billable: opts.billable !== false, 65 rate: rate, 66 minutes: 0, 67 amount: 0, 68 status: 'running', 69 startedAt: now.getTime(), 70 createdAt: serverTs, 71 }); 72 return { id: ref.id }; 73 } 74 75 // Handler per il menu "+ Nuovo": avvia il timer e porta alla vista Tempi dello 76 // scadenziario (il TimerPill lo mostrerà live). Deep-link via flag + evento. 77 async function startFromMenu() { 78 let res; 79 try { 80 res = await start(); 81 } catch (e) { 82 console.error('[GlobalTimer] start failed', e); 83 if (window.showToast) window.showToast('Impossibile avviare il timer', 'error'); 84 return; 85 } 86 if (res && res.error === 'no-session') { 87 if (window.showToast) window.showToast('Sessione non pronta, riprova', 'error'); 88 return; 89 } 90 if (window.showToast) { 91 window.showToast(res && res.already ? 'Timer già in corso' : 'Timer avviato', 'success'); 92 } 93 // Misura d'uso: solo un timer nuovo davvero scritto (non «già in corso»). 94 if (res && res.id && !res.already) { 95 try { if (window.NavUsage) window.NavUsage.azione('scadenze', 'timer_avviato'); } catch (_) {} 96 // Un timer partito è un tempo_registrato cominciato: finisce quando si ferma. 97 try { if (window.NavUsage && window.NavUsage.inizio) window.NavUsage.inizio('scadenze', 'tempo_registrato'); } catch (_) {} 98 } 99 // Deep-link alla vista Tempi RIUSANDO il bridge esistente dello scadenziario 100 // (`__scadenziarioPendingView` consumata al cold mount + evento 101 // `scadenziario:navigate` per l'app già montata). Niente meccanism
101i paralleli. 102 // IMPORTANTE: router.load è async â un suo reject NON deve sfuggire come 103 // unhandledrejection (mostrerebbe "Si è verificato un errore di sistema"). 104 try { window.__scadenziarioPendingView = 'timesheet'; } catch (_) {} 105 try { 106 if (window.router && typeof window.router.load === 'function') { 107 var p = window.router.load('flows'); 108 if (p && typeof p.then === 'function') p.then(function () {}, function () {}); 109 } 110 } catch (_) {} 111 setTimeout(function () { 112 try { 113 window.dispatchEvent(new CustomEvent('scadenziario:navigate', { detail: { view: 'timesheet' } })); 114 } catch (_) {} 115 }, 500); 116 } 117 118 window.PrismaGlobalTimer = { start: start, startFromMenu: startFromMenu }; 119})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.