Compare commits
No commits in common. "7d297ad2d979394349a6ed0af29ba94c28ff8e0d" and "7f3e277c43b419e417faf83229bb43198804e0fc" have entirely different histories.
7d297ad2d9
...
7f3e277c43
6 changed files with 68 additions and 387 deletions
|
|
@ -1310,91 +1310,12 @@ html.dark .stats-card { background:var(--surface); }
|
|||
border-top:1px dashed var(--border);
|
||||
margin-top:-4px;
|
||||
padding-top:10px;
|
||||
align-items:stretch;
|
||||
}
|
||||
.filter-native-hidden{display:none!important;}
|
||||
|
||||
.adv-filter-wrap{
|
||||
position:relative;
|
||||
min-width:180px;
|
||||
max-width:260px;
|
||||
flex:1 1 180px;
|
||||
}
|
||||
.adv-filter-btn{
|
||||
width:100%;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
gap:8px;
|
||||
padding:7px 10px;
|
||||
border:1px solid var(--border);
|
||||
border-radius:var(--radius-sm);
|
||||
background:var(--surface2);
|
||||
color:var(--text2);
|
||||
font-family:inherit;
|
||||
font-size:12px;
|
||||
cursor:pointer;
|
||||
transition:border-color .15s,color .15s,background .15s;
|
||||
}
|
||||
.adv-filter-btn:hover{
|
||||
border-color:var(--accent);
|
||||
color:var(--accent);
|
||||
}
|
||||
.adv-filter-btn span{
|
||||
min-width:0;
|
||||
overflow:hidden;
|
||||
text-overflow:ellipsis;
|
||||
white-space:nowrap;
|
||||
text-align:left;
|
||||
}
|
||||
.adv-filter-wrap.open .adv-filter-btn{
|
||||
border-color:var(--accent);
|
||||
color:var(--accent);
|
||||
background:var(--accent-light);
|
||||
}
|
||||
.adv-filter-panel{
|
||||
display:none;
|
||||
position:absolute;
|
||||
top:calc(100% + 4px);
|
||||
left:0;
|
||||
right:0;
|
||||
z-index:320;
|
||||
max-height:260px;
|
||||
overflow:auto;
|
||||
background:var(--surface);
|
||||
border:1px solid var(--border);
|
||||
border-radius:var(--radius);
|
||||
box-shadow:0 8px 24px rgba(0,0,0,.13);
|
||||
padding:6px;
|
||||
}
|
||||
.adv-filter-panel.open{display:block;}
|
||||
.adv-filter-item{
|
||||
display:flex;
|
||||
align-items:flex-start;
|
||||
gap:8px;
|
||||
padding:6px 7px;
|
||||
border-radius:6px;
|
||||
font-size:12px;
|
||||
color:var(--text2);
|
||||
cursor:pointer;
|
||||
user-select:none;
|
||||
}
|
||||
.adv-filter-item:hover{background:var(--surface2);}
|
||||
.adv-filter-item input{
|
||||
margin-top:2px;
|
||||
accent-color:var(--accent);
|
||||
cursor:pointer;
|
||||
}
|
||||
.adv-filter-item span{
|
||||
flex:1;
|
||||
min-width:0;
|
||||
line-height:1.35;
|
||||
word-break:break-word;
|
||||
}
|
||||
.adv-filter-empty{
|
||||
padding:8px 10px;
|
||||
color:var(--text3);
|
||||
font-size:12px;
|
||||
.filter-multi{
|
||||
min-width:160px;
|
||||
min-height:74px;
|
||||
background-image:none;
|
||||
padding-right:10px;
|
||||
}
|
||||
|
||||
/* ── Chip Dropdown ────────────────────────────────────── */
|
||||
|
|
|
|||
|
|
@ -298,50 +298,16 @@
|
|||
<input class="filter-search" type="text" id="search-input" placeholder="Search..." oninput="renderTable()">
|
||||
</div>
|
||||
<div class="filter-row filter-row-advanced">
|
||||
<div class="adv-filter-wrap" id="filter-who-wrap">
|
||||
<button class="adv-filter-btn" type="button" onclick="toggleAdvancedFilterDropdown(event,'filter-who-select')">
|
||||
<span id="filter-who-label">Кто ответил: все</span>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<div class="adv-filter-panel" id="filter-who-panel"></div>
|
||||
<select class="filter-select filter-native-hidden" id="filter-who-select" multiple></select>
|
||||
</div>
|
||||
|
||||
<div class="adv-filter-wrap" id="filter-executor-wrap">
|
||||
<button class="adv-filter-btn" type="button" onclick="toggleAdvancedFilterDropdown(event,'filter-executor-select')">
|
||||
<span id="filter-executor-label">Исполнители: все</span>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<div class="adv-filter-panel" id="filter-executor-panel"></div>
|
||||
<select class="filter-select filter-native-hidden" id="filter-executor-select" multiple></select>
|
||||
</div>
|
||||
|
||||
<div class="adv-filter-wrap" id="filter-object-wrap">
|
||||
<button class="adv-filter-btn" type="button" onclick="toggleAdvancedFilterDropdown(event,'filter-object-select')">
|
||||
<span id="filter-object-label">Объекты: все</span>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<div class="adv-filter-panel" id="filter-object-panel"></div>
|
||||
<select class="filter-select filter-native-hidden" id="filter-object-select" multiple></select>
|
||||
</div>
|
||||
|
||||
<div class="adv-filter-wrap" id="filter-intra-wrap">
|
||||
<button class="adv-filter-btn" type="button" onclick="toggleAdvancedFilterDropdown(event,'filter-intra-select')">
|
||||
<span id="filter-intra-label">Статус Intra: все</span>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<div class="adv-filter-panel" id="filter-intra-panel"></div>
|
||||
<select class="filter-select filter-native-hidden" id="filter-intra-select" multiple></select>
|
||||
</div>
|
||||
|
||||
<div class="adv-filter-wrap" id="filter-internal-wrap">
|
||||
<button class="adv-filter-btn" type="button" onclick="toggleAdvancedFilterDropdown(event,'filter-internal-select')">
|
||||
<span id="filter-internal-label">Внутр. статусы: все</span>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<div class="adv-filter-panel" id="filter-internal-panel"></div>
|
||||
<select class="filter-select filter-native-hidden" id="filter-internal-select" multiple></select>
|
||||
</div>
|
||||
<select class="filter-select" id="filter-who-select" onchange="renderTable()">
|
||||
<option value="all">Who answered: all</option>
|
||||
<option value="internal">Internal</option>
|
||||
<option value="external">External</option>
|
||||
<option value="nocomment">No comment</option>
|
||||
</select>
|
||||
<select class="filter-select filter-multi" id="filter-executor-select" multiple onchange="renderTable()"></select>
|
||||
<select class="filter-select filter-multi" id="filter-object-select" multiple onchange="renderTable()"></select>
|
||||
<select class="filter-select filter-multi" id="filter-intra-select" multiple onchange="renderTable()"></select>
|
||||
<select class="filter-select filter-multi" id="filter-internal-select" multiple onchange="renderTable()"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ function showApp() {
|
|||
|
||||
let lastLoadedSettings = {};
|
||||
let internalStatusesAdminCache = [];
|
||||
let missingCommentsFetchInFlight = false;
|
||||
|
||||
async function init() {
|
||||
await loadInternalStatusesConfig(false, true);
|
||||
|
|
@ -138,7 +137,6 @@ async function loadTasksFromServer(silent = false) {
|
|||
}
|
||||
|
||||
async function fetchMissingComments() {
|
||||
if (missingCommentsFetchInFlight) return;
|
||||
const toFetch = allData.filter((i) => {
|
||||
if (i.lastComment) return false;
|
||||
const tid = String(i.task.id);
|
||||
|
|
@ -147,23 +145,10 @@ async function fetchMissingComments() {
|
|||
});
|
||||
if (!toFetch.length) return;
|
||||
|
||||
missingCommentsFetchInFlight = true;
|
||||
try {
|
||||
const CONCURRENCY = 8;
|
||||
let cursor = 0;
|
||||
let loaded = 0;
|
||||
|
||||
async function worker() {
|
||||
while (true) {
|
||||
const idx = cursor;
|
||||
cursor += 1;
|
||||
if (idx >= toFetch.length) return;
|
||||
|
||||
const item = toFetch[idx];
|
||||
for (const item of toFetch) {
|
||||
const tid = String(item.task.id);
|
||||
const upd = encodeURIComponent(item.task.updatedat || '');
|
||||
let lc = null;
|
||||
|
||||
try {
|
||||
const res = await apiGet(`/api/tasks/lastcomment/${tid}?upd=${upd}`);
|
||||
if (res?.entries?.length) {
|
||||
|
|
@ -182,20 +167,10 @@ async function fetchMissingComments() {
|
|||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
item.lastComment = lc;
|
||||
forcedCommentCache.set(tid, { updatedat: item.task.updatedat, lastComment: lc });
|
||||
loaded += 1;
|
||||
if (loaded % 25 === 0 && typeof renderTable === 'function') renderTable(true);
|
||||
}
|
||||
}
|
||||
|
||||
const workers = Array.from({ length: Math.min(CONCURRENCY, toFetch.length) }, () => worker());
|
||||
await Promise.all(workers);
|
||||
} finally {
|
||||
missingCommentsFetchInFlight = false;
|
||||
if (typeof renderTable === 'function') renderTable(true);
|
||||
}
|
||||
renderTable();
|
||||
}
|
||||
|
||||
async function loadTasks() {
|
||||
|
|
|
|||
|
|
@ -147,7 +147,6 @@ function applyTasksDiff(newRawTasks, silent = false) {
|
|||
document.getElementById('update-banner')?.classList.remove('visible');
|
||||
|
||||
if (typeof onTasksLoaded === 'function') onTasksLoaded();
|
||||
if (typeof refreshAdvancedFilters === 'function') refreshAdvancedFilters();
|
||||
if (typeof renderTable === 'function') renderTable();
|
||||
if (typeof forceComment !== 'undefined' && forceComment) {
|
||||
if (typeof fetchMissingComments === 'function') fetchMissingComments();
|
||||
|
|
|
|||
|
|
@ -64,12 +64,8 @@ function onMarksConfigChanged() {
|
|||
rebuildStateChipsFromConfig();
|
||||
|
||||
// ── Seen ──────────────────────────────────────────────────────────
|
||||
function seenStorageKey() {
|
||||
const username = String(currentUser?.username || '').trim().toLowerCase();
|
||||
return username ? `${SEEN_KEY}:${username}` : SEEN_KEY;
|
||||
}
|
||||
function loadSeen() { try { return JSON.parse(localStorage.getItem(seenStorageKey())||'{}'); } catch { return {}; } }
|
||||
function saveSeen(s) { try { localStorage.setItem(seenStorageKey(), JSON.stringify(s||{})); } catch {} }
|
||||
function loadSeen() { try { return JSON.parse(localStorage.getItem(SEEN_KEY)||'{}'); } catch { return {}; } }
|
||||
function saveSeen(s) { try { localStorage.setItem(SEEN_KEY, JSON.stringify(s)); } catch {} }
|
||||
function markSeen(id,upd) { const s=loadSeen(); s[id]=upd; saveSeen(s); }
|
||||
function markAllRead(){
|
||||
const s=loadSeen();
|
||||
|
|
|
|||
230
public/js/ui.js
230
public/js/ui.js
|
|
@ -16,44 +16,6 @@ let statFilter = 'all';
|
|||
let selectedRows = new Set();
|
||||
let mainTab = 'all'; // 'all' | 'open' | 'done' | 'empty'
|
||||
|
||||
const ADV_FILTER_CONFIG = {
|
||||
'filter-who-select': {
|
||||
panelId: 'filter-who-panel',
|
||||
wrapId: 'filter-who-wrap',
|
||||
labelId: 'filter-who-label',
|
||||
allLabel: 'Кто ответил: все',
|
||||
shortLabel: 'Кто ответил',
|
||||
},
|
||||
'filter-executor-select': {
|
||||
panelId: 'filter-executor-panel',
|
||||
wrapId: 'filter-executor-wrap',
|
||||
labelId: 'filter-executor-label',
|
||||
allLabel: 'Исполнители: все',
|
||||
shortLabel: 'Исполнители',
|
||||
},
|
||||
'filter-object-select': {
|
||||
panelId: 'filter-object-panel',
|
||||
wrapId: 'filter-object-wrap',
|
||||
labelId: 'filter-object-label',
|
||||
allLabel: 'Объекты: все',
|
||||
shortLabel: 'Объекты',
|
||||
},
|
||||
'filter-intra-select': {
|
||||
panelId: 'filter-intra-panel',
|
||||
wrapId: 'filter-intra-wrap',
|
||||
labelId: 'filter-intra-label',
|
||||
allLabel: 'Статус Intra: все',
|
||||
shortLabel: 'Статус Intra',
|
||||
},
|
||||
'filter-internal-select': {
|
||||
panelId: 'filter-internal-panel',
|
||||
wrapId: 'filter-internal-wrap',
|
||||
labelId: 'filter-internal-label',
|
||||
allLabel: 'Внутр. статусы: все',
|
||||
shortLabel: 'Внутр. статусы',
|
||||
},
|
||||
};
|
||||
|
||||
function getSelectedSet(selectId){
|
||||
const el=document.getElementById(selectId);
|
||||
if(!el) return new Set();
|
||||
|
|
@ -72,102 +34,10 @@ function fillMultiSelect(selectId, items, selectedSet, emptyLabel){
|
|||
[...el.options].forEach((opt)=>{ opt.selected=selectedSet.has(String(opt.value)); });
|
||||
}
|
||||
|
||||
function updateAdvancedFilterLabel(selectId){
|
||||
const cfg=ADV_FILTER_CONFIG[selectId];
|
||||
if(!cfg) return;
|
||||
const el=document.getElementById(selectId);
|
||||
const lbl=document.getElementById(cfg.labelId);
|
||||
if(!el||!lbl) return;
|
||||
const selected=[...el.selectedOptions].filter((o)=>!o.disabled&&String(o.value).trim()!=='');
|
||||
if(!selected.length){
|
||||
lbl.textContent=cfg.allLabel;
|
||||
return;
|
||||
}
|
||||
if(selected.length===1){
|
||||
lbl.textContent=`${cfg.shortLabel}: ${selected[0].textContent}`;
|
||||
return;
|
||||
}
|
||||
lbl.textContent=`${cfg.shortLabel}: ${selected.length}`;
|
||||
}
|
||||
|
||||
function buildAdvancedFilterPanel(selectId){
|
||||
const cfg=ADV_FILTER_CONFIG[selectId];
|
||||
if(!cfg) return;
|
||||
const sel=document.getElementById(selectId);
|
||||
const panel=document.getElementById(cfg.panelId);
|
||||
if(!sel||!panel) return;
|
||||
panel.innerHTML='';
|
||||
const opts=[...sel.options].filter((o)=>!o.disabled&&String(o.value).trim()!=='');
|
||||
if(!opts.length){
|
||||
const empty=document.createElement('div');
|
||||
empty.className='adv-filter-empty';
|
||||
empty.textContent='Нет значений';
|
||||
panel.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
const frag=document.createDocumentFragment();
|
||||
opts.forEach((opt)=>{
|
||||
const item=document.createElement('label');
|
||||
item.className='adv-filter-item';
|
||||
|
||||
const cb=document.createElement('input');
|
||||
cb.type='checkbox';
|
||||
cb.checked=!!opt.selected;
|
||||
cb.addEventListener('change',()=>{
|
||||
opt.selected=cb.checked;
|
||||
updateAdvancedFilterLabel(selectId);
|
||||
renderTable();
|
||||
});
|
||||
|
||||
const text=document.createElement('span');
|
||||
text.textContent=opt.textContent;
|
||||
|
||||
item.appendChild(cb);
|
||||
item.appendChild(text);
|
||||
frag.appendChild(item);
|
||||
});
|
||||
panel.appendChild(frag);
|
||||
}
|
||||
|
||||
function closeAdvancedFilterDropdowns(exceptSelectId=''){
|
||||
Object.entries(ADV_FILTER_CONFIG).forEach(([selectId,cfg])=>{
|
||||
if(exceptSelectId&&selectId===exceptSelectId) return;
|
||||
document.getElementById(cfg.panelId)?.classList.remove('open');
|
||||
document.getElementById(cfg.wrapId)?.classList.remove('open');
|
||||
});
|
||||
}
|
||||
|
||||
function toggleAdvancedFilterDropdown(e,selectId){
|
||||
e?.stopPropagation();
|
||||
const cfg=ADV_FILTER_CONFIG[selectId];
|
||||
if(!cfg) return;
|
||||
const panel=document.getElementById(cfg.panelId);
|
||||
const wrap=document.getElementById(cfg.wrapId);
|
||||
if(!panel||!wrap) return;
|
||||
const willOpen=!panel.classList.contains('open');
|
||||
closeAdvancedFilterDropdowns(selectId);
|
||||
if(!willOpen){
|
||||
panel.classList.remove('open');
|
||||
wrap.classList.remove('open');
|
||||
return;
|
||||
}
|
||||
buildAdvancedFilterPanel(selectId);
|
||||
panel.classList.add('open');
|
||||
wrap.classList.add('open');
|
||||
}
|
||||
|
||||
function syncAdvancedFiltersUi(){
|
||||
Object.keys(ADV_FILTER_CONFIG).forEach((selectId)=>{
|
||||
updateAdvancedFilterLabel(selectId);
|
||||
const cfg=ADV_FILTER_CONFIG[selectId];
|
||||
const panel=document.getElementById(cfg.panelId);
|
||||
if(panel?.classList.contains('open')) buildAdvancedFilterPanel(selectId);
|
||||
});
|
||||
}
|
||||
|
||||
function refreshAdvancedFilters(){
|
||||
const selectedWho=getSelectedSet('filter-who-select');
|
||||
const who=document.getElementById('filter-who-select');
|
||||
if(who && !who.value) who.value='all';
|
||||
|
||||
const selectedExecutors=getSelectedSet('filter-executor-select');
|
||||
const selectedObjects=getSelectedSet('filter-object-select');
|
||||
const selectedIntra=getSelectedSet('filter-intra-select');
|
||||
|
|
@ -192,19 +62,12 @@ function refreshAdvancedFilters(){
|
|||
const execItems=[...execMap.entries()].map(([value,label])=>({value,label})).sort((a,b)=>a.label.localeCompare(b.label,'ru'));
|
||||
const objectItems=[...objectMap.entries()].map(([value,label])=>({value,label})).sort((a,b)=>a.label.localeCompare(b.label,'ru'));
|
||||
const intraItems=[...intraMap.entries()].map(([value,label])=>({value,label:`#${value} ${label}`}));
|
||||
const whoItems=[
|
||||
{ value:'internal', label:'Сотрудник' },
|
||||
{ value:'external', label:'Клиент' },
|
||||
{ value:'nocomment', label:'Нет ответа' },
|
||||
];
|
||||
const internalItems=MARKS_CONFIG.map((m)=>({value:m.key,label:m.label}));
|
||||
|
||||
fillMultiSelect('filter-who-select', whoItems, selectedWho, 'No values');
|
||||
fillMultiSelect('filter-executor-select', execItems, selectedExecutors, 'No executors');
|
||||
fillMultiSelect('filter-object-select', objectItems, selectedObjects, 'No objects');
|
||||
fillMultiSelect('filter-intra-select', intraItems, selectedIntra, 'No Intra statuses');
|
||||
fillMultiSelect('filter-internal-select', internalItems, selectedInternal, 'No statuses');
|
||||
syncAdvancedFiltersUi();
|
||||
}
|
||||
|
||||
function setMainTab(tab, el) {
|
||||
|
|
@ -499,14 +362,11 @@ function renderTable(keepPage){
|
|||
if(!keepPage) currentPage = 1;
|
||||
const search =document.getElementById('search-input')?.value?.toLowerCase()||'';
|
||||
const remoteOnly =document.getElementById('opt-remote-only')?.checked;
|
||||
const selectedWho=getSelectedSet('filter-who-select');
|
||||
const whoSelect =document.getElementById('filter-who-select')?.value||'all';
|
||||
const selectedExecutors=getSelectedSet('filter-executor-select');
|
||||
const selectedObjects=getSelectedSet('filter-object-select');
|
||||
const selectedIntra=getSelectedSet('filter-intra-select');
|
||||
const selectedInternal=getSelectedSet('filter-internal-select');
|
||||
const mentionTaskIds=mentionedFilterOn?new Set(mentionsData.map((m)=>String(m.task_id))):null;
|
||||
const seenMap=loadSeen();
|
||||
const isUnreadFast=(task)=>!seenMap[task.id]||seenMap[task.id]!==task.updatedat;
|
||||
|
||||
filteredData=allData.filter(item=>{
|
||||
const tid=String(item.task.id);
|
||||
|
|
@ -514,7 +374,7 @@ function renderTable(keepPage){
|
|||
const activeInternal=MARKS_CONFIG.find((m)=>isMark(tid,m.key))?.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&&mentionTaskIds&&!mentionTaskIds.has(tid)) 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;
|
||||
|
|
@ -530,12 +390,8 @@ function renderTable(keepPage){
|
|||
if(selectedInternal.size){
|
||||
if(!activeInternal || !selectedInternal.has(activeInternal)) return false;
|
||||
}
|
||||
const authorType=getAuthorType(item);
|
||||
if(statFilter!=='all'){
|
||||
if(authorType!==statFilter) return false;
|
||||
}else if(selectedWho.size&& !selectedWho.has(authorType)){
|
||||
return false;
|
||||
}
|
||||
const whoFilter=whoSelect!=='all'?whoSelect:statFilter;
|
||||
if(whoFilter!=='all'&&getAuthorType(item)!==whoFilter) return false;
|
||||
// Main tab filter
|
||||
if(mainTab==='open' && !isTaskOpen(item)) return false;
|
||||
if(mainTab==='done' && !isTaskDone(item)) return false;
|
||||
|
|
@ -558,57 +414,31 @@ function renderTable(keepPage){
|
|||
}
|
||||
|
||||
// Counts
|
||||
const markCounts=Object.fromEntries(MARKS_CONFIG.map((m)=>[m.key,0]));
|
||||
let baseLen=0;
|
||||
let baseUnmarked=0;
|
||||
let baseExternal=0;
|
||||
let baseInternal=0;
|
||||
let baseNoComment=0;
|
||||
let unreadTotal=0;
|
||||
let tabOpen=0;
|
||||
let tabDone=0;
|
||||
let tabEmpty=0;
|
||||
|
||||
allData.forEach((item)=>{
|
||||
if(isUnreadFast(item.task)) unreadTotal++;
|
||||
if(isTaskOpen(item)) tabOpen++;
|
||||
if(isTaskDone(item)) tabDone++;
|
||||
if(isTaskEmpty(item)) tabEmpty++;
|
||||
|
||||
if(!taskMatchesDate(item.task)) return;
|
||||
baseLen++;
|
||||
|
||||
const author=getAuthorType(item);
|
||||
if(author==='external') baseExternal++;
|
||||
else if(author==='internal') baseInternal++;
|
||||
else if(author==='nocomment') baseNoComment++;
|
||||
|
||||
const tid=String(item.task.id);
|
||||
const markedKey=MARKS_CONFIG.find((m)=>isMark(tid,m.key))?.key||'';
|
||||
if(markedKey) markCounts[markedKey]=(markCounts[markedKey]||0)+1;
|
||||
else baseUnmarked++;
|
||||
});
|
||||
|
||||
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', baseLen);
|
||||
MARKS_CONFIG.forEach((m)=>{ sv(`chip-${m.key}-cnt`, markCounts[m.key]||0); });
|
||||
sv('chip-unmarked-cnt', baseUnmarked);
|
||||
sv('stat-total', baseLen);
|
||||
sv('stat-external', baseExternal);
|
||||
sv('stat-internal', baseInternal);
|
||||
sv('stat-nocomment', baseNoComment);
|
||||
sv('stat-unread', unreadTotal);
|
||||
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', `\u00b7 ${filteredData.length} \u0437\u0430\u044f\u0432\u043e\u043a`);
|
||||
sv('topbar-sub', `· ${filteredData.length} заявок`);
|
||||
sv('total-loaded', filteredData.length);
|
||||
sv('total-count', allData.length);
|
||||
sv('tf-info', `${filteredData.length} \u0438\u0437 ${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', tabOpen);
|
||||
sv('tcnt-done', tabDone);
|
||||
sv('tcnt-empty', tabEmpty);
|
||||
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=`<div class="state-box"><span class="icon">🔍</span><div class="msg">Заявок не найдено</div><div class="sub">Измените фильтры</div></div>`;
|
||||
|
|
@ -621,8 +451,6 @@ function renderTable(keepPage){
|
|||
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 idxMap=new Map();
|
||||
allData.forEach((item,idx)=>idxMap.set(item,idx));
|
||||
|
||||
const sc=k=>visibleCols.has(k);
|
||||
const sortArrow = col => sortCol===col ? (sortDir==='asc' ? ' ↑' : ' ↓') : ' ↕';
|
||||
|
|
@ -643,8 +471,8 @@ function renderTable(keepPage){
|
|||
const {task,lastComment}=item;
|
||||
const tid=String(task.id);
|
||||
const num=task.tasknumber||task.id;
|
||||
const idx=idxMap.get(item);
|
||||
const unread=isUnreadFast(task);
|
||||
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();
|
||||
|
|
@ -1123,14 +951,10 @@ function refreshDetailIfOpen(tid){
|
|||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initMarkChips();
|
||||
syncChipDropdownItems();
|
||||
syncAdvancedFiltersUi();
|
||||
// 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');
|
||||
}
|
||||
if (!e.target.closest('.adv-filter-wrap')) {
|
||||
closeAdvancedFilterDropdowns();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue