;
}
/* Föranalys (kind:"analysis"): bara admin. Hopfälld som standard. Claude Code-texter börjar med en egen "## Föranalys · …"-rad som döljs tills man visar den. */
/* v742 (André 28/9: "Vi vill bara se senaste. Hisotriken kan visas om man klickar i något så man inte väljer fel version när man felsöker"):
tråden visar bara den senaste föranalysen; äldre versioner ligger under Visa tidigare versioner och är märkta Ersatt. */
function TkAnalysis({m,onDelete,history,old}){
const TK=window.HemsTickets, [open,setOpen]=React.useState(false), [raw,setRaw]=React.useState(false), [hOpen,setHOpen]=React.useState(false);
const hs=Array.isArray(history)?history:[];
const cc=m.source==='claude-code'; let body=m.text||'', head=null;
if(cc){ const mm=body.match(/^\s*##\s*Föranalys[^\n]*(\n|$)/); if(mm){ head=mm[0].trim(); body=body.slice(mm[0].length); } }
const who=m.author||'admin', title=cc?`Föranalys · Claude Code · körd av ${who} ${TK.fmtTime(m.at)}`:`Föranalys · inklistrad av ${who} ${TK.fmtTime(m.at)}`;
const pv=body.split('\n').map(x=>x.replace(/^[#>\s|*-]+|\|/g,' ').trim()).filter(x=>x&&!/^[-:\s]+$/.test(x)).join(' · ').slice(0,220);
const del=m.deleted;
return
{del&&
Borttagen · {del.by} {TK.fmtTime(del.at)}
}
{!open&&
setOpen(true)}>{pv}
}
{open&&
{head&&
{raw&&{head}}
}
}
{old?'Äldre version. Felsök med den senaste.':'Bara admin ser föranalysen.'}{hs.length>0&&}{onDelete&&!del&&}
{hOpen&&hs.length>0&&
{[...hs].reverse().map(x=>)}
}
;
}
/* Förslagets status (S2): Mottaget, Planerat, Byggt, Avböjt. prefix = "Förslag · " för Driftcentralen, där fel och förslag står i samma lista. */
/* v741 Larmraden i ett ärende (Utred i Varningar): larmet som det stod när utredningen började. Bara admin ser den. */
function TkAlarm({m}){
const TK=window.HemsTickets, a=m.alarm||{}, ws=Array.isArray(a.warnings)?a.warnings:[], off=!!a.status&&a.status!=='connected';
const ms=(x)=>{ const n=Number(x); return !Number.isFinite(n)||n<=0?0:n<1e12?n*1000:n; };
/* v748 (André 29/9): rött när larmraden har en kritisk varning, som Kritisk i Varningar. */ const cr=ws.some(w=>w&&w.severity==='critical');
return
LarmFrån Varningar{[m.author?`Utrett av ${m.author}`:null,TK.fmtTime(m.at)].filter(Boolean).join(' · ')}
{off&&
Ingen data{[a.message||'Ingen data tas emot',a.age_minutes!=null&&Number.isFinite(Number(a.age_minutes))?`senaste data för ${TK.fmtDur(Number(a.age_minutes)*60000)} sedan`:null].filter(Boolean).join(' · ')}
}
{ws.map((w,i)=>
{w.severity==='critical'?'Kritisk':'Varning'}{w.text}{ms(w.since)>0?` · först sedd ${TK.fmtTime(ms(w.since))}`:''}
)}
{!off&&!ws.length&&m.text&&
{m.text}
}
Syns aldrig för kunden.
;
}
function TkIdeaTag({t,prefix}){ const TK=window.HemsTickets; if(!t||t.type!=='forslag') return null; const k=t.idea_status, l=TK.IDEA[k]; return {prefix&&l?'Förslag · ':''}{l||'Förslag'}; }
/* view 'customer' | 'admin'. Interna rader visas bara för admin. Sidan (höger/vänster) styrs av CSS per vy. */
function TkMessages({t,view,onDecide,busy,onZoom,onDelete}){
const TK=window.HemsTickets, ref=React.useRef(), [openSys,setOpenSys]=React.useState({});
React.useEffect(()=>{ const el=ref.current; if(!el) return; if(view==='admin') el.scrollTop=el.scrollHeight; },[t.id,t.messages.length,view]);
/* kind:'deletion_log' = backendens eventuella revisionsrad vid borttagning; visas aldrig (André 25/9: bara utgråning). */
/* Visningsloggar ("X öppnade dina inställningar/mätvärden", kind:'view_log') visas inte (André 25/9: spammar varje gång admin tittar). */
const items=t.messages.filter(m=>!TK.isHiddenRow(m)&&(view==='admin'||(m.from!=='internal'&&!m.deleted)));
const adm=view==='admin';
/* v742: bara den senaste föranalysen i tråden (den senaste som inte är borttagen); de andra följer med den som historik. */
const ans=adm?items.filter(m=>m.kind==='analysis'):[], anCur=[...ans].reverse().find(m=>!m.deleted)||ans[ans.length-1]||null, anOld=ans.filter(m=>m!==anCur);
const rg=(h,a)=>{ const n=parseInt(String(h).slice(1),16); return `rgba(${n>>16},${n>>8&255},${n&255},${a})`; };
const delBtn=(m)=>adm&&onDelete&&!m.deleted&&(m.from==='admin'||m.from==='internal')?:null;
const delTag=(m)=>m.deleted?Borttaget · {m.deleted.by} {TK.fmtTime(m.deleted.at)} · kunden ser inte detta:null;
const atts=(m)=>(m.attachments||[]).map((a,i)=>);
/* v711 (André 27/9): namnband i skribentens färg. Internt ligger bandet på mörk botten så färgen inte blandas med den gula panelen. Gul är aldrig en adminfärg (TK.okColor). */
/* v719 (André 27/9): internt band helgult med namnet i mörk text och en prick i skribentens färg; till kund som förut i skribentens färg. */
const band=(m,intl)=>{ const c=TK.okColor(m.author_color); if(intl) return
;
};
/* v711: interna lappar i följd (admin) i en gul panel över hela bredden. Nytt band när skribenten byts; nästa lapp från samma person står under bandet med sin tid. */
const akey=(x)=>x.author_id||x.author||'';
const intUnit=(rows)=>
{rows.map((m,i)=>i>0&&akey(rows[i-1])===akey(m)
?
{m.text}{atts(m)}{delTag(m)}
{TK.fmtTime(m.at)}{delBtn(m)}
:
{band(m,true)}
{m.text}{atts(m)}{delTag(m)}
)}
;
const isInt=(m)=>adm&&m.from==='internal'&&m.kind!=='analysis'&&m.kind!=='alarm';
/* S10 (backend 26/9): två eller fler systemrader i följd fälls ihop till "N händelser" som går att fälla ut. En ensam systemrad visas som den är. Kund, admin och förslag visas alltid. */
const blocks=[]; items.forEach(m=>{ const b=blocks[blocks.length-1]; if(m.from==='system'&&b&&b.sys) b.rows.push(m); else if(isInt(m)&&b&&b.int) b.rows.push(m); else blocks.push({sys:m.from==='system',int:isInt(m),rows:[m]}); });
return