// ── Column visibility ─────────────────────────────────────────────
const ALL_COLS = [
{ key:'num', label:'# Заявка', def:true },
{ key:'title', label:'Тема', def:true },
{ key:'intrastatus', label:'Статус в интре', def:true },
{ key:'client', label:'Клиент', def:true },
{ key:'date', label:'Время ответа', def:true },
{ key:'created', label:'Создана', def:true },
{ key:'preview', label:'Превью/Заметка', def:true },
{ key:'status', label:'Статус', def:true },
];
let visibleCols = new Set(ALL_COLS.filter(c=>c.def).map(c=>c.key));
let colMenuOpen = false;
let filteredData = [];
let statFilter = 'all';
let selectedRows = new Set();
let mainTab = 'all'; // 'all' | 'open' | 'done' | 'empty'
function setMainTab(tab, el) {
mainTab = tab;
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('tab-btn-active'));
if (el) el.classList.add('tab-btn-active');
renderTable();
}
function isTaskOpen(item) {
const sn = (item.task._statusname || item.task.statusname || '').toLowerCase();
return sn.includes('открыт') || sn.includes('в работе') || sn.includes('новая') || sn.includes('new') || sn.includes('назначен');
}
function isTaskDone(item) {
const sn = (item.task._statusname || item.task.statusname || '').toLowerCase();
return sn.includes('выполнен') || sn.includes('закрыт') || sn.includes('решен');
}
function isTaskEmpty(item) {
return !item.lastComment;
}
// ── Helpers ───────────────────────────────────────────────────────
function getTaskTags(task){const r=task.tags?.data??task.tags??[];return r.map(t=>({id:t.id??t,name:t.name??String(t)}));}
function buildTagIndex(){return [];}
function renderTagFilter(){}
function abbrevName(name){
if(!name||name==='—') return name||'—';
const p=name.trim().split(/\s+/);
if(p.length===1) return p[0];
return p[0]+' '+p[1][0]+'.';
}
const AV_COLORS=['#5b7fff','#ff4d4f','#52c41a','#fa8c16','#722ed1','#13c2c2','#eb2f96'];
function avatarColor(n){let h=0;for(let i=0;i<(n||'').length;i++)h=(h*31+n.charCodeAt(i))%AV_COLORS.length;return AV_COLORS[h];}
function avatarInitials(n){
if(!n||n==='—') return'?';
const p=n.trim().split(/\s+/);
return p.length>1?(p[0][0]+p[1][0]).toUpperCase():n.slice(0,2).toUpperCase();
}
// ── Chips ─────────────────────────────────────────────────────────
function setChip(el,on){
if(!el)return;
el.classList.toggle('chip-on',on);
el.classList.toggle('chip-off',!on);
}
function syncAllChip(){setChip(document.getElementById('chip-all'),STATE_CHIP_KEYS.every(k=>stateChips[k]));}
function toggleStateChip(name){
stateChips[name]=!stateChips[name];
setChip(document.getElementById('chip-'+name),stateChips[name]);
syncAllChip(); renderTable();
}
function toggleAllChip(){
const t=!STATE_CHIP_KEYS.every(k=>stateChips[k]);
STATE_CHIP_KEYS.forEach(k=>{stateChips[k]=t;setChip(document.getElementById('chip-'+k),t);});
setChip(document.getElementById('chip-all'),t);
renderTable();
}
// ── Init mark chips + bulk-select from MARKS_CONFIG ───────────────
function initMarkChips(){
// Chip dropdown items
const container=document.getElementById('mark-chips-container');
if(container){
container.innerHTML=MARKS_CONFIG.map(m=>`
${m.chipLabel}
0
`).join('');
}
// Bulk-status select options
const sel=document.getElementById('bulk-status-sel');
if(sel){
const clearOpt=sel.querySelector('option[value="clear"]');
MARKS_CONFIG.slice().reverse().forEach(m=>{
const opt=document.createElement('option');
opt.value=m.key; opt.textContent=m.label;
sel.insertBefore(opt, clearOpt);
});
}
}
function toggleMentionedFilter(){
mentionedFilterOn=!mentionedFilterOn;
setChip(document.getElementById('chip-mentioned'),mentionedFilterOn);
renderTable();
}
function toggleChipDropdown() {
const panel = document.getElementById('chip-dropdown-panel');
if (!panel) return;
panel.classList.toggle('chip-dd-open');
}
function updateChipDropdownLabel() {
const allOn = STATE_CHIP_KEYS.every(k => stateChips[k]);
const activeKeys = STATE_CHIP_KEYS.filter(k => stateChips[k]);
const lbl = document.getElementById('chip-dropdown-label');
if (!lbl) return;
if (allOn) {
lbl.textContent = 'Все статусы';
} else if (activeKeys.length === 0) {
lbl.textContent = 'Ни один';
} else if (activeKeys.length === 1) {
const key = activeKeys[0];
if (key === 'unmarked') { lbl.textContent = 'Без статуса'; return; }
const m = MARKS_CONFIG.find(x => x.key === key);
lbl.textContent = m ? m.chipLabel : key;
} else {
lbl.textContent = `Статусов: ${activeKeys.length}`;
}
}
function syncChipDropdownItems() {
const allOn = STATE_CHIP_KEYS.every(k => stateChips[k]);
const allItem = document.getElementById('chipdd-all');
if (allItem) allItem.classList.toggle('chip-dd-active', allOn);
MARKS_CONFIG.forEach(m => {
const el = document.getElementById(`chipdd-${m.key}`);
if (el) el.classList.toggle('chip-dd-active', !!stateChips[m.key]);
});
const uEl = document.getElementById('chipdd-unmarked');
if (uEl) uEl.classList.toggle('chip-dd-active', !!stateChips['unmarked']);
updateChipDropdownLabel();
}
function setStatFilter(f,el){
statFilter=f;
document.querySelectorAll('.stat-card').forEach(c=>c.classList.remove('active'));
if(el)el.classList.add('active');
renderTable();
}
function setNavView(v,e){}
// ── Date filter ───────────────────────────────────────────────────
function taskMatchesDate(task){
const v=document.getElementById('date-filter')?.value||'0';
if(v==='0')return true;
const now=new Date(),upd=new Date(task.updatedat);
if(v==='today')return upd.toDateString()===now.toDateString();
if(v==='yesterday'){const y=new Date(now);y.setDate(y.getDate()-1);return upd.toDateString()===y.toDateString();}
return upd>=new Date(now.setDate(now.getDate()-parseInt(v)));
}
// ── Status badges ─────────────────────────────────────────────────
function internalStatusBadge(tid){
const m=MARKS_CONFIG.find(m=>isMark(tid,m.key));
if(m) return`${m.label}`;
return`Без статуса`;
}
// ── IntraDesk status badge ────────────────────────────────────────
function intraStatusBadge(task) {
const name = task._statusname || task.statusname || '';
if (!name) {
// Показываем числовой id статуса как fallback
const sid = task.statusid || task.status;
if (sid) return `#${sid}`;
return '—';
}
const lc = name.toLowerCase();
let cls = 'sb-intra-other';
if (lc.includes('открыт') || lc.includes('new') || lc.includes('открытый')) cls = 'sb-intra-open';
else if (lc.includes('работ') || lc.includes('прогресс') || lc.includes('назначен')) cls = 'sb-intra-work';
else if (lc.includes('выполнен') || lc.includes('закрыт') || lc.includes('решен')) cls = 'sb-intra-done';
else if (lc.includes('ожидан') || lc.includes('ожидает') || lc.includes('hold')) cls = 'sb-intra-hold';
return `${esc(name)}`;
}
// Who answered — ring class for avatar
function whoAnsweredRing(lc){
if(!lc) return 'ring-none';
return lc.usertype===10 ? 'ring-internal' : 'ring-external';
}
// Who answered — badge html
function whoAnsweredBadge(lc){
if(!lc) return`Нет ответа`;
if(lc.usertype===10) return`Сотрудник`;
return`Клиент`;
}
// Row color — ONLY by internal marks, not by who answered
function rowColorClass(tid){
const m=MARKS_CONFIG.find(m=>isMark(tid,m.key));
return m?m.rowClass:'';
}
// ── Who set a mark ────────────────────────────────────────────────
function markSetByHtml(tid) {
const lines=MARKS_CONFIG
.filter(cfg=>marksCache[tid]?.[cfg.key]?.active&&marksCache[tid][cfg.key].by)
.map(cfg=>{
const mc=marksCache[tid][cfg.key];
return`👤 ${esc(abbrevName(mc.by))} · «${cfg.label}»${mc.at?` · ${timeAgo(new Date(mc.at))}`:''}`;
});
return lines.length?`${lines.join('')}
`:'';
}
// ── Inline status picker ──────────────────────────────────────────
function statusPickerHtml(tid){
const items=MARKS_CONFIG.map(m=>`
${m.label}
`).join('');
return`
${internalStatusBadge(tid)}
`;
}
function ispShow(el){
const dd=el.querySelector('.isp-dropdown');
if(!dd) return;
const r=el.getBoundingClientRect();
dd.style.top=(r.bottom)+'px';
dd.style.left=r.left+'px';
dd.style.display='block';
}
function ispHide(el){
const dd=el.querySelector('.isp-dropdown');
if(dd) dd.style.display='none';
}
// ── Column picker ─────────────────────────────────────────────────
function toggleColMenu(e){
e.stopPropagation();
colMenuOpen=!colMenuOpen;
document.getElementById('col-menu')?.classList.toggle('open',colMenuOpen);
}
document.addEventListener('click',()=>{
colMenuOpen=false;
document.getElementById('col-menu')?.classList.remove('open');
});
function toggleCol(key){
if(visibleCols.has(key))visibleCols.delete(key);else visibleCols.add(key);
renderColMenu(); renderTable();
}
function renderColMenu(){
const m=document.getElementById('col-menu');
if(!m)return;
m.innerHTML=ALL_COLS.map(c=>`
`).join('');
}
// ── Sort state ────────────────────────────────────────────────────
let sortCol = null; // 'date' | 'created'
let sortDir = 'desc';
function toggleSort(col) {
if (sortCol === col) sortDir = sortDir === 'desc' ? 'asc' : 'desc';
else { sortCol = col; sortDir = 'desc'; }
renderTable();
}
// ── Pagination state ──────────────────────────────────────────────
let currentPage = 1;
function getPageSize() { const v=parseInt(document.getElementById('opt-page-size')?.value||'50'); return v||0; }
function setPage(p) { currentPage = p; renderTable(true); }
function onPageSizeChange() { currentPage = 1; renderTable(); }
// ── Chip logic: click label = solo, click dot = toggle ────────────
function chipClickLabel(name) {
STATE_CHIP_KEYS.forEach(k => { stateChips[k] = (k === name); });
syncChipDropdownItems();
renderTable();
}
function chipClickDot(name, e) {
e && e.stopPropagation();
stateChips[name] = !stateChips[name];
syncChipDropdownItems();
renderTable();
}
function toggleAllChip() {
const t = !STATE_CHIP_KEYS.every(k => stateChips[k]);
STATE_CHIP_KEYS.forEach(k => { stateChips[k] = t; });
syncChipDropdownItems();
renderTable();
}
// ── Render table ──────────────────────────────────────────────────
function renderTable(keepPage){
if(!keepPage) currentPage = 1;
const search =document.getElementById('search-input')?.value?.toLowerCase()||'';
const remoteOnly =document.getElementById('opt-remote-only')?.checked;
filteredData=allData.filter(item=>{
const tid=String(item.task.id);
const marked=MARKS_CONFIG.some(m=>isMark(tid,m.key));
for(const m of MARKS_CONFIG){if(isMark(tid,m.key)&&!stateChips[m.key])return false;}
if(!marked&&!stateChips.unmarked) return false;
if(mentionedFilterOn&&!mentionsData.some(m=>String(m.task_id)===tid)) return false;
if(search&&!(item.task.name?.toLowerCase().includes(search)||String(item.task.tasknumber).includes(search)||(item.task._clientname||item.task.clientname||'').toLowerCase().includes(search))) return false;
if(remoteOnly&&item.task.service!==84683) return false;
if(!taskMatchesDate(item.task)) return false;
if(statFilter!=='all'&&getAuthorType(item)!==statFilter) return false;
// Main tab filter
if(mainTab==='open' && !isTaskOpen(item)) return false;
if(mainTab==='done' && !isTaskDone(item)) return false;
if(mainTab==='empty' && !isTaskEmpty(item)) return false;
return true;
});
// Sort
if(sortCol) {
filteredData = [...filteredData].sort((a,b) => {
const da = sortCol==='date'
? (a.lastComment?.eventat||a.task.updatedat||'')
: (a.task.createdat||'');
const db = sortCol==='date'
? (b.lastComment?.eventat||b.task.updatedat||'')
: (b.task.createdat||'');
const cmp = da < db ? -1 : da > db ? 1 : 0;
return sortDir==='asc' ? cmp : -cmp;
});
}
// Counts
const base=allData.filter(i=>taskMatchesDate(i.task));
const g=id=>document.getElementById(id);
const sv=(id,v)=>{const e=g(id);if(e)e.textContent=v;};
sv('chip-all-cnt', base.length);
MARKS_CONFIG.forEach(m=>{
sv(`chip-${m.key}-cnt`, base.filter(i=>isMark(String(i.task.id),m.key)).length);
});
sv('chip-unmarked-cnt', base.filter(i=>{
const tid=String(i.task.id);return !MARKS_CONFIG.some(m=>isMark(tid,m.key));
}).length);
sv('stat-total', base.length);
sv('stat-external', base.filter(i=>getAuthorType(i)==='external').length);
sv('stat-internal', base.filter(i=>getAuthorType(i)==='internal').length);
sv('stat-nocomment', base.filter(i=>getAuthorType(i)==='nocomment').length);
sv('stat-unread', allData.filter(i=>isUnread(i.task)).length);
sv('nav-badge-tasks', filteredData.length);
sv('topbar-sub', `· ${filteredData.length} заявок`);
sv('total-loaded', filteredData.length);
sv('total-count', allData.length);
sv('tf-info', `${filteredData.length} из ${allData.length}`);
// Tab counts (using allData for totals regardless of chip filter)
sv('tcnt-all', allData.length);
sv('tcnt-open', allData.filter(i=>isTaskOpen(i)).length);
sv('tcnt-done', allData.filter(i=>isTaskDone(i)).length);
sv('tcnt-empty', allData.filter(i=>isTaskEmpty(i)).length);
if(!filteredData.length){
g('table-wrap').innerHTML=`🔍Заявок не найдено
Измените фильтры
`;
const pb=g('pagination-bar'); if(pb){pb.style.display='none';pb.innerHTML='';}
return;
}
// Pagination slice
const PAGE_SIZE = getPageSize();
const totalPages = PAGE_SIZE > 0 ? Math.ceil(filteredData.length / PAGE_SIZE) : 1;
if(currentPage > totalPages) currentPage = totalPages;
const pageData = PAGE_SIZE > 0 ? filteredData.slice((currentPage-1)*PAGE_SIZE, currentPage*PAGE_SIZE) : filteredData;
const sc=k=>visibleCols.has(k);
const sortArrow = col => sortCol===col ? (sortDir==='asc' ? ' ↑' : ' ↓') : ' ↕';
const sortStyle = col => `cursor:pointer;user-select:none;${sortCol===col?'color:var(--accent);':'color:inherit;'}`;
const thead=`
|
${sc('num') ?'# | ' :''}
${sc('title') ?'Тема | ' :''}
${sc('intrastatus')?'Статус в интре | ':''}
${sc('status') ?'Статус | ' :''}
${sc('client') ?'Кто ответил | ' :''}
${sc('date') ?`Время ответа${sortArrow('date')} | ` :''}
${sc('created')?`Создана${sortArrow('created')} | ` :''}
${sc('preview')?'Превью | ' :''}
`;
const rows=pageData.map(item=>{
const {task,lastComment}=item;
const tid=String(task.id);
const num=task.tasknumber||task.id;
const idx=allData.indexOf(item);
const unread=isUnread(task);
const note=getNote(tid);
const cc=rowColorClass(tid);
const rowCls=((unread&&!cc?'row-unread ':'')+cc+(currentModalIdx===idx?' active-row':'')).trim();
const authorName=lastComment?.username||task.updatedby||task.username||'';
const orgName=task._clientname||task.clientname||task.organization||'';
const unreadDot=unread?'':'';
const cntBadge=getCommentCount(tid)?``:'';
const prevCell=note
?`📝 ${esc(note.slice(0,42))}${note.length>42?'…':''}
`
:(lastComment?`${esc(stripHtml(lastComment.text||'').slice(0,52))}
`:'');
const whoLabel=lastComment
?(lastComment.usertype===10
?'● Сотрудник'
:'● Клиент')
:(task.updatedby
?`${esc(abbrevName(task.updatedby))}`
:'нет ответа');
return`
|
|
${sc('num') ?`#${num}${unreadDot} | `:''}
${sc('title') ?`${esc(task.name)} ${orgName?`${esc(orgName)} `:''} | `:''}
${sc('intrastatus')?`${intraStatusBadge(task)} | `:''}
${sc('status') ?`${statusPickerHtml(tid)} | `:''}
${sc('client') ?`
${esc(abbrevName(authorName)||'—')}
${whoLabel}
| `:''}
${sc('date') ?`${lastComment?` ${fmtDateShort(new Date(lastComment.eventat))} ${fmtTime(new Date(lastComment.eventat))} ${timeAgo(new Date(lastComment.eventat))} `:task.updatedat?`${fmtDateShort(new Date(task.updatedat))} ${fmtTime(new Date(task.updatedat))} ${timeAgo(new Date(task.updatedat))} `:'— '} | `:''}
${sc('created')?`${task.createdat?` ${fmtDateShort(new Date(task.createdat))} ${fmtTime(new Date(task.createdat))} `:'— '} | `:''}
${sc('preview')?`${prevCell}${cntBadge} | `:''}
`;
}).join('');
g('table-wrap').innerHTML=``;
// Pagination bar
const pb = g('pagination-bar');
if(pb) {
if(totalPages <= 1 || PAGE_SIZE === 0){ pb.style.display='none'; pb.innerHTML=''; }
else {
pb.style.display='flex';
const start=(currentPage-1)*PAGE_SIZE+1, end=Math.min(currentPage*PAGE_SIZE,filteredData.length);
let pages='';
for(let p=1;p<=totalPages;p++){
if(p===1||p===totalPages||Math.abs(p-currentPage)<=2){
pages+=``;
} else if(Math.abs(p-currentPage)===3){
pages+=`…`;
}
}
pb.innerHTML=`
${pages}
${start}–${end} из ${filteredData.length}`;
}
}
}
function fmtDateShort(d){return d.toLocaleDateString('ru-RU',{day:'2-digit',month:'2-digit',year:'2-digit'});}
function fmtTime(d){return d.toLocaleTimeString('ru-RU',{hour:'2-digit',minute:'2-digit'});}
function toggleSelectAll(cb){document.querySelectorAll('.row-check').forEach(c=>{const i=+c.dataset.idx;if(cb.checked)selectedRows.add(i);else selectedRows.delete(i);c.checked=cb.checked;});updateBulkBar();}
function toggleRowCheck(idx,cb){if(cb.checked)selectedRows.add(idx);else selectedRows.delete(idx);updateBulkBar();}
function updateBulkBar(){
const info=document.getElementById('bulk-sel-info');
const cnt =document.getElementById('bulk-sel-count');
const sel =document.getElementById('bulk-status-sel');
if(!info)return;
if(selectedRows.size>0){
info.style.display='flex';
if(cnt)cnt.textContent=`${selectedRows.size} выбрано`;
if(sel)sel.value='';
}else{
info.style.display='none';
}
}
function bulkApplyStatusFromSelect(sel){
const v=sel.value;
sel.value='';
if(!v)return;
bulkApplyStatus(v==='clear'?null:v);
}
function bulkApplyStatus(markType){
if(!selectedRows.size)return;
const now=new Date().toISOString();
const tids=[...selectedRows].map(idx=>allData[idx]?String(allData[idx].task.id):null).filter(Boolean);
for(const tid of tids){
if(!marksCache[tid])marksCache[tid]={};
if(!markType){
const active=STATUS_MARKS.find(m=>marksCache[tid][m]?.active);
STATUS_MARKS.forEach(m=>{marksCache[tid][m]={...(marksCache[tid][m]||{}),active:false};});
if(active) apiPost('/api/marks',{task_id:tid,mark_type:active,value:false,clear_others:false});
}else{
STATUS_MARKS.forEach(m=>{marksCache[tid][m]={...(marksCache[tid][m]||{}),active:false};});
marksCache[tid][markType]={active:true,by:currentUser?.username,at:now};
apiPost('/api/marks',{task_id:tid,mark_type:markType,value:true,clear_others:true});
}
}
renderTable();
if(currentModalIdx!==null){
const openTid=String(allData[currentModalIdx]?.task?.id);
if(tids.includes(openTid))renderSidePanel(currentModalIdx);
}
}
// ── SIDE PANEL open/close ─────────────────────────────────────────
function openModal(idx){
if(idx===null||idx===undefined||!allData[idx]) return;
currentModalIdx=idx; currentTab='last';
const {task,lastComment}=allData[idx];
// Immediately mark read & update that row in-place
if(isUnread(task)){
markSeen(task.id,task.updatedat);
const row=document.querySelector(`tbody tr[data-idx="${idx}"]`);
if(row){
['row-unread',...MARKS_CONFIG.map(m=>m.rowClass)].forEach(c=>row.classList.remove(c));
const cc=rowColorClass(String(task.id));
if(cc) row.classList.add(cc);
row.querySelector('.unread-dot-inline')?.remove();
}
const su=document.getElementById('stat-unread');
if(su) su.textContent=allData.filter(i=>isUnread(i.task)).length;
}
// Mark mentions read
if(mentionsData.some(m=>String(m.task_id)===String(task.id)&&!m.read_at)){
apiPost('/api/mentions/read',{task_id:String(task.id)}).then(r=>{
if(r){
mentionUnread=r.unread||0;
mentionsData=mentionsData.map(m=>
String(m.task_id)===String(task.id)?{...m,read_at:new Date().toISOString()}:m);
renderMentionsBell();
}
});
}
// Highlight active row
document.querySelectorAll('.tasks-table tbody tr').forEach(r=>r.classList.remove('active-row'));
document.querySelector(`tbody tr[data-idx="${idx}"]`)?.classList.add('active-row');
// Show side panel
const dc=document.getElementById('detail-col');
if(dc) dc.classList.remove('hidden');
renderSidePanel(idx);
loadCommentsForTask(String(task.id));
}
function closeModal(){
const dc=document.getElementById('detail-col');
if(dc) dc.classList.add('hidden');
document.querySelectorAll('.tasks-table tbody tr').forEach(r=>r.classList.remove('active-row'));
currentModalIdx=null;
}
function closeModalOnOverlay(){}
document.addEventListener('keydown',e=>{if(e.key==='Escape')closeModal();});
// ── SIDE PANEL render ─────────────────────────────────────────────
function renderSidePanel(idx){
const container=document.getElementById('detail-content');
if(!container) return;
if(idx===null||idx===undefined||!allData[idx]){
container.innerHTML='';
return;
}
const {task,lastComment}=allData[idx];
const tid=String(task.id);
const num=task.tasknumber||task.id;
const note=getNote(tid),noteBy=getNoteBy(tid),noteAt=getNoteAt(tid);
const cnt=getCommentCount(tid);
const authorName=lastComment?.username||task.username||'—';
const rawDesc=task.description||task.descriptionhtml||task.body||task.text||'';
const noteHtml=(openNoteIdx===idx)
?``
:note
?`📝 ${renderMentions(note)}
${noteBy?`👤 ${esc(abbrevName(noteBy))}`:''}${noteAt?` · ${fmtDate(new Date(noteAt))}`:''}
`
:`+ Добавить заметку
`;
container.innerHTML=`
${MARKS_CONFIG.map(m=>{
const active=isMark(tid,m.key);
return``;
}).join('')}
${markSetByHtml(tid)}
${noteHtml}
Последний комментарий
Загрузить заявку
${renderTabContent(idx,currentTab,rawDesc,lastComment)}
`;
const ta=document.getElementById('composer-ta');
if(ta){
ta.addEventListener('keydown',e=>{
if(e.ctrlKey&&e.key==='Enter'){e.preventDefault();submitComment();}
handleAutocompleteKey(e);
});
}
if(openNoteIdx===idx){
const nta=document.getElementById('detail-note-ta');
if(nta){nta.focus();nta.setSelectionRange(nta.value.length,nta.value.length);}
}
}
// aliases for SSE/mentions compatibility
function renderDetailPanel(idx){renderSidePanel(idx);}
function renderModalContent(idx){renderSidePanel(idx);}
function openDetail(idx){openModal(idx);}
function closeDetail(){closeModal();}
// ── Tab content ───────────────────────────────────────────────────
function renderTabContent(idx,tab,rawDesc,lastComment){
const {task}=allData[idx];
const tid=String(task.id);
if(tab==='last'){
const org=task.clientname||task.organization||'';
const authorName=lastComment?.username||'—';
const created=task.createdat?new Date(task.createdat):'';
const descHtml=rawDesc
?``:'';
const actHtml=lastComment
?`Последняя активность
${avatarInitials(authorName)}
${esc(abbrevName(authorName))}${timeAgo(new Date(lastComment.eventat))}
${esc(stripHtml(lastComment.text||'').slice(0,180))}
`:'';
const executorName = task._executorname || '';
const executorGroupName = task._executorgroupname || '';
return`
${descHtml}${actHtml}`;
}
if(tab==='comments'){
return renderCommentsHtml(tid,commentsCache[tid]||[]);
}
if(tab==='history'){
setTimeout(()=>loadAndRenderHistory(idx,'sp-body'),10);
return``;
}
return'';
}
function switchTab(tab){
currentTab=tab;
if(currentModalIdx===null) return;
['last','comments','history'].forEach(t=>document.getElementById(`dtab-${t}`)?.classList.toggle('active',t===tab));
const {task}=allData[currentModalIdx];
const rawDesc=task.description||task.descriptionhtml||task.body||task.text||'';
const sb=document.getElementById('sp-body');
if(sb) sb.innerHTML=renderTabContent(currentModalIdx,tab,rawDesc,allData[currentModalIdx].lastComment);
}
// ── Comments ──────────────────────────────────────────────────────
function renderCommentsHtml(tid,comments){
if(!comments.length) return`Комментариев нет. Напишите первый!
`;
return`
${comments.map(c=>{
const own=c.author===currentUser.username;
const canEdit=own||currentUser.is_admin;
const te=c.text.replace(/\\/g,'\\\\').replace(/'/g,"\\'").replace(/\n/g,'\\n');
return``;
}).join('')}
`;
}
async function loadAndRenderHistory(idx,containerId){
const {task}=allData[idx];
const tid=String(task.id);
const res=await apiGet(`/api/tasks/history/${tid}`);
if(currentModalIdx!==idx||currentTab!=='history') return;
const el=document.getElementById(containerId);
if(!el) return;
const entries=res?.entries||[];
if(!entries.length){el.innerHTML=`История пуста
`;return;}
const rawDesc=task.description||task.descriptionhtml||task.body||task.text||'';
let db='';
if(rawDesc) db=`Описание
${sanitizeHtml(rawDesc)}
`;
const items=entries.map(entry=>{
const staff=entry.usertype===10;
const evHtml=(entry.events?.data||[]).map(ev=>{
const tn=EVENT_TYPE_NAMES[ev.type]||`тип ${ev.type}`;
const isCmt=COMMENT_TYPES.has(ev.type);
let body='';
if(isCmt&&ev.stringvalue) body=`${sanitizeHtml(ev.stringvalue)}
`;
else if(ev.type===10&&ev.entitylanguagevalue) body=`«${esc(ev.entitylanguagevalue.valueold?.name?.ru||'?')}» → «${esc(ev.entitylanguagevalue.valuenew?.name?.ru||'?')}»
`;
else if(ev.entityvalue) body=`«${esc(ev.entityvalue.valueold?.name||'?')}» → «${esc(ev.entityvalue.valuenew?.name||'?')}»
`;
return`${body}
`;
}).join('');
return`
${evHtml}
`;
}).join('');
el.innerHTML=`${db}Событий: ${entries.length}
${items}`;
}
// ── Submit comment ────────────────────────────────────────────────
async function submitComment(){
if(currentModalIdx===null) return;
const ta=document.getElementById('composer-ta');
const text=ta?.value?.trim();
if(!text) return;
const tid=String(allData[currentModalIdx].task.id);
ta.disabled=true;
const r=await apiPost('/api/comments',{task_id:tid,text});
if(r?.id){
if(!commentsCache[tid]) commentsCache[tid]=[];
commentsCache[tid].push(r);
if(typeof commentCounts==='object') commentCounts[tid]=commentsCache[tid].length;
ta.value='';
if(currentTab==='comments'){
const sb=document.getElementById('sp-body');
if(sb) sb.innerHTML=renderCommentsHtml(tid,commentsCache[tid]);
}
const dt=document.getElementById('dtab-comments');
if(dt) dt.textContent=`💬 Наши (${commentsCache[tid].length})`;
renderTable();
} else showToast('❌ Ошибка отправки','warn');
ta.disabled=false;
}
// alias
function submitCommentFromModal(){submitComment();}
function insertAtSymbol(){
const ta=document.getElementById('composer-ta');
if(!ta) return;
const p=ta.selectionStart;
ta.value=ta.value.slice(0,p)+'@'+ta.value.slice(p);
ta.selectionStart=ta.selectionEnd=p+1;
ta.focus(); onCommentInput(ta);
}
// ── Note editor ───────────────────────────────────────────────────
function openNoteEditor(idx,e){
e&&e.stopPropagation();
openNoteIdx=openNoteIdx===idx?null:idx;
if(currentModalIdx===idx) renderSidePanel(idx);
else renderTable();
}
function closeNoteEditor(){
openNoteIdx=null;
if(currentModalIdx!==null) renderSidePanel(currentModalIdx);
else renderTable();
}
async function saveNoteAndClose(idx,e){
e&&e.stopPropagation();
const ta=document.getElementById('detail-note-ta');
const tid=String(allData[idx].task.id);
const text=ta?ta.value:'';
if(text.trim()) notesCache[tid]={text:text.trim(),by:currentUser.username,at:new Date().toISOString()};
else delete notesCache[tid];
openNoteIdx=null;
if(currentModalIdx===idx) renderSidePanel(idx);
renderTable();
const r=await apiPost('/api/notes',{task_id:tid,text});
if(r&&!r.ok&&r.error) showToast('❌ '+r.error,'warn');
}
// ── Settings ──────────────────────────────────────────────────────
function openSettingsModal(){document.getElementById('settings-modal')?.classList.add('open');}
function closeSettingsModal(){document.getElementById('settings-modal')?.classList.remove('open');}
// ── SSE refresh ───────────────────────────────────────────────────
function refreshDetailIfOpen(tid){
if(currentModalIdx===null) return;
if(String(allData[currentModalIdx].task.id)!==String(tid)) return;
renderSidePanel(currentModalIdx);
if(currentTab==='comments'){
const sb=document.getElementById('sp-body');
if(sb) sb.innerHTML=renderCommentsHtml(tid,commentsCache[tid]||[]);
}
}
// Инициализация динамических чипов и bulk-select при загрузке DOM
document.addEventListener('DOMContentLoaded', () => {
initMarkChips();
syncChipDropdownItems();
// Close chip dropdown on outside click
document.addEventListener('click', e => {
if (!e.target.closest('#chip-dropdown-wrap')) {
document.getElementById('chip-dropdown-panel')?.classList.remove('chip-dd-open');
}
});
});