public paste
BTRqKokit
—2 views31,251 chars
stdout
Paste content
const API='/api/data';
const $=id=>document.getElementById(id);let projects=[],contractors=[],selectedProjectId=null;
async function get(type,extra=''){try{const r=await fetch(API+'?type='+type+extra);return r.ok?await r.json():[]}catch(e){return[]}}
async function send(data,method='POST'){const r=await fetch(API,{method,headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});return await r.json()}
async function refresh(){projects=await get('projects');contractors=await get('contractors');if(projects.length&&!selectedProjectId)selectedProjectId=projects[0].id;const pc=$('projectCount'),cc=$('contractorCount'),wc=$('workCount');if(pc)pc.textContent=projects.length;if(cc)cc.textContent=contractors.length;let n=0;for(const p of projects)n+=(await get('works','&project_id='+p.id)).length;if(wc)wc.textContent=n}
function openSection(s){if(s==='projects')projectsView();else if(s==='contractors')contractorsView();else if(s==='works')worksView();else if(s==='floors')floorsView();else if(s==='calendar')entriesView();else if(s==='history')historyView();else if(s==='prints')printsView();else simpleView(s==='progress'?'ΠΡΟΟΔΟΣ & ΚΟΣΤΟΣ':s==='documents'?'ΕΓΓΡΑΦΑ':'ΙΣΤΟΡΙΚΟ')}
function simpleView(t){$('content').innerHTML='<div class="sectionHead"><h3>'+t+'</h3></div><div class="notice">Η ενότητα είναι διαθέσιμη.</div>'}
async function historyView(){const p=projects.find(x=>x.id===selectedProjectId)||projects[0];const ph=p?await get('project_history','&project_id='+p.id):[];const audit=await get('history');const projectHtml=ph.length?ph.map(e=>'<div class="row" style="display:block"><b>'+e.entry_date+' · '+(e.contractor_name||'Χωρίς συνεργείο')+'</b><div>'+ (e.work_description||'Χωρίς περιγραφή εργασίας') +'</div><small>'+(e.floor_name||'Χωρίς όροφο')+' · '+(e.people_count||0)+' άτομα'+(e.machines?' · '+e.machines:'')+(e.comments?' · '+e.comments:'')+'</small></div>').join(''):'<div class="notice">Δεν υπάρχουν ακόμη ημερήσιες καταχωρήσεις για το έργο.</div>';const auditHtml=audit.length?audit.map(h=>'<div class="row" style="display:block"><b>'+h.created_at.replace('T',' ').slice(0,16)+' · '+(h.project_name||'Γενικό')+'</b><div>'+h.description+'</div><small>'+h.action+(h.details?' · '+h.details:'')+'</small></div>').join(''):'<div class="notice">Δεν υπάρχουν ακόμη αλλαγές συστήματος.</div>';$('content').innerHTML='<div class="sectionHead"><h3>Ιστορικό έργου</h3><small>'+(p?.name||'')+'</small></div><div class="list">'+projectHtml+'</div><div class="sectionHead" style="margin-top:18px"><h3>Ιστορικό αλλαγών συστήματος</h3></div><div class="list">'+auditHtml+'</div>'}
async function printsView(){const p=projects.find(x=>x.id===selectedProjectId)||projects[0];if(!p){simpleView('ΕΚΤΥΠΩΣΕΙΣ');return}const es=(await get('entries')).filter(e=>e.project_id===p.id).sort((a,b)=>String(a.entry_date).localeCompare(String(b.entry_date)));const ws=await get('works','&project_id='+p.id);$('content').innerHTML='<div class="sectionHead"><h3>Εκτυπώσεις</h3><small>'+p.name+'</small></div><div class="list"><button class="tile full" onclick="printPreview(\'calendar\')"><b>Ημερολόγιο Έργου</b><span>Ημερήσιες εργασίες · συνεργεία · άτομα · μηχανήματα · όροφοι · σχόλια</span></button><button class="tile full" onclick="printPreview(\'progress\')"><b>Πρόοδος Έργου</b><span>Εργασίες · ποσοστά ολοκλήρωσης · κατάσταση</span></button><button class="tile full" onclick="printPreview(\'history\')"><b>Ιστορικό Έργου</b><span>Χρονολογική εικόνα των εργασιών του εργοταξίου</span></button><button class="tile full" onclick="printPreview(\'certification\')"><b>Πιστοποίηση Εργασιών</b><span>Προετοιμασία μορφής πιστοποίησης — θα συνδεθεί με τις συμβάσεις και τις αξίες</span></button></div>'}async function printPreview(kind){const p=projects.find(x=>x.id===selectedProjectId)||projects[0];if(!p)return;const es=(await get('entries')).filter(e=>e.project_id===p.id).sort((a,b)=>String(a.entry_date).localeCompare(String(b.entry_date)));const ws=await get('works','&project_id='+p.id);let title='';let html='';if(kind==='calendar'||kind==='history'){title=kind==='calendar'?'Ημερολόγιο Έργου':'Ιστορικό Έργου';html=es.length?es.map(e=>'<tr><td>'+e.entry_date+'</td><td>'+ (e.contractor_name||'—')+'</td><td>'+ (e.work_description||'—')+'</td><td>'+ (e.floor_name||'—')+'</td><td>'+ (e.people_count||0)+'</td><td>'+ (e.machines||'—')+'</td><td>'+ (e.comments||'')+'</td></tr>').join(''):'<tr><td colspan="7">Δεν υπάρχουν καταχωρήσεις.</td></tr>'}else if(kind==='progress'){title='Πρόοδος Έργου';html=ws.length?ws.map(w=>'<tr><td>'+w.name+'</td><td>'+w.status+'</td><td>'+ (w.progress||0)+'%</td></tr>').join(''):'<tr><td colspan="3">Δεν υπάρχουν εργασίες.</td></tr>'}else{title='Πιστοποίηση Εργασιών';html='<tr><td colspan="3">Η αναλυτική πιστοποίηση θα συνδεθεί με συμβάσεις, αξίες και πιστοποιημένες ποσότητες.</td></tr>'}const cols=kind==='progress'?'<th>Εργασία</th><th>Κατάσταση</th><th>Πρόοδος</th>':'<th>Ημερομηνία</th><th>Συνεργείο</th><th>Εργασία</th><th>Όροφος</th><th>Άτομα</th><th>Μηχανήματα</th><th>Σχόλια</th>';const box=document.createElement('div');box.className='printPreview';box.innerHTML='<div class="printToolbar"><button onclick="this.closest(\'.printPreview\').remove()">Κλείσιμο</button><button class="primary" onclick="window.print()">Εκτύπωση / PDF</button></div><article class="printPaper"><div class="eyebrow">CONSTRUCTION MANAGER</div><h1>'+title+'</h1><h2>'+p.name+'</h2><div class="printMeta">'+(p.address||'')+' · Ημερομηνία έκδοσης '+new Date().toLocaleDateString('el-GR')+'</div><table><thead><tr>'+cols+'</tr></thead><tbody>'+html+'</tbody></table><div class="printFooter">Construction Manager · '+p.name+'</div></article>';document.body.appendChild(box)}
async function openQuickPopup(type,anchor){const p=projects.find(x=>x.id===selectedProjectId)||projects[0];if(!p&&type!=='projects')return;document.querySelector('.quickBackdrop')?.remove();document.querySelector('.quickPopup')?.remove();const titles={projects:'Έργα',works:'Εργασίες',contractors:'Εργολάβοι & Συνεργεία',floors:'Όροφοι',machines:'Μηχανήματα',calendar:'Ημερολόγιο',progress:'Πρόοδος & Κόστος',documents:'Έγγραφα',history:'Ιστορικό',prints:'Εκτυπώσεις'};let body='<div class="notice">Επιλέξτε τι θέλετε να κάνετε.</div>';if(type==='works'){const ws=await get('works','&project_id='+p.id);body=ws.length?ws.map(w=>'<div class="quickItem"><b>'+w.name+'</b><small>'+w.progress+'% · '+w.status+'</small></div>').join(''):'<div class="notice">Δεν υπάρχουν εργασίες.</div>';body+='<button class="primary full" onclick="closeQuickPopup();openModal(\'work\')">+ Νέα εργασία</button>'}else if(type==='contractors'){const cs=contractors||[];body=cs.length?cs.map(c=>'<div class="quickItem"><div><b>'+c.company_name+'</b><small>'+((c.specialty||c.relationship||'').trim())+'</small></div><button onclick="closeQuickPopup();openModal(\'editContractor\',\''+c.id+'\')">Διόρθωση</button></div>').join(''):'<div class="notice">Δεν υπάρχουν καταχωρημένα συνεργεία.</div>';body+='<button class="primary full" onclick="closeQuickPopup();openModal(\'contractor\')">+ Νέο συνεργείο</button>'}else if(type==='projects'){const ps=projects||[];body=ps.length?ps.map(x=>'<div class="quickItem"><b>'+x.name+'</b><small>'+((x.address||'').trim())+'</small></div>').join(''):'<div class="notice">Δεν υπάρχουν καταχωρημένα έργα.</div>';body+='<button class="primary full" onclick="closeQuickPopup();openModal(\'project\')">+ Νέο έργο</button>'}else if(type==='floors'){const fs=await get('floors','&project_id='+p.id);body=fs.length?fs.map(f=>'<div class="quickItem"><b>'+f.name+'</b></div>').join(''):'<div class="notice">Δεν υπάρχουν καταχωρημένοι όροφοι στο έργο.</div>';body+='<button class="primary full" onclick="closeQuickPopup();openModal(\'floor\')">+ Νέος όροφος</button>'}else if(type==='calendar'){const es=(await get('entries')).filter(e=>e.project_id===p.id);body=es.length?es.map(e=>'<div class="quickItem"><div><b>'+e.entry_date+' · '+(e.contractor_name||'—')+'</b><small>'+(e.work_description||'Χωρίς εργασία')+' · '+(e.floor_name||'Χωρίς όροφο')+' · '+(e.people_count||0)+' άτομα'+(e.machines?' · '+e.machines:'')+'</small></div><span><button onclick="closeQuickPopup();openModal(\'editEntry\',\''+e.id+'\')">Διόρθωση</button> <button onclick="deleteEntry(\''+e.id+'\')">×</button></span></div>').join(''):'<div class="notice">Δεν υπάρχουν ημερήσιες καταχωρήσεις.</div>';body+='<button class="primary full" onclick="closeQuickPopup();openModal(\'entry\')">+ Νέα ημερήσια καταχώρηση</button>'}else if(type==='machines'){const es=(await get('entries')).filter(e=>e.project_id===p.id);const machines=[...new Set(es.flatMap(e=>String(e.machines||'').split(/[,;\n]+/).map(x=>x.trim()).filter(Boolean)))];body=machines.length?machines.map(m=>'<div class="quickItem"><b>'+m+'</b></div>').join(''):'<div class="notice">Δεν έχουν καταχωρηθεί ακόμη μηχανήματα στο έργο.</div>';body+='<button class="tile full" onclick="closeQuickPopup();openModal(\'entry\')">+ Καταχώρηση μηχανήματος</button>'}else if(type==='history'){const hs=await get('project_history','&project_id='+p.id);body=hs.length?hs.slice(-20).reverse().map(h=>'<div class="quickItem"><div><b>'+h.entry_date+' · '+(h.contractor_name||'Χωρίς συνεργείο')+'</b><small>'+ (h.work_description||'Χωρίς περιγραφή') +' · '+(h.floor_name||'Χωρίς όροφο')+' · '+(h.people_count||0)+' άτομα'+(h.machines?' · '+h.machines:'')+'</small></div></div>').join(''):'<div class="notice">Δεν υπάρχουν ακόμη ημερήσιες καταχωρήσεις για το έργο.</div>'}const back=document.createElement('div');back.className='quickBackdrop';back.onclick=closeQuickPopup;document.body.appendChild(back);const pop=document.createElement('div');pop.className='quickPopup';pop.innerHTML='<button class="close" onclick="closeQuickPopup()">×</button><div class="eyebrow">ΕΡΓΟ</div><h3 style="margin:6px 0 0">'+titles[type]+' · '+(p?.name||'Construction Manager')+'</h3><div class="quickList">'+body+'</div><button class="tile full" onclick="closeQuickPopup();openSection(\''+type+'\')">Άνοιγμα πλήρους ενότητας</button>';document.body.appendChild(pop)}
function closeQuickPopup(){document.querySelector('.quickBackdrop')?.remove();document.querySelector('.quickPopup')?.remove()}
function projectSelect(name='project_id'){return '<label>Έργο<select name="'+name+'" id="selProject">'+projects.map(p=>'<option value="'+p.id+'" '+(p.id===selectedProjectId?'selected':'')+'>'+p.name+'</option>').join('')+'</select></label>'}
function projectsView(){$('content').innerHTML='<div class="sectionHead"><h3>Έργα</h3><button class="primary" onclick="openModal(\'project\')">+ Νέο έργο</button></div><div class="list">'+projects.map(p=>'<div class="row"><div><b>'+p.name+'</b><small>'+ (p.address||'')+'</small></div><button onclick="selectedProjectId=\''+p.id+'\';worksView()">Άνοιγμα</button></div>').join('')+'</div>'}
function contractorsView(){$('content').innerHTML='<div class="sectionHead"><h3>Εργολάβοι & Συνεργεία</h3><button class="primary" onclick="openModal(\'contractor\')">+ Νέο συνεργείο</button></div><div class="list">'+contractors.map(c=>'<div class="row"><div><b>'+c.company_name+'</b><small>'+ (c.specialty||'')+'</small></div><button onclick="openModal(\'editContractor\',\''+c.id+'\')">Διόρθωση</button> <button onclick="openModal(\'assignment\',\''+c.id+'\')">Εργασίες & αξίες</button> <button onclick="removeContractor(\''+c.id+'\')">Αφαίρεση</button></div>').join('')+'</div>'}
async function worksView(){if(!projects.length)return simpleView('ΕΡΓΑΣΙΕΣ');let p=projects.find(x=>x.id===selectedProjectId)||projects[0];selectedProjectId=p.id;let ws=await get('works','&project_id='+p.id),as=await get('assignments','&project_id='+p.id);$('content').innerHTML='<div class="sectionHead"><h3>Εργασίες</h3><button class="primary" onclick="openModal(\'work\')">+ Νέα εργασία</button></div>'+projectSelect('work_project')+'<div class="list">'+ws.map(w=>'<div class="row"><div><b>'+w.name+'</b><small>'+w.status+' · '+w.progress+'%'+((as.filter(a=>a.work_id===w.id).map(a=>a.contractor_name).join(', '))?' · '+as.filter(a=>a.work_id===w.id).map(a=>a.contractor_name).join(', '):' · Χωρίς συνεργείο')+'</small></div><span><button onclick="openModal(\'editWork\',\''+w.id+'\')">Διόρθωση</button> <button onclick="openModal(\'workAssignment\',\''+w.id+'\')">Σύνδεση συνεργείου</button> <button onclick="deleteWork(\''+w.id+'\')">Διαγραφή</button></span></div>').join('')+'</div>';$('selProject').onchange=e=>{selectedProjectId=e.target.value;worksView()}}
async function floorsView(){if(!projects.length)return simpleView('ΟΡΟΦΟΙ');let p=projects.find(x=>x.id===selectedProjectId)||projects[0],fs=await get('floors','&project_id='+p.id);$('content').innerHTML='<div class="sectionHead"><h3>Όροφοι</h3><button class="primary" onclick="openModal(\'floor\')">+ Νέος όροφος</button></div>'+projectSelect('floor_project')+'<div class="list">'+fs.map(f=>'<div class="row"><b>'+f.name+'</b></div>').join('')+'</div>';$('selProject').onchange=e=>{selectedProjectId=e.target.value;floorsView()}}
async function entriesView(){let es=await get('entries');const groups={};es.forEach(e=>{const k=e.entry_date+'|'+e.project_name;(groups[k]??=[]).push(e)});const html=Object.entries(groups).map(([k,rows])=>{const [date,project]=k.split('|');return '<div class="row" style="display:block"><div><b>'+date+' · '+project+'</b></div>'+rows.map(e=>'<div style="padding:10px 0;border-top:1px solid #ddd"><b>'+ (e.contractor_name||'—')+'</b><div>'+ (e.work_description||'—')+' · '+(e.floor_name||'—')+'</div><small>'+ (e.people_count||0)+' άτομα'+(e.machines?' · '+e.machines:'')+'</small><div style="margin-top:7px"><button onclick="openModal(\'editEntry\',\''+e.id+'\')">Διόρθωση</button> <button onclick="deleteEntry(\''+e.id+'\')">Αφαίρεση</button></div></div>').join('')+'</div>'}).join('');$('content').innerHTML='<div class="sectionHead"><h3>Ημερολόγιο</h3><button class="primary" onclick="openModal(\'entry\')">+ Καταχώρηση</button></div><div class="list">'+html+'</div>'}
async function openModal(type,id){$('modal').classList.remove('hidden');let h='';let item=null;if(id){if(type==='editWork'||type==='workAssignment')item=(await get('works','&project_id='+selectedProjectId)).find(x=>x.id===id);if(type==='editContractor')item=contractors.find(x=>x.id===id)}
if(type==='project')h='<h3>Νέο έργο</h3><form onsubmit="save(event,\'project\')"><input name="name" placeholder="Ονομασία έργου *" required><input name="address" placeholder="Διεύθυνση"><input name="start_date" type="date"><button class="primary full">Δημιουργία</button></form>';
if(type==='contractor'||type==='editContractor'){let c=item||{};h='<h3>'+ (item?'Διόρθωση συνεργείου':'Νέο συνεργείο')+'</h3><form onsubmit="save(event,\''+(item?'editContractor':'contractor')+'\')"><input type="hidden" name="id" value="'+(c.id||'')+'"><input name="company_name" value="'+(c.company_name||'')+'" placeholder="Επωνυμία *" required><input name="contact_name" value="'+(c.contact_name||'')+'" placeholder="Υπεύθυνος"><input name="tax_id" value="'+(c.tax_id||'')+'" placeholder="ΑΦΜ"><input name="phone" value="'+(c.phone||'')+'" placeholder="Τηλέφωνο"><input name="email" value="'+(c.email||'')+'" placeholder="Email"><input name="relationship" value="'+(c.relationship||'')+'" placeholder="Ρόλος"><input name="specialty" value="'+(c.specialty||'')+'" placeholder="Ειδικότητα"><button class="primary full">Αποθήκευση</button></form>'}
if(type==='assignment'){let c=contractors.find(x=>x.id===id)||{};h='<h3>Εργασίες · '+(c.company_name||'')+'</h3><form onsubmit="save(event,\'contractorWorks\')"><input type="hidden" name="contractor_id" value="'+c.id+'">'+projectSelect()+'<div id="assignmentWorks" class="checkList"><div class="notice">Φόρτωση εργασιών…</div></div><button class="primary full">Αποθήκευση εργασιών συνεργείου</button></form>';$('modalBody').innerHTML=h;const ap=$('selProject');const load=async()=>{const ws=await get('works','&project_id='+ap.value),as=await get('assignments','&project_id='+ap.value);const linked=new Set(as.filter(a=>a.contractor_id===c.id).map(a=>a.work_id));$('assignmentWorks').innerHTML=ws.length?ws.map(w=>'<label class="checkRow"><input type="checkbox" name="work_ids" value="'+w.id+'" '+(linked.has(w.id)?'checked':'')+'><span>'+w.name+'</span><small>'+w.status+' · '+(w.progress||0)+'%</small></label>').join(''):'<div class="notice">Δεν υπάρχουν εργασίες στο έργο.</div>'};ap.onchange=load;load()}
if(type==='workAssignment'){const w=item||{};h='<h3>Σύνδεση συνεργείου · '+(w.name||'')+'</h3><form onsubmit="save(event,\'assignment\')"><input type="hidden" name="work_id" value="'+w.id+'">'+projectSelect()+'<select name="contractor_id" required><option value="">Εργολάβος / συνεργείο *</option>'+contractors.map(c=>'<option value="'+c.id+'">'+c.company_name+'</option>').join('')+'</select><input name="contract_value" type="number" step="0.01" min="0" placeholder="Αξία εργασίας (€)"><input name="pricing_unit" placeholder="Μονάδα"><textarea name="notes" placeholder="Σημειώσεις"></textarea><button class="primary full">Αποθήκευση σύνδεσης</button></form>';$('modalBody').innerHTML=h}
if(type==='floor')h='<h3>Νέος όροφος</h3><form onsubmit="save(event,\'floor\')">'+projectSelect()+'<input name="name" placeholder="π.χ. Ισόγειο, 1ος, 2ος *" required><button class="primary full">Προσθήκη</button></form>';
if(type==='work'||type==='editWork'){let w=item||{};h='<h3>'+ (item?'Διόρθωση εργασίας':'Νέα εργασία')+'</h3><form onsubmit="save(event,\''+(item?'editWork':'work')+'\')"><input type="hidden" name="id" value="'+(w.id||'')+'">'+projectSelect('project_id')+'<input name="name" value="'+(w.name||'')+'" placeholder="Περιγραφή εργασίας *" required><input name="category" value="'+(w.category||'')+'" placeholder="Κατηγορία"><select name="status"><option value="planned" '+(w.status==='planned'?'selected':'')+'>Προγραμματισμένη</option><option value="active" '+(w.status==='active'?'selected':'')+'>Σε εξέλιξη</option><option value="done" '+(w.status==='done'?'selected':'')+'>Ολοκληρωμένη</option></select><input name="planned_start" type="date" value="'+(w.planned_start||'')+'"><input name="progress" type="number" min="0" max="100" value="'+(w.progress||0)+'"><button class="primary full">Αποθήκευση</button></form>'}
if(type==='editEntry'){const es=await get('entries');const e=es.find(x=>x.id===id)||{};selectedProjectId=e.project_id||selectedProjectId;const dayEntries=es.filter(x=>x.project_id===e.project_id&&x.entry_date===e.entry_date&&x.contractor_id===e.contractor_id);const ws=await get('works','&project_id='+e.project_id),fs=await get('floors','&project_id='+e.project_id);const workOptions=ws;h='<h3>Διόρθωση ημερήσιας καταχώρησης</h3><form onsubmit="save(event,\'editEntry\')"><input type="hidden" name="project_id" value="'+(e.project_id||'')+'"><input name="entry_date" type="date" value="'+(e.entry_date||'')+'" required><input type="hidden" name="contractor_id" value="'+(e.contractor_id||'')+'"><div style="margin:14px 0 8px;font-weight:800">'+(e.contractor_name||e.company_name||'Εργολάβος / συνεργείο')+'</div><div style="font-size:12px;color:var(--muted);margin-bottom:8px">Όλες οι εργασίες του ίδιου εργολάβου για την ίδια ημέρα</div><div class="editWorks">'+dayEntries.map(x=>'<div class="editWorkRow" data-id="'+x.id+'" style="border:1px solid var(--line);border-radius:14px;padding:12px;margin:10px 0"><select name="work_id"><option value="">Εργασία</option>'+workOptions.map(w=>'<option value="'+w.id+'" '+(w.id===x.work_id?'selected':'')+'>'+w.name+'</option>').join('')+'</select><select name="floor_id"><option value="">Όροφος</option>'+fs.map(f=>'<option value="'+f.id+'" '+(f.id===x.floor_id?'selected':'')+'>'+f.name+'</option>').join('')+'</select><label style="display:flex;align-items:center;gap:6px">Ποσοστό ολοκλήρωσης <input name="progress" type="number" min="0" max="100" step="1" inputmode="numeric" value="'+(x.work_progress??'')+'" style="flex:1"><span>%</span></label></div>').join('')+'</div><input name="people_count" type="number" min="0" value="'+(e.people_count||0)+'" placeholder="Άτομα συνολικά"><input name="machines" value="'+(e.machines||'')+'" placeholder="Μηχανήματα"><textarea name="comments" placeholder="Σχόλια">'+(e.comments||'')+'</textarea><button class="primary full">Αποθήκευση διόρθωσης</button></form>';$('modalBody').innerHTML=h}
if(type==='entry')h='<h3>Ημερήσια καταχώρηση</h3><form id="entryForm" onsubmit="save(event,\'entry\')">'+projectSelect()+'<input name="entry_date" type="date" required><div id="contractorGroups"><div class="contractorGroup" style="border:1px solid #ddd;border-radius:12px;padding:12px;margin:10px 0"><select name="contractor_id" class="entryContractor"><option value="">Εργολάβος / συνεργείο</option>'+contractors.map(c=>'<option value="'+c.id+'">'+c.company_name+'</option>').join('')+'</select><div class="entryWorks"><div class="entryWorkRow" style="display:grid;gap:8px;margin:10px 0"><div class="entryWorkLine"><select name="work_id" class="entryWork"><option value="">Εργασία</option></select><button type="button" class="tile" onclick="newDailyWork(this)">+ Νέα εργασία</button></div><select name="floor_id" class="entryFloor"><option value="">Όροφος</option></select><label style="display:flex;align-items:center;gap:6px">Ποσοστό ολοκλήρωσης <input name="progress" type="number" min="0" max="100" step="1" inputmode="numeric" placeholder="0–100" style="flex:1"><span>%</span></label></div></div><button type="button" class="tile" onclick="addWorkRow(this)">+ Άλλη εργασία</button><div style="border-top:1px solid #ddd;margin-top:12px;padding-top:12px"><input name="people_count" type="number" min="0" placeholder="Άτομα συνολικά"><div class="machinePicker"><input type="hidden" name="machines"><button type="button" class="tile full" onclick="openMachinePicker(this)">🚜 Μηχανήματα <span class="machineSummary">Επιλογή μηχανημάτων</span></button></div><textarea name="comments" placeholder="Σχόλια"></textarea></div></div></div><button type="button" class="tile" onclick="addContractorGroup()">+ Άλλος εργολάβος / συνεργείο</button><button class="primary full">Αποθήκευση ημερήσιας καταχώρησης</button></form>';$('modalBody').innerHTML=h;const ep=$('selProject');if(type==='entry'){const loadGroups=async()=>{const as=await get('assignments','&project_id='+ep.value),ws=await get('works','&project_id='+ep.value),fs=await get('floors','&project_id='+ep.value);document.querySelectorAll('.contractorGroup').forEach(group=>{const contractor=group.querySelector('.entryContractor').value;const linked=new Set(as.filter(a=>a.contractor_id===contractor).map(a=>a.work_id));const allowed=contractor?ws.filter(w=>linked.has(w.id)):[];group.querySelectorAll('.entryWorkRow').forEach(row=>{const current=row.querySelector('.entryWork').value;row._entryWorks=allowed;row.querySelector('.entryWork').innerHTML='<option value="">Εργασία</option>'+allowed.map(w=>'<option value="'+w.id+'" '+(w.id===current?'selected':'')+'>'+w.name+'</option>').join('');row.querySelector('.entryFloor').innerHTML='<option value="">Όροφος</option>'+fs.map(f=>'<option value="'+f.id+'">'+f.name+'</option>').join('')})})};document.querySelectorAll('.entryContractor').forEach(s=>s.onchange=loadGroups);window.addWorkRow=btn=>{const group=btn.closest('.contractorGroup'),box=group.querySelector('.entryWorks'),source=box.querySelector('.entryWorkRow:last-child'),row=source.cloneNode(true);row.querySelector('.entryWork').selectedIndex=0;row.querySelector('.entryFloor').selectedIndex=0;row.querySelector('[name="progress"]').value='';row._entryWorks=source._entryWorks||[];box.appendChild(row);loadGroups()};window.addContractorGroup=()=>{const box=$('contractorGroups'),source=box.querySelector('.contractorGroup:last-child'),group=source.cloneNode(true);group.querySelector('.entryContractor').selectedIndex=0;group.querySelectorAll('.entryWorkRow').forEach((row,i)=>{if(i>0)row.remove();row.querySelector('.entryWork').selectedIndex=0;row.querySelector('.entryFloor').selectedIndex=0;row.querySelector('[name="progress"]').value=''});group.querySelector('[name="people_count"]').value='';group.querySelector('[name="machines"]').value='';group.querySelector('[name="comments"]').value='';box.appendChild(group);group.querySelector('.entryContractor').onchange=loadGroups;loadGroups()};ep.onchange=()=>{selectedProjectId=ep.value;loadGroups()};loadGroups()}}
async function newDailyWork(btn){const row=btn.closest('.entryWorkRow'),group=btn.closest('.contractorGroup'),contractor_id=group?.querySelector('.entryContractor')?.value;if(!contractor_id){alert('Πρώτα επίλεξε εργολάβο / συνεργείο.');return}const name=prompt('Όνομα νέας εργασίας');if(!name||!name.trim())return;const pid=$('selProject').value;const r=await send({type:'work',project_id:pid,name:name.trim(),status:'active',progress:0},'POST');if(r.error){alert(r.error);return}const w=r;const a=await send({type:'assignment',project_id:pid,contractor_id,work_id:w.id,contract_value:0},'POST');if(a.error){alert(a.error);return}const box=group.querySelector('.entryWorks'),newRow=row.cloneNode(true);newRow.querySelector('.entryWork').innerHTML='<option value="">Εργασία</option><option value="'+w.id+'">'+w.name+'</option>';newRow.querySelector('.entryWork').value=w.id;newRow.querySelector('.entryFloor').value=row.querySelector('.entryFloor').value||'';newRow.querySelector('[name="progress"]').value='';newRow._entryWorks=[...(row._entryWorks||[]),w];row.insertAdjacentElement('afterend',newRow);alert('Η νέα εργασία δημιουργήθηκε, συνδέθηκε με τον εργολάβο και προστέθηκε ως ξεχωριστή εργασία της ίδιας ημέρας.');}
function openMachinePicker(btn){const input=btn.closest('.contractorGroup')?.querySelector('[name="machines"]')||btn.closest('.machinePicker')?.querySelector('[name="machines"]');if(!input)return;document.querySelector('.machinePickerPopup')?.remove();const current=(input.value||'').split(',').map(x=>x.trim()).filter(Boolean);const options=['Τσαπάκι','Bobcat','Φορτηγό','Κάδος / Skip','Γερανός','Ανυψωτικό','Άλλο'];const box=document.createElement('div');box.className='machinePickerPopup';box.innerHTML='<div class="quickPopup" style="z-index:10001"><button class="close" onclick="this.closest(\'.machinePickerPopup\').remove()">×</button><div class="eyebrow">ΗΜΕΡΗΣΙΑ ΚΑΤΑΧΩΡΗΣΗ</div><h3>Μηχανήματα</h3><div class="checkList">'+options.map(o=>'<label class="checkRow"><input type="checkbox" value="'+o+'" '+(current.includes(o)?'checked':'')+'><span>'+o+'</span></label>').join('')+'</div><input id="customMachine" placeholder="Άλλο μηχάνημα"><button class="primary full" onclick="saveMachinePicker(this)">Αποθήκευση</button></div>';document.body.appendChild(box);box._target=input}function saveMachinePicker(btn){const pop=btn.closest('.machinePickerPopup'),input=pop._target,vals=[...pop.querySelectorAll('input[type="checkbox"]:checked')].map(x=>x.value);const custom=pop.querySelector('#customMachine').value.trim();if(custom)vals.push(custom);input.value=[...new Set(vals)].join(', ');const summary=input.closest('.machinePicker')?.querySelector('.machineSummary');if(summary)summary.textContent=input.value||'Επιλογή μηχανημάτων';pop.remove()}async function save(ev,type){ev.preventDefault();if(type==='editEntry'){const f=ev.target;const pid=f.querySelector('[name="project_id"]').value,date=f.querySelector('[name="entry_date"]').value,cid=f.querySelector('[name="contractor_id"]').value;const ws=await get('works','&project_id='+pid);for(const row of [...f.querySelectorAll('.editWorkRow')]){const work_id=row.querySelector('[name="work_id"]').value||null;if(!work_id)continue;const work=ws.find(w=>w.id===work_id);const d={type:'entry',id:row.dataset.id,project_id:pid,entry_date:date,contractor_id:cid,floor_id:row.querySelector('[name="floor_id"]').value||null,work_id,work_description:work?work.name:null,progress:row.querySelector('[name="progress"]').value||'',people_count:f.querySelector('[name="people_count"]').value||0,machines:f.querySelector('[name="machines"]').value||null,comments:f.querySelector('[name="comments"]').value||null};const r=await send(d,'PUT');if(r.error){alert(r.error);return}}closeModal();await refresh();openSection('calendar');return}if(type==='entry'){const f=ev.target,pid=f.querySelector('[name="project_id"]').value,date=f.querySelector('[name="entry_date"]').value;for(const group of [...f.querySelectorAll('.contractorGroup')]){const contractor_id=group.querySelector('[name="contractor_id"]').value;if(!contractor_id)continue;const people_count=group.querySelector('[name="people_count"]').value||0,machines=group.querySelector('[name="machines"]').value||null,comments=group.querySelector('[name="comments"]').value||null;for(const row of [...group.querySelectorAll('.entryWorkRow')]){const work_id=row.querySelector('[name="work_id"]').value||null;if(!work_id)continue;const work=(row._entryWorks||[]).find(w=>w.id===work_id);if(work)await send({type:'assignment',project_id:pid,contractor_id,work_id,contract_value:0},'POST');const r=await send({type:'entry',project_id:pid,entry_date:date,contractor_id,floor_id:row.querySelector('[name="floor_id"]').value||null,work_description:work?work.name:null,work_id,progress:row.querySelector('[name="progress"]').value,people_count,machines,comments},'POST');if(r.error){alert(r.error);return}}}closeModal();await refresh();openSection('calendar');return}if(type==='contractorWorks'){const f=ev.target;const work_ids=[...f.querySelectorAll('input[name="work_ids"]:checked')].map(x=>x.value);const d={type:'contractor_work_links',project_id:f.querySelector('[name="project_id"]').value,contractor_id:f.querySelector('[name="contractor_id"]').value,work_ids};const r=await send(d,'PUT');if(r.error){alert(r.error);return}closeModal();await refresh();openSection('contractors');return}const d={type:type==='editWork'?'work':type==='editContractor'?'contractor':type};new FormData(ev.target).forEach((v,k)=>d[k]=v);if(type==='work'||type==='editWork'||type==='floor'||type==='assignment')d.project_id=d.project_id||selectedProjectId;const r=await send(d,type==='editWork'||type==='editContractor'?'PUT':'POST');if(r.error){alert(r.error);return}closeModal();await refresh();openSection(type.includes('contractor')||type==='assignment'?'contractors':type.includes('work')?'works':type==='floor'?'floors':type==='project'?'projects':'calendar')}
async function deleteWork(id){if(!confirm('Να διαγραφεί αυτή η εργασία;'))return;await send({type:'work',id},'DELETE');worksView()}
async function removeContractor(id){if(!confirm('Να διαγραφεί αυτός ο εργολάβος / συνεργείο;'))return;const r=await send({type:'contractor',id},'DELETE');if(r.error){alert(r.error);return}await refresh();contractorsView()}
async function deleteEntry(id){if(!confirm('Να αφαιρεθεί αυτή η ημερήσια καταχώρηση;'))return;const r=await send({type:'entry',id},'DELETE');if(r.error){alert(r.error);return}closeQuickPopup();await refresh();openSection('calendar')}
function closeModal(){$('modal').classList.add('hidden')}
window.openSection=openSection;window.openModal=openModal;window.closeModal=closeModal;window.save=save;window.deleteWork=deleteWork;window.openQuickPopup=openQuickPopup;window.openMachinePicker=openMachinePicker;window.saveMachinePicker=saveMachinePicker;refresh();$ tail -f comments.log
Comments [0]
// no comments yet