131 lines
5.9 KiB
JavaScript
131 lines
5.9 KiB
JavaScript
// Display a fresh, customer-scoped order overview after login or session recovery.
|
|
var CustomerOverview = {
|
|
create(options){
|
|
const panel=document.getElementById('customer-overview');
|
|
const title=document.getElementById('customer-overview-title');
|
|
const summary=document.getElementById('customer-overview-summary');
|
|
const body=document.getElementById('customer-overview-body');
|
|
let user=null;
|
|
let overview=null;
|
|
let failure='';
|
|
let loading=false;
|
|
let requestVersion=0;
|
|
let controller=null;
|
|
panel.hidden=true;
|
|
|
|
function element(tag,className,text){
|
|
const node=document.createElement(tag);
|
|
if(className) node.className=className;
|
|
if(text !== undefined) node.textContent=text;
|
|
return node;
|
|
}
|
|
|
|
function queryButton(label,question,className){
|
|
const button=element('button',className,label);
|
|
button.type='button';
|
|
button.onclick=function(){
|
|
panel.open=false;
|
|
options.onQuery(question);
|
|
};
|
|
return button;
|
|
}
|
|
|
|
function render(){
|
|
title.textContent=options.text('overviewTitle');
|
|
body.innerHTML='';
|
|
panel.hidden=!user;
|
|
if(!user){summary.textContent='';return;}
|
|
if(!overview){
|
|
const key=loading ? 'overviewLoading' : failure==='unlinked' ? 'overviewUnlinked' : 'overviewFailed';
|
|
summary.textContent=options.text(key);
|
|
}else{
|
|
summary.textContent=options.text('overviewProcessing')+' '+overview.processing+' · '+options.text('overviewExceptions')+' '+overview.exceptions;
|
|
const metrics=element('div','overview-metrics');
|
|
for(const [key,count,question] of [
|
|
['overviewTotal',overview.total,options.text('overviewAllQuery')],
|
|
['overviewProcessing',overview.processing,options.text('overviewProcessingQuery')],
|
|
['overviewExceptions',overview.exceptions,options.text('exceptionQuery')]
|
|
]){
|
|
const button=queryButton('',question,'overview-metric');
|
|
button.append(element('strong','',String(count)),element('span','',options.text(key)));
|
|
metrics.appendChild(button);
|
|
}
|
|
body.appendChild(metrics);
|
|
const stages=element('div','overview-stages');
|
|
const labels=options.text('overviewStageLabels');
|
|
for(const stage of overview.stages){
|
|
stages.appendChild(element('span','',labels[stage.code]+' '+stage.count));
|
|
}
|
|
body.appendChild(stages);
|
|
const orders=element('div','overview-orders');
|
|
for(const order of overview.orders){
|
|
const button=element('button','overview-order');
|
|
button.type='button';
|
|
const reference=order.inbound_code || order.order_no;
|
|
button.appendChild(element('strong','overview-reference',reference));
|
|
button.appendChild(element('span','overview-stage',labels[order.stage]));
|
|
if(order.exception_reason){
|
|
button.classList.add('has-exception');
|
|
button.appendChild(element('span','overview-reason',order.exception_reason));
|
|
}
|
|
button.onclick=function(){options.onLookup(order,button);};
|
|
orders.appendChild(button);
|
|
}
|
|
if(!overview.orders.length) orders.appendChild(element('p','overview-empty',options.text('overviewEmpty')));
|
|
body.appendChild(orders);
|
|
}
|
|
const refresh=element('button','overview-refresh',options.text(loading ? 'overviewLoading' : 'overviewRefresh'));
|
|
refresh.type='button';
|
|
refresh.disabled=loading;
|
|
refresh.onclick=refreshOverview;
|
|
body.appendChild(refresh);
|
|
}
|
|
|
|
async function refreshOverview(){
|
|
if(!user || loading) return;
|
|
const token=++requestVersion;
|
|
const customerId=user.id;
|
|
controller=new AbortController();
|
|
const activeController=controller;
|
|
const timer=setTimeout(()=>activeController.abort(),10000);
|
|
loading=true;
|
|
failure='';
|
|
render();
|
|
try{
|
|
const response=await options.fetch('/api/customer/session?overview=1',{signal:activeController.signal});
|
|
const result=await response.json();
|
|
if(token!==requestVersion || !user || user.id!==customerId) return;
|
|
if(!response.ok || !result.ok) throw new Error('Overview unavailable');
|
|
if(!result.authenticated || !result.user || result.user.id!==customerId){
|
|
setUser(null);
|
|
if(options.onExpired) options.onExpired();
|
|
return;
|
|
}
|
|
overview=result.overview || null;
|
|
failure=result.overview_error || (overview ? '' : 'unavailable');
|
|
}catch(error){
|
|
if(token===requestVersion){overview=null;failure='unavailable';}
|
|
}finally{
|
|
clearTimeout(timer);
|
|
if(token===requestVersion){controller=null;loading=false;render();}
|
|
}
|
|
}
|
|
|
|
function setUser(next){
|
|
if(user && next && user.id===next.id) return;
|
|
requestVersion+=1;
|
|
if(controller) controller.abort();
|
|
controller=null;
|
|
user=next;
|
|
overview=null;
|
|
failure='';
|
|
loading=false;
|
|
panel.open=Boolean(user);
|
|
render();
|
|
if(user) refreshOverview();
|
|
}
|
|
|
|
return {setUser:setUser,render:render};
|
|
}
|
|
};
|