- Introduced tests for mail templates including ranking, rendering, and attachment handling. - Added tests for time billing selection and approval processes, ensuring proper validation and state management. - Implemented tests for case types in JavaScript, focusing on loading and editing functionality. - Created tests for contact mail AI integration, ensuring proper parsing and error handling. - Developed tests for dark theme CSS contrast and loading order. - Added tests for invoice fee service, covering various scenarios including exemptions and invalid inputs. - Implemented vendor resource tests, ensuring proper file handling and query validation.
34 lines
7.0 KiB
JavaScript
34 lines
7.0 KiB
JavaScript
/* Shared case file tab; no content is interpreted as HTML. */
|
|
(()=>{'use strict';
|
|
async function init(){
|
|
const tabs=document.getElementById('caseTabs'),panes=document.getElementById('caseTabsContent'),id=location.pathname.match(/\/sag\/(\d+)/)?.[1];if(!tabs||!panes||!id)return;
|
|
const base=`/api/v1/sag/${id}/file-browser`;
|
|
let files=[],selected=null,page=1,pages=1,token=0;const cache=new Map();
|
|
async function api(url){const r=await fetch(url,{credentials:'include'});if(!r.ok)throw new Error('Filen kunne ikke læses');return r.json();}
|
|
const nav=document.createElement('li');nav.className='nav-item';nav.hidden=true;nav.innerHTML='<button class="nav-link" id="case-files-tab" type="button" data-bs-toggle="tab" data-bs-target="#case-files" role="tab" aria-controls="case-files">Filer</button>';tabs.append(nav);
|
|
const pane=document.createElement('div');pane.className='tab-pane fade';pane.id='case-files';pane.setAttribute('role','tabpanel');pane.setAttribute('aria-labelledby','case-files-tab');
|
|
pane.innerHTML='<div class="card case-file-browser"><div class="card-header"><h6 class="mb-0">Filer på sagen</h6></div><div class="card-body"><div class="d-flex flex-wrap gap-2 mb-3"><input type="search" class="form-control form-control-sm" data-search placeholder="Søg filnavn eller tekst i TXT/PDF" aria-label="Søg filer"><select class="form-select form-select-sm" data-type aria-label="Filtype"><option value="">Alle typer</option></select><select class="form-select form-select-sm" data-sort aria-label="Sortering"><option value="name">Navn</option><option value="type">Filtype</option></select></div><p class="small text-muted" data-status role="status"></p><div class="case-file-columns"><div data-list class="case-file-list"></div><div class="case-file-preview" tabindex="0" aria-label="Filvisning. Venstre og højre pil skifter PDF-side; op og ned skifter fil."><div class="d-flex gap-2 align-items-center mb-2"><button type="button" class="btn btn-sm btn-outline-secondary" data-prev aria-label="Forrige side">←</button><span data-page class="small"></span><button type="button" class="btn btn-sm btn-outline-secondary" data-next aria-label="Næste side">→</button><a data-download class="btn btn-sm btn-outline-primary ms-auto" hidden>Download</a></div><div data-preview>Vælg en fil. Brug ← → til PDF-sider og ↑ ↓ til filer.</div></div></div></div></div>';
|
|
panes.append(pane);const q=s=>pane.querySelector(s),status=q('[data-status]');
|
|
const imageTypes=new Set(['jpg','jpeg','png','gif','webp','bmp','tif','tiff','ico']);
|
|
const key=f=>`${f.source}/${f.id}`;
|
|
async function content(f){const k=key(f);if(!cache.has(k))cache.set(k,api(`${base}/${k}/text`).catch(()=>({pages:1,text:'',error:true})));return cache.get(k);}
|
|
let visible=[];
|
|
async function render(){const current=++token,query=q('[data-search]').value.trim().toLocaleLowerCase('da'),type=q('[data-type]').value;let rows=files.filter(f=>!type||f.type===type);
|
|
if(query){status.textContent='Søger i filnavne og dokumenttekst…';const found=await Promise.all(rows.map(async f=>{if(f.filename.toLocaleLowerCase('da').includes(query))return f;if(!['txt','pdf'].includes(f.type))return null;const c=await content(f);return c.text.toLocaleLowerCase('da').includes(query)?f:null;}));rows=found.filter(Boolean);}
|
|
if(current!==token)return;rows.sort((a,b)=>(q('[data-sort]').value==='type'?a.type.localeCompare(b.type):0)||a.filename.localeCompare(b.filename,'da'));visible=rows;q('[data-list]').replaceChildren();
|
|
for(const f of rows){const b=document.createElement('button');b.type='button';b.className='case-file-item'+(selected&&key(f)===key(selected)?' active':'');const name=document.createElement('strong');name.textContent=f.filename;const hint=document.createElement('small');hint.textContent=`${f.type.toUpperCase()} · ${f.source==='mail'?'Vedhæftet mail':'Sagsfil'}`;const thumb=document.createElement('img');thumb.className='case-file-thumbnail';thumb.alt='';thumb.loading='lazy';thumb.src=`${base}/${key(f)}/thumbnail`;const caption=document.createElement('span');caption.append(name,hint);b.append(thumb,caption);b.addEventListener('click',()=>select(f));q('[data-list]').append(b);}
|
|
status.textContent=`${rows.length} af ${files.length} filer. PDF-søgning gælder indlejret tekst, ikke scannede billeder.`;
|
|
}
|
|
function showPage(){q('[data-page]').textContent=selected?`Side ${page} af ${pages}`:'';q('[data-prev]').disabled=page<=1;q('[data-next]').disabled=page>=pages;if(selected?.type==='pdf'){const img=document.createElement('img');img.alt=`${selected.filename} · side ${page}`;img.src=`${base}/${key(selected)}/page/${page}`;img.addEventListener('error',()=>{q('[data-preview]').textContent='PDF-siden kunne ikke vises. Brug Download.';});q('[data-preview]').replaceChildren(img);}}
|
|
async function select(f){selected=f;page=1;pages=1;q('[data-preview]').textContent='Indlæser…';const a=q('[data-download]');a.hidden=false;a.href=`${base}/${key(f)}/download`;const c=await content(f);if(selected!==f)return;pages=c.pages||1;if(f.type==='txt'){const pre=document.createElement('pre');pre.textContent=c.text||'Teksten kunne ikke læses';q('[data-preview]').replaceChildren(pre);}else if(imageTypes.has(f.type)){const img=document.createElement('img');img.alt=f.filename;img.src=`${base}/${key(f)}/image`;img.addEventListener('error',()=>{if(selected===f)q('[data-preview]').textContent='Billedet kunne ikke vises. Brug Download.';});q('[data-preview]').replaceChildren(img);}else if(f.type!=='pdf')q('[data-preview]').textContent='Denne filtype kan downloades.';showPage();render();}
|
|
q('[data-prev]').addEventListener('click',()=>{if(page>1){page--;showPage();}});q('[data-next]').addEventListener('click',()=>{if(page<pages){page++;showPage();}});
|
|
pane.addEventListener('keydown',e=>{if(e.target.closest('input,select,textarea')||!selected)return;if(['ArrowLeft','ArrowRight'].includes(e.key)){e.preventDefault();page=Math.max(1,Math.min(pages,page+(e.key==='ArrowRight'?1:-1)));showPage();}if(['ArrowUp','ArrowDown'].includes(e.key)){const i=visible.findIndex(f=>key(f)===key(selected)),f=visible[i+(e.key==='ArrowDown'?1:-1)];if(f){e.preventDefault();select(f);}}});
|
|
q('[data-search]').addEventListener('input',render);q('[data-type]').addEventListener('change',render);q('[data-sort]').addEventListener('change',render);
|
|
async function refresh(){try{files=await api(base);nav.hidden=!files.length;const type=q('[data-type]');const old=type.value;type.replaceChildren(new Option('Alle typer',''),...[...new Set(files.map(f=>f.type))].sort().map(t=>new Option(t.toUpperCase(),t)));type.value=old;render();}catch{status.textContent='Filer kunne ikke hentes. Prøv at åbne fanen igen.';}}
|
|
nav.addEventListener('shown.bs.tab',refresh);document.addEventListener('visibilitychange',()=>{if(!document.hidden)refresh();});
|
|
// Existing uploads and mail links refresh their data without navigating.
|
|
document.addEventListener('click',()=>{clearTimeout(init.refreshTimer);init.refreshTimer=setTimeout(refresh,1800);});
|
|
await refresh();
|
|
}
|
|
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init);else init();})();
|