Files
SGF/public/geography-workspace.mjs
2026-11-04 09:37:20 +02:00

16 lines
7.5 KiB
JavaScript

const root=document.querySelector('#geography'),status=document.querySelector('#status'),current=document.querySelector('#current'),options=document.querySelector('#options'),paging=document.querySelector('#paging'),trail=document.querySelector('#trail'),chosen=document.querySelector('#chosen'),decision=document.querySelector('#decision'),impact=document.querySelector('#impact'),confirm=document.querySelector('#confirm');const kind=root.dataset.kind,id=Number(root.dataset.entityId),segment=id===0,entityPath='/entities/'+kind+'/'+id;let state=null,picked=null,path=[],version=null,key=crypto.randomUUID(),cut=null,generation=0,busy=false,choiceRevision=0,reportAfter=0,decided=false;
const n=(tag,text)=>{const e=document.createElement(tag);if(text!==undefined)e.textContent=text;return e;};const labels=['Província','Distrito','Posto administrativo','Localidade','Bairro urbano','Aldeia/quarteirão'],issues={missing_designation:'Designação ausente',duplicate_path:'Caminho de códigos repetido',parent_record_missing:'Ascendente sem registo correspondente',parent_record_ambiguous:'Ascendente com mais de um registo',ancestry_requires_review:'Hierarquia por conferir'};
const describe=r=>r?(r.designation??'Designação ausente')+' · '+Object.values(r.codes).filter(Boolean).join(' / '):'Localização por indicar';
async function api(url,data){const r=await fetch('/api/geography'+url,{method:data===undefined?'GET':'POST',headers:{Accept:'application/json','X-SGF-Context':root.dataset.context,'X-CSRF-TOKEN':root.dataset.csrf,...(data===undefined?{}:{'Content-Type':'application/json'})},body:data===undefined?undefined:JSON.stringify(data)});const d=await r.json();if(!r.ok)throw Error(Object.values(d.errors||{}).flat().join(' ')||d.message||'Não foi possível concluir.');return d;}
function invalidate(){choiceRevision++;cut=null;key=crypto.randomUUID();confirm.disabled=true;impact.hidden=true;}
function choose(record){picked=record;decided=record!==null;invalidate();chosen.textContent='Escolha explícita: '+describe(record);}
function breadcrumbs(){trail.replaceChildren();for(const [i,r]of path.entries()){const b=n('button',r.designation);b.onclick=()=>{path=path.slice(0,i+1);choose(r);breadcrumbs();browse(r.key);};trail.append(b);}}
async function browse(parent=null,page=1,search=''){const turn=++generation;try{const q=new URLSearchParams({page:String(page)});if(parent)q.set('parent',parent);if(search)q.set('search',search);const d=await api('/catalogue?'+q);if(turn!==generation)return;if(version&&version!==d.version){choose(null);path=[];breadcrumbs();}version=d.version;options.replaceChildren(n('p','Versão da fonte: '+version.slice(0,12)));for(const r of d.records.data){const block=n('article'),b=n('button',labels[r.level-1]+' · '+describe(r));b.disabled=!r.selectable;b.onclick=()=>{if(search)path=[];path.push(r);choose(r);breadcrumbs();browse(r.key);};block.append(b);for(const issue of r.issues)block.append(n('p',issues[issue]??issue));options.append(block);}if(!d.records.data.length)options.append(n('p','Sem outros registos neste nível ou nesta pesquisa.'));paging.replaceChildren(n('span','Página '+d.records.current_page+' de '+d.records.last_page));for(const [enabled,p,label]of [[page>1,page-1,'Anterior'],[page<d.records.last_page,page+1,'Seguinte']])if(enabled){const b=n('button',label);b.onclick=()=>browse(parent,p,search);paging.append(b);}}catch(e){status.textContent=e.message;}}
async function loadHistory(page=1){try{const d=await api(entityPath+'/history?page='+page),target=document.querySelector('#history');target.replaceChildren(n('h2','Histórico da localização'));for(const h of d.data){const row=n('details');row.append(n('summary',h.created_at+' · autor #'+h.actor_id+' · '+h.reason),n('pre',JSON.stringify({anterior:h.before,posterior:h.after},null,2)));target.append(row);}target.append(n('p','Página '+d.current_page+' de '+d.last_page));for(const [enabled,p,label]of [[page>1,page-1,'Anterior'],[page<d.last_page,page+1,'Seguinte']])if(enabled){const b=n('button',label);b.onclick=()=>loadHistory(p);target.append(b);}}catch(e){status.textContent=e.message;}}
async function load(){try{state=await api(entityPath);version=state.catalogue_version;current.replaceChildren(n('h2',state.entity.name),n('p',describe(state.location?.selection)),n('p','Revisão da localização: '+(state.location?.revision??0)));const link=n('a','Consultar localizações deste tipo por território');link.href='/geography/segments/'+kind;current.append(link);decision.hidden=!state.entity.editable;await loadHistory();}catch(e){status.textContent=e.message;}}
document.querySelector('#search').onsubmit=e=>{e.preventDefault();browse(null,1,new FormData(e.target).get('search').trim());};document.querySelector('#restart').onclick=()=>{path=[];choose(null);breadcrumbs();browse();};document.querySelector('#clear').onclick=()=>{choose(null);decided=true;chosen.textContent='Decisão explícita: deixar localização por indicar.';};document.querySelector('#reason').oninput=invalidate;
document.querySelector('#prepare').onsubmit=async e=>{e.preventDefault();if(busy||!state)return;if(!decided){status.textContent='Seleccione um territorio ou indique explicitamente que fica por indicar.';return;}busy=true;const observed=choiceRevision;try{const p=await api(entityPath+'/preview',{operation_key:key,revision:state.revision,catalogue_version:version,territory_key:picked?.key??null,reason:document.querySelector('#reason').value});if(observed!==choiceRevision)return;cut=p.preview;impact.replaceChildren(n('h3','Impacto antes da confirmação'),n('p','Registo: '+p.impact.entity),n('p','Anterior: '+describe(p.impact.before)),n('p','Posterior: '+describe(p.impact.after)),n('p','Fundamento: '+p.impact.reason));impact.hidden=false;confirm.disabled=false;}catch(e){status.textContent=e.message;}finally{busy=false;}};
confirm.onclick=async()=>{if(busy||!cut)return;busy=true;confirm.disabled=true;try{await api(entityPath+'/confirm',{preview:cut});status.textContent='Localização registada com histórico.';invalidate();await load();}catch(e){status.textContent=e.message+' Se perdeu a resposta, pode repetir a mesma confirmação sem duplicar a operação.';confirm.disabled=false;}finally{busy=false;}};
async function report(after=0){try{const q=new URLSearchParams({after:String(after)});if(picked)for(const [field,value]of Object.entries(picked.codes))if(value)q.set(field,value);const d=await api('/segments/'+kind+'?'+q),target=document.querySelector('#results');target.hidden=false;target.replaceChildren(n('h2','Localizações indicadas para segmentação'),n('p','Filtro: '+describe(picked)));for(const row of d.data){const article=n('article'),link=n('a',row.name);link.href='/geography/'+kind+'/'+row.entity_id;article.append(link,n('p',describe(row.location)));target.append(article);}if(!d.data.length)target.append(n('p','Sem localizações indicadas que correspondam a este filtro.'));if(d.next_after!==null){const next=n('button','Seguinte');next.onclick=()=>report(d.next_after);target.append(next);}if(after>0){const first=n('button','Primeira página');first.onclick=()=>report();target.append(first);}reportAfter=after;}catch(e){status.textContent=e.message;}}
if(segment){current.replaceChildren(n('h2','Segmentação territorial'),n('p','Mostra apenas localizações explicitamente indicadas e acessíveis neste contexto.'));document.querySelector('#history').hidden=true;const apply=n('button','Aplicar território escolhido'),all=n('button','Consultar todas as localizações indicadas');apply.onclick=()=>report();all.onclick=()=>{choose(null);report();};current.append(apply,all);report();}else load();browse();