/* arenden.jsx — kundens supportärenden: lista, nytt ärende, chatt med förslag att godkänna. Rutter i hash: #/ (lista) · #/ny[?kat=…&typ=forslag] · #/t/. På dator två spalter (lista + ärende), på mobil en vy i taget. Data via tickets.js; delade bitar i tickets-ui.jsx. */ const TK=window.HemsTickets; const CATS=TK.CATEGORIES; /* S3 (backend 26/9): "Har du läst …?" när kategorin är vald, före textfältet. Avsnitten i help-content.json valda efter vad de tar upp: Felsökning = sensorer och data som saknas, Vanliga frågor = varför HEMS gör som det gör, bil, planer och villkor, Kommandon = flöden för bil och laster. */ const HELP_FOR={'Styrning':[['troubleshooting','Felsökning'],['faq','Vanliga frågor']],'Data saknas':[['troubleshooting','Felsökning']],'Bil':[['faq','Vanliga frågor'],['commands','Kommandon']],'Laster':[['commands','Kommandon']],'Inställningar':[['faq','Vanliga frågor']],'Betalning':[['faq','Vanliga frågor']],'Annat':[['faq','Vanliga frågor']]}; function route(h){ const s=(h||'#/').replace(/^#/,''); let m; if((m=s.match(/^\/ny(?:\?(.*))?$/))){ const q=new URLSearchParams(m[1]||''); return {p:'ny',kat:q.get('kat'),typ:q.get('typ')==='forslag'?'forslag':'fel'}; } if((m=s.match(/^\/t\/(.+)$/))) return {p:'t',id:decodeURIComponent(m[1])}; return {p:'list'}; } function TopBar({title,sub,back}){ return <>
Gravio HEMS

{title}

{sub&&
{sub}
}
{/* Krav 5 (5/10): demoläget som band över hela bredden, inte en liten tagg. Visas bara med ?tickets=mock. */} {TK.isMock()&&
Demoläge · exempeldata i din webbläsare. Inget här är dina riktiga ärenden.
} ; } function statusTag(t){ /* S2: förslag visar sin status (Mottaget, Planerat, Byggt, Avböjt) i stället för ärendets läge. */ if(t.type==='forslag') return {t.unread_customer>0&&Nytt svar}; if(t.unread_customer>0) return Nytt svar; if(t.status==='closed') return Avslutat; const w=TK.waitingOn(t); if(w==='new') return Mottaget; if(w==='customer') return Väntar på dig; return Hos supporten; } function TicketList({data,active}){ const open=data.tickets.filter(t=>t.status!=='closed'), closed=data.tickets.filter(t=>t.status==='closed'); return
Supportkod
{data.support_code||'—'}
Rapportera problem
{data.tickets.length===0&&
Inga ärenden ännu.
} {[...open,...closed].map(t=> {t.id}{t.subject}{TK.rowSub(t)} {statusTag(t)} )}
; } function NewTicket({kat,typ,setTyp,supportCode,onCreated}){ const [cat,setCat]=React.useState(kat&&CATS.includes(kat)?kat:null); const [text,setText]=React.useState(''), [files,setFiles]=React.useState([]), [busy,setBusy]=React.useState(false), [err,setErr]=React.useState(null); const fileRef=React.useRef(); const idea=typ==='forslag'; /* F1: När uppstod problemet? Förvalt nu, max nu, min 14 dygn bakåt. Skickas som occurred_at (epok-ms). Inte för förslag. */ const loc=(ts)=>{ const d=new Date(ts), p=n=>String(n).padStart(2,'0'); return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`; }; const [t0]=React.useState(()=>Date.now()), [occ,setOcc]=React.useState(()=>loc(t0)); const occTs=occ?new Date(occ).getTime():NaN, occOk=Number.isFinite(occTs)&&occTs<=Date.now()+6e4&&occTs>=Date.now()-14*864e5; const ok=cat&&text.trim().length>=10&&(idea||occOk); /* Valen skrivs in i adressen utan ny historikpost, så att Tillbaka från Hjälp landar på samma val. */ const keep=(c,ty)=>{ try{ const q=new URLSearchParams(); if(c) q.set('kat',c); if(ty==='forslag') q.set('typ','forslag'); const s=q.toString(); history.replaceState(history.state,'','#/ny'+(s?'?'+s:'')); }catch(_){} }; const pickCat=(c)=>{ setCat(c); keep(c,typ); }; const pickTyp=(v)=>{ setTyp(v); keep(cat,v); }; const add=async(e)=>{ const fs=[...e.target.files]; e.target.value=''; const out=[]; for(const f of fs){ try{ out.push(await TK.fileToDataUrl(f)); }catch(er){ setErr(er.message); } } setFiles(x=>[...x,...out].slice(0,4)); }; const send=async()=>{ if(!ok) return; setBusy(true); setErr(null); try{ const b={type:typ,category:cat,text:text.trim(),attachments:files,page_build:TK.build}; if(!idea) b.occurred_at=Math.min(occTs,Date.now()); const t=await TK.create(b); onCreated(t); }catch(er){ setErr(er&&(er.status===422||er.status===403)&&er.message&&!/^HTTP /.test(er.message)?er.message:`Kunde inte skicka ${idea?'förslaget':'ärendet'} (${er.message||'fel'}). Försök igen.`); setBusy(false); } }; const help=cat&&HELP_FOR[cat]; return <>
{/* S1 (backend 26/9): fel eller förslag överst, skickas som type. Standard är fel. */}
{[['fel','Fel eller problem'],['forslag','Förslag på förbättring']].map(([v,l])=>)}
Vad gäller det?
{CATS.map(c=>)}
{help&&
Har du läst {help.map(([id,l],i)=>{i>0?' och ':''}{l})} i Hjälp?
} {!idea&&}