1// funcionalidadeName is intentionally a `var` (not `const`) so this shared 2// script can coexist on the ERP page without a top-level redeclaration error. 3var funcionalidadeName = (window.__COGNIJUS_GP_PAGE__) 4 ? 'Gestão Processual' 5 : ((window.__COGNIJUS_ASSESSOR_LANG__ === 'en') ? 'Legal Advisor' : 'Assessor jurÃdico'); 6const __isEN = (window.__COGNIJUS_ASSESSOR_LANG__ === 'en'); 7const __L = { 8 attn: __isEN ? 'Attention' : 'Atenção', 9 choosePdf: __isEN ? 'Please select at least one PDF file.' : 'Escolha pelo menos um arquivo PDF.', 10 invalidPdf: (name) => __isEN ? `The file "${name}" is not a valid PDF.` : `O arquivo "${name}" não é um PDF válido.`, 11 invalidPages: __isEN ? 'Invalid page format. Use numbers separated by commas and hyphens for ranges (e.g.: 1, 3, 5-7)' : 'Formato de páginas inválido. Use números separados por vÃrgula e hÃfens para intervalos (Ex: 1, 3, 5-7)', 12 selectPersona: __isEN ? 'Please select a persona before analyzing.' : 'Selecione uma persona antes de analisar.', 13 selectPiece: __isEN ? 'Please select a draft before analyzing.' : 'Selecione uma peça antes de analisar.', 14 queued: __isEN ? 'Your analysis has been queued and will be processed shortly.' : 'Sua análise foi enfileirada e será processada em breve.', 15 queuedTitle: __isEN ? 'Analysis Queued' : 'Análise Enfileirada', 16 enabled: __isEN ? 'Enabled' : 'Ativada', 17 disabled: __isEN ? 'Disabled' : 'Desativada', 18 selectPiece2: __isEN ? 'Select a draft' : 'Selecione uma peça', 19}; 20 21function assessorHtmlNewlinesToBr(html) { 22 return String(html || '') 23 .replace(/<\/(p|div|h[1-6]|blockquote|ul|ol|li|table|thead|tbody|tr|hr)>\s*(?:\n|<br\s*\/?\s*>)+\s*/gi, '</$1>') 24 .replace(/(?:\n|<br\s*\/?\s*>)+\s*(<(?:p|div|h[1-6]|blockquote|ul|ol|table|hr)\b)/gi, '$1') 25 .replace(/(?:\n|<br\s*\/?\s*>)+\s*(<blockquote\b)/gi, '$1') 26 .replace(/(<\/blockquote>)\s*(?:\n|<br\s*\/?\s*>)+/gi, '$1') 27 .replace(/\n/g, '<br>'); 28} 29 30// Catálogo de peças/personas (config/assessor-config.json via /extension/assessor-config) 31var buttonConfig = []; 32let gestaoAssessorConfigLoaded = false; 33let gestaoUniquePersonas = []; 34 35function getAssessorLimits() { 36 return window.__COGNIJUS_ASSESSOR_LIMITS__ || { 37 maxPdfBytes: 104857600, 38 maxPdfBytesProMax: 262144000, 39 maxPdfMB: 100, 40 maxPdfMBProMax: 250, 41 uploadMaxMbFree: 100, 42 uploadMaxMbPro: 100, 43 uploadMaxMbProMax: 250 44 }; 45} 46 47function isAssessorUserPro() { 48 return !!(typeof cognijusUserData !== 'undefined' && cognijusUserData && cognijusUserData.isPro); 49} 50 51function isAssessorUserProMax() { 52 return !!(typeof cognijusUserData !== 'undefined' && cognijusUserData && cognijusUserData.isProMax); 53} 54 55function getAssessorUploadMaxBytes() { 56 const limits = getAssessorLimits(); 57 const mb = isAssessorUserProMax() ? limits.uploadMaxMbProMax : (isAssessorUserPro() ? limits.uploadMaxMbPro : limits.uploadMaxMbFree); 58 return mb * 1024 * 1024; 59} 60 61function getAssessorUploadMaxMbLabel() { 62 const limits = getAssessorLimits(); 63 return isAssessorUserProMax() ? limits.uploadMaxMbProMax : (isAssessorUserPro() ? limits.uploadMaxMbPro : limits.uploadMaxMbFree); 64} 65 66function formatAssessorFileSizeLabel(bytes) { 67 const n = Number(bytes) || 0; 68 if (n >= 1048576) return (n / 1048576).toFixed(1) + ' MB'; 69 return Math.round(n / 1024) + ' KB'; 70} 71 72function formatAssessorCharCountLabel(n) { 73 const chars = Number(n); 74 if (!Number.isFinite(chars) || chars <= 0) return ''; 75 if (chars < 1000) return chars + ' Chars'; 76 return Math.round(chars / 1000) + 'K Chars'; 77} 78 79function buildGestaoSizeCellHtml(p) { 80 if (!p || !p.fileSize) return 'â'; 81 const sizeLabel = formatAssessorFileSizeLabel(p.fileSize); 82 const charsLabel = formatAssessorCharCountLabel(p.charCount); 83 return `<div class="gp-size-stack"><span class="gp-size-bytes">${sizeLabel}</span>${charsLabel ? `<span class="gp-size-chars">${charsLabel}</span>` : ''}</div>`; 84} 85 86function assessorFileTooLargeMessage(fileOrLabel, sizeBytes) { 87 const label = typeof fileOrLabel === 'string' ? fileOrLabel : fileOrLabel.name; 88 const size = typeof sizeBytes === 'number' ? sizeBytes : (fileOrLabel.size || 0); 89 const limitMb = getAssessorUploadMaxMbLabel(); 90 return __isEN 91 ? `"${label}" (${formatAssessorFileSizeLabel(size)}) exceeds the ${limitMb} MB limit for your plan. Use Focus Pages or a smaller PDF.` 92 : `"${label}" (${formatAssessorFileSizeLabel(size)}) excede o limite de ${limitMb} MB do seu plano. Use Páginas em Foco ou um PDF menor.`; 93} 94 95function validateAssessorFileForAnalysis(file, options) { 96 if (!file) return false;
97 const uploadMax = getAssessorUploadMaxBytes(); 98 const size = file.size || 0; 99 const useFuncMsg = !!(options && options.useFuncMsg); 100 101 if (size > uploadMax) { 102 if (useFuncMsg) { 103 funcMsg('warning', __L.attn, assessorFileTooLargeMessage(file), '', '', undefined, options && options.insight); 104 } 105 return false; 106 } 107 108 return true; 109} 110 111function validateAssessorFilesForAnalysis(files, options) { 112 if (!Array.isArray(files) || files.length === 0) return false; 113 let totalBytes = 0; 114 for (const file of files) { 115 if (!validateAssessorFileForAnalysis(file, options)) return false; 116 totalBytes += file.size || 0; 117 } 118 if (totalBytes > getAssessorUploadMaxBytes()) { 119 const msg = assessorFileTooLargeMessage( 120 __isEN ? 'Selected files (combined)' : 'Arquivos selecionados (total)', 121 totalBytes 122 ); 123 if (options && options.useFuncMsg) { 124 funcMsg('warning', __L.attn, msg, '', '', undefined, options && options.insight); 125 } 126 return false; 127 } 128 return true; 129} 130 131function validateAssessorFileForStorage(file, options) { 132 return validateAssessorFileForAnalysis(file, options); 133} 134 135function escapeGestaoHtml(text) { 136 return String(text || '') 137 .replace(/&/g, '&') 138 .replace(/</g, '<') 139 .replace(/>/g, '>') 140 .replace(/"/g, '"'); 141} 142 143// Busboy (latin1) + Finder (NFD/UTF-8) grava "AnotacçoÃÂes" / "jurisprudeÃÂncia". 144// Reinterpreta code points 0-255 como UTF-8 quando o decode é válido; NFC 145// depois, para o @ bater com o teclado. Espelha normalizeAssessorFilename. 146function normalizeGestaoFilename(name) { 147 const raw = String(name == null ? '' : name); 148 if (!raw) return ''; 149 let hasBeyondLatin1 = false; 150 for (let i = 0; i < raw.length; i++) { 151 if (raw.charCodeAt(i) > 255) { 152 hasBeyondLatin1 = true; 153 break; 154 } 155 } 156 if (!hasBeyondLatin1) { 157 try { 158 const bytes = new Uint8Array(raw.length); 159 for (let i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i); 160 const decoded = new TextDecoder('utf-8', { fatal: true }).decode(bytes); 161 return decoded.normalize('NFC'); 162 } catch (_) { /* nome já era Unicode NFC (ç/õ), não mojibake */ } 163 } 164 try { 165 return raw.normalize('NFC'); 166 } catch (_) { 167 return raw; 168 } 169} 170 171// Arquivos individuais que formaram um "Processo unificado". Uploads antigos só 172// têm originalFileNames (sem fronteiras de página) e caem no fallback. 173// Espelha resolveAssessorMergedParts no servidor. 174function gestaoResolveMergedParts(p) { 175 if (!p) return []; 176 if (Array.isArray(p.mergedParts) && p.mergedParts.length > 1) { 177 return p.mergedParts 178 .filter(part => part && part.name) 179 .map((part, index) => ({ 180 index: Number.isInteger(part.index) ? part.index : index, 181 name: normalizeGestaoFilename(part.name), 182 originalName: normalizeGestaoFilename(part.originalName || part.name), 183 convertedFromWord: !!part.convertedFromWord, 184 size: Number(part.size) || 0, 185 startPage: Number(part.startPage) || 0, 186 endPage: Number(part.endPage) || 0 187 })); 188 } 189 const names = Array.isArray(p.originalFileNames) ? p.originalFileNames.filter(Boolean) : []; 190 if (names.length > 1) { 191 return names.map((name, index) => ({ 192 index, 193 name: normalizeGestaoFilename(name), 194 originalName: normalizeGestaoFilename(name), 195 convertedFromWord: false, 196 size: 0, 197 startPage: 0, 198 endPage: 0 199 })); 200 } 201 return []; 202} 203 204function gestaoPartPagesLabel(part) { 205 if (!part || !part.startPage || !part.endPage) return ''; 206 if (part.startPage === part.endPage) return `${__isEN ? 'p.' : 'fl.'} ${part.startPage}`; 207 return `${__isEN ? 'pp.' : 'fls.'} ${part.startPage}-${part.endPage}`; 208} 209 210function formatGestaoLogField(value) { 211 return typeof formatStringArray === 'function' ? formatStringArray(value) : String(value || ''); 212} 213 214function logGestaoFuncMsg(tipo, titulo, msg, id, value) { 215 fetch(base_url + '/funcMsg', { 216 method: 'post', 217 headers: { 'content-type': 'application/json' }, 218 body: JSON.stringify({ 219 codigoUnico, 220 timestamp, 221 funcionalidadeName, 222 tipo, 223 msg: formatGestaoLogField(msg), 224 id: formatGestaoLogField(id), 225 value: formatGestaoLogField(value),
226 // Success/info toasts (fila e peca pronta) nao precisam de email ao admin. 227 notifyAdmin: tipo === 'error' || tipo === 'warning' 228 }) 229 }).catch(() => { }); 230} 231 232function ensureGestaoToastStack() { 233 let stack = document.getElementById('gestaoToastStack'); 234 if (!stack) { 235 stack = document.createElement('div'); 236 stack.id = 'gestaoToastStack'; 237 stack.className = 'gestao-toast-stack'; 238 stack.setAttribute('aria-live', 'polite'); 239 document.body.appendChild(stack); 240 } else if (stack.parentNode !== document.body) { 241 document.body.appendChild(stack); 242 } 243 return stack; 244} 245 246function showGestaoToast(titulo, msg, msOrOpts) { 247 const stack = ensureGestaoToastStack(); 248 249 let ms = 6000; 250 let variant = ''; 251 if (typeof msOrOpts === 'number') { 252 ms = msOrOpts; 253 } else if (msOrOpts && typeof msOrOpts === 'object') { 254 if (msOrOpts.ms != null) ms = msOrOpts.ms; 255 if (msOrOpts.variant) variant = msOrOpts.variant; 256 } 257 258 const el = document.createElement('div'); 259 el.className = 'gestao-toast' + (variant ? ` gestao-toast--${variant}` : ''); 260 el.innerHTML = `<div class="gestao-toast-title">${escapeGestaoHtml(titulo)}</div><div class="gestao-toast-body">${escapeGestaoHtml(msg)}</div>`; 261 stack.appendChild(el); 262 requestAnimationFrame(() => requestAnimationFrame(() => el.classList.add('show'))); 263 264 setTimeout(() => { 265 el.classList.remove('show'); 266 setTimeout(() => el.remove(), 320); 267 }, ms); 268} 269 270function gestaoSuccessNotify(titulo, msg, id, value) { 271 showGestaoToast(titulo, msg); 272 logGestaoFuncMsg('success', titulo, msg, id || '', value || ''); 273} 274 275function gestaoDisplayFilename(filename) { 276 try { 277 return decodeURIComponent(String(filename || '').replace(/\+/g, ' ')); 278 } catch (_) { 279 return filename || ''; 280 } 281} 282 283const gestaoWatchedJobs = new Map(); 284const gestaoNotifiedJobIds = new Set(); 285const GESTAO_WATCHED_JOBS_STORAGE_KEY = 'cognijus_gestao_watched_jobs'; 286 287function persistGestaoWatchedJobs() { 288 try { 289 const jobs = []; 290 gestaoWatchedJobs.forEach((value, id) => { 291 jobs.push({ 292 id, 293 lastStatus: value.lastStatus != null ? value.lastStatus : null, 294 lastPecaCount: typeof value.lastPecaCount === 'number' ? value.lastPecaCount : -1 295 }); 296 }); 297 if (jobs.length) { 298 sessionStorage.setItem(GESTAO_WATCHED_JOBS_STORAGE_KEY, JSON.stringify({ jobs })); 299 } else { 300 sessionStorage.removeItem(GESTAO_WATCHED_JOBS_STORAGE_KEY); 301 } 302 } catch (_) { /* quota / private mode */ } 303} 304 305function rehydrateGestaoWatchedJobsFromSession() { 306 try { 307 const raw = sessionStorage.getItem(GESTAO_WATCHED_JOBS_STORAGE_KEY); 308 if (!raw) return; 309 const parsed = JSON.parse(raw); 310 if (!parsed || !Array.isArray(parsed.jobs)) return; 311 parsed.jobs.forEach((entry) => { 312 const key = String(entry.id || '').trim(); 313 if (!key) return; 314 gestaoWatchedJobs.set(key, { 315 lastStatus: entry.lastStatus != null ? entry.lastStatus : null, 316 lastPecaCount: typeof entry.lastPecaCount === 'number' ? entry.lastPecaCount : -1 317 }); 318 }); 319 } catch (_) { /* ignore corrupt storage */ } 320} 321 322function gestaoProcessoId(p) { 323 if (!p || p._id == null) return ''; 324 const id = p._id; 325 if (typeof id === 'object') { 326 return String(id.$oid || id.toString?.() || id); 327 } 328 return String(id); 329} 330 331function gestaoPecaCount(p) { 332 return Array.isArray(p.pecas) ? p.pecas.length : (p.hasPeca ? 1 : 0); 333} 334 335function gestaoTrackJob(id, options) { 336 const key = String(id || '').trim(); 337 if (!key) return; 338 gestaoWatchedJobs.set(key, { 339 lastStatus: options && options.markPendenteSeen ? 'pendente' : null, 340 lastPecaCount: -1 341 }); 342 gestaoNotifiedJobIds.delete(key); 343 persistGestaoWatchedJobs(); 344} 345 346function gestaoUntrackJob(id) { 347 const key = String(id || '').trim(); 348 if (!key || !gestaoWatchedJobs.has(key)) return; 349 gestaoWatchedJobs.delete(key); 350 persistGestaoWatchedJobs(); 351} 352 353function gestaoStatusToastCopy(status, fname) { 354 const name = fname || (__isEN ? 'Your case' : 'Seu processo'); 355 if (__isEN) { 356 switch (status) { 357 case 'pendente': return { title: 'Pending', msg: `${name} is queued.` }; 358 case 'processando': return { title: 'Processing', msg: `Generating draft for ${name}...` }; 359 case 'concluido': return { title: 'Draft ready', msg: `${name} is ready in Batch Analysis.` }; 360 case 'erro': return { title: 'Error', msg: `${name} could not be processed.` }; 361 default: return { title: 'Update', msg: name }; 362 } 363 } 364 switch (status) { 365 case 'pendente': return { title: 'Pendente', msg: `${name} aguardando processamento.` }; 366 case 'processando': return { title: 'Processando', msg: `Gerando peça para ${name}...` }; 367 case 'concluido': return { title: 'Peça gerada', msg: `${name} disponÃvel em Análise em Lote.` }; 368 case 'erro': return { title: 'Erro', msg: `Não foi possÃvel processar ${name}.` }; 369 default: return { title: 'Atualização', msg: name }; 370 } 371} 372 373function gestaoResolveJobErrorMessage(p) { 374 return String(p?.errorMessage || p?.lastError || '').trim(); 375} 376 377function gestaoIsDocumentTooLargeError(p, msg) { 378 const reason = String(p?.lastErrorReason || '').trim(); 379 if (reason === 'document_too_large') return true; 380 const text = String(msg || gestaoResolveJobErrorMessage(p)).toLowerCase(); 381 return /muito extenso|token_limit|muitas páginas|grande demais|too large|too many pages/i.test(text); 382} 383 384function gestaoNotifyJobError(p, fname) { 385 const name = fname || gestaoDisplayFilename(p?.filename); 386 const detail = gestaoResolveJobErrorMessage(p); 387 const isDocLarge = gestaoIsDocumentTooLargeError(p, detail); 388 const fallback = gestaoStatusToastCopy('erro', name); 389 390 let title; 391 let body; 392 if (detail) { 393 title = isDocLarge 394 ? (__isEN ? 'Document too large' : 'Documento muito extenso') 395 : (__isEN ? 'Processing failed' : 'Falha no processamento'); 396 body = detail; 397 } else { 398 title = fallback.title; 399 body = fallback.msg; 400 } 401 402 showGestaoToast(title, body, { 403 ms: detail ? 14000 : 8000, 404 variant: isDocLarge ? 'warning' : 'error' 405 }); 406 407 if (isDocLarge && !isAssessorUserPro() && typeof openCogniJUSProModal === 'function') { 408 openCogniJUSProModal(detail || (__isEN 409 ? 'This document exceeds the free plan limit. Upgrade to PRO or specify the most relevant pages.' 410 : 'Este documento excede o limite do plano gratuito. Assine o PRO ou indique as páginas mais relevantes.')); 411 } 412 413 logGestaoFuncMsg( 414 isDocLarge ? 'warning' : 'error', 415 title, 416 body,
417 gestaoProcessoId(p) || '', 418 JSON.stringify({ 419 processoId: gestaoProcessoId(p), 420 filename: p?.filename, 421 status: 'erro', 422 errorMessage: detail, 423 lastErrorReason: p?.lastErrorReason || '' 424 }) 425 ); 426} 427 428function gestaoProcessWatchedJob(p) { 429 const id = gestaoProcessoId(p); 430 if (!id || !gestaoWatchedJobs.has(id)) return; 431 432 const watched = gestaoWatchedJobs.get(id); 433 const status = p.status || 'pendente'; 434 const pecaCount = gestaoPecaCount(p); 435 const fname = gestaoDisplayFilename(p.filename); 436 437 if (watched.lastStatus !== status) { 438 const copy = gestaoStatusToastCopy(status, fname); 439 if (status === 'concluido' && pecaCount > 0) { 440 if (!gestaoNotifiedJobIds.has(id)) { 441 gestaoNotifiedJobIds.add(id); 442 gestaoSuccessNotify(copy.title, copy.msg, id, JSON.stringify({ processoId: id, filename: p.filename, pecaCount, status })); 443 } 444 gestaoUntrackJob(id); 445 } else if (status === 'erro') { 446 gestaoNotifyJobError(p, fname); 447 gestaoUntrackJob(id); 448 } else { 449 showGestaoToast(copy.title, copy.msg); 450 } 451 watched.lastStatus = status; 452 persistGestaoWatchedJobs(); 453 } else if (status === 'concluido' && pecaCount > watched.lastPecaCount && pecaCount > 0 && !gestaoNotifiedJobIds.has(id)) { 454 gestaoNotifiedJobIds.add(id); 455 const copy = gestaoStatusToastCopy('concluido', fname); 456 gestaoSuccessNotify(copy.title, copy.msg, id, JSON.stringify({ processoId: id, filename: p.filename, pecaCount, status })); 457 gestaoUntrackJob(id); 458 } 459 460 watched.lastPecaCount = pecaCount; 461 if (gestaoWatchedJobs.has(id)) persistGestaoWatchedJobs(); 462} 463 464function gestaoNotifyCompleted(p) { 465 const id = gestaoProcessoId(p); 466 if (!id || gestaoNotifiedJobIds.has(id)) return; 467 const pecaCount = gestaoPecaCount(p); 468 if (p.status !== 'concluido' || pecaCount === 0) return; 469 470 gestaoNotifiedJobIds.add(id); 471 gestaoUntrackJob(id); 472 473 const fname = gestaoDisplayFilename(p.filename); 474 const copy = gestaoStatusToastCopy('concluido', fname); 475 gestaoSuccessNotify( 476 copy.title, 477 copy.msg, 478 id, 479 JSON.stringify({ processoId: id, filename: p.filename, pecaCount }) 480 ); 481 if (typeof window.cjErpRefreshAfterAssessor === 'function') window.cjErpRefreshAfterAssessor(); 482} 483 484var selectedFiles = [] // Array de arquivos PDF selecionados (suporta múltiplos) 485var formData = new FormData() 486let isRequestInProgress = false // Flag to prevent multiple simultaneous requests 487 488// Check if user needs to login before uploading 489function checkAuthenticationBeforeAction() { 490 if (!cognijusUserToken) { 491 openCogniJUSModal(); 492 return false; 493 } 494 return true; 495} 496 497// Function to toggle the intro dropdown 498function toggleIntroDropdown() { 499 const dropdownContent = document.getElementById('dropdownContent') 500 const dropdownArrow = document.getElementById('dropdownArrow') 501 502 if (dropdownContent.classList.contains('expanded')) { 503 dropdownContent.classList.remove('expanded') 504 dropdownArrow.classList.remove('rotated') 505 } else { 506 dropdownContent.classList.add('expanded') 507 dropdownArrow.classList.add('rotated') 508 } 509} 510 511// Function to validate page input format 512function validatePageInput(input, showError = true) { 513 const value = input.value.trim(); 514 const errorDiv = document.getElementById('pageInputError'); 515 516 // If empty, it's valid (optional field) 517 if (value === '') { 518 if (showError) { 519 errorDiv.style.display = 'none'; 520 input.classList.remove('input-error'); 521 } 522 return true; 523 } 524 525 // Regular expression to validate the format: numbers, commas, and hyphens only 526 const validFormat = /^[\d\s,\-]+$/.test(value); 527 528 if (!validFormat) { 529 if (showError) { 530 errorDiv.style.display = 'block'; 531 input.classList.add('input-error'); 532 } 533 return false; 534 } 535 536 // Additional validation: check if the structure makes sense 537 const parts = value.split(',').map(p => p.trim()); 538 let isValid = true; 539 540 for (const part of parts) { 541 if (part.includes('-')) { 542 const rangeParts = part.split('-'); 543 if (rangeParts.length !== 2) { 544 isValid = false;
545 break; 546 } 547 const start = parseInt(rangeParts[0].trim()); 548 const end = parseInt(rangeParts[1].trim()); 549 if (isNaN(start) || isNaN(end) || start > end || start <= 0) { 550 isValid = false; 551 break; 552 } 553 } else { 554 const page = parseInt(part); 555 if (isNaN(page) || page <= 0) { 556 isValid = false; 557 break; 558 } 559 } 560 } 561 562 if (!isValid) { 563 if (showError) { 564 errorDiv.style.display = 'block'; 565 input.classList.add('input-error'); 566 } 567 return false; 568 } 569 570 // If we get here, the format is valid 571 if (showError) { 572 errorDiv.style.display = 'none'; 573 input.classList.remove('input-error'); 574 } 575 return true; 576} 577 578// Function to parse page ranges (e.g., "1, 3, 5-7") 579function parsePageRanges(pageString) { 580 if (!pageString || pageString.trim() === '') return []; 581 582 const pages = []; 583 const parts = pageString.split(',').map(p => p.trim()); 584 585 for (const part of parts) { 586 if (part.includes('-')) { 587 const [start, end] = part.split('-').map(p => parseInt(p.trim())); 588 if (!isNaN(start) && !isNaN(end) && start <= end) { 589 for (let i = start; i <= end; i++) { 590 pages.push(i); 591 } 592 } 593 } else { 594 const page = parseInt(part); 595 if (!isNaN(page)) { 596 pages.push(page); 597 } 598 } 599 } 600 601 return [...new Set(pages)].sort((a, b) => a - b); 602} 603 604// Legacy individual analysis upload flow was removed. Gestão Processual uses the batch upload functions below. 605 606// ============================================================================ 607// PROMPT BANK (additional instructions history) 608// ============================================================================ 609 610let assessorPromptHistory = []; 611let promptHistoryFetchSeq = 0; 612let promptHistoryAuthState = null; 613let promptHistoryLoading = false; 614let promptHistoryLoadedOnce = false; 615let promptHistoryExpanded = false; 616const PROMPT_HISTORY_INITIAL_LIMIT = 3; 617 618function getErpActiveOrgId() { 619 try { 620 return typeof window.cjErpGetActiveOrgId === 'function' ? window.cjErpGetActiveOrgId() : null; 621 } catch (_) { 622 return null; 623 } 624} 625 626function getPromptHistoryCacheKey() { 627 const owner = (cognijusUserData && cognijusUserData.email) || cognijusUserToken || 'anon'; 628 const orgId = getErpActiveOrgId(); 629 return orgId ? ('assessorPromptBank:' + owner + ':erp:' + orgId) : ('assessorPromptBank:' + owner); 630} 631 632function mapErpPromptsToAssessorHistory(items) { 633 return (items || []).map(item => ({ 634 _id: item.id || item._id, 635 prompt: item.content || '', 636 title: item.title || '', 637 timestamp: (item.metadata && item.metadata.assessorUsedAt) || item.updatedAt || item.createdAt || new Date().toISOString() 638 })); 639} 640 641window.cjAssessorResetPromptHistory = function () { 642 promptHistoryLoadedOnce = false; 643 assessorPromptHistory = []; 644 try { sessionStorage.removeItem(getPromptHistoryCacheKey()); } catch (_) { /* noop */ } 645}; 646 647window.cjAssessorRefreshPromptBank = function () { 648 window.cjAssessorResetPromptHistory(); 649 const modal = document.getElementById('gestaoInstructionModal'); 650 if (modal && !modal.hidden && typeof loadGestaoInstructionBank === 'function') { 651 loadGestaoInstructionBank(); 652 } 653}; 654 655function readPromptHistoryCache() { 656 try { 657 const raw = sessionStorage.getItem(getPromptHistoryCacheKey()); 658 if (!raw) return null; 659 const parsed = JSON.parse(raw); 660 return Array.isArray(parsed?.prompts) ? parsed.prompts : null; 661 } catch (_) { 662 return null; 663 } 664} 665 666function writePromptHistoryCache(prompts) { 667 try { 668 sessionStorage.setItem(getPromptHistoryCacheKey(), JSON.stringify({ 669 prompts: prompts || [], 670 ts: Date.now() 671 })); 672 } catch (_) { /* quota / private mode */ } 673} 674 675function hidePromptHistorySection() { 676 const section = document.getElementById('promptHistorySection'); 677 const content = document.getElementById('promptHistoryContent'); 678 const showMore = document.getElementById('promptHistoryShowMore'); 679 if (section) { 680 section.style.display = 'none'; 681 section.classList.remove('is-loading'); 682 section.setAttribute('aria-busy', 'false'); 683 delete section.dataset.promptShellReady; 684 } 685 if (content) content.hidden = true; 686 if (showMore) { 687 showMore.innerHTML = ''; 688 showMore.hidden = true; 689 } 690 promptHistoryExpanded = false;
691 syncPromptHistoryToggleUi(); 692} 693 694function syncPromptHistoryToggleUi() { 695 const content = document.getElementById('promptHistoryContent'); 696 const showMore = document.getElementById('promptHistoryShowMore'); 697 const toggleIcon = document.getElementById('promptHistoryToggleIcon'); 698 const toggleBtn = document.querySelector('.assessor-prompt-history-toggle'); 699 const expanded = !!(content && !content.hidden); 700 promptHistoryExpanded = expanded; 701 if (toggleIcon) toggleIcon.className = expanded ? 'fa fa-chevron-up' : 'fa fa-chevron-down'; 702 if (toggleBtn) toggleBtn.setAttribute('aria-expanded', expanded ? 'true' : 'false'); 703 if (showMore) showMore.hidden = !(expanded && showMore.innerHTML); 704} 705 706function updatePromptHistoryHeaderCount(count, loading) { 707 const label = document.getElementById('promptHistoryLabel'); 708 const header = document.getElementById('promptHistoryHeader'); 709 const section = document.getElementById('promptHistorySection'); 710 if (!label) return; 711 712 const base = __isEN ? 'Prompt bank' : 'Banco de prompts'; 713 let suffix = ''; 714 if (loading) { 715 suffix = `<span class="assessor-prompt-history-status">${__isEN ? 'Loadingâ¦' : 'Carregandoâ¦'}</span>`; 716 } else if (promptHistoryLoadedOnce && typeof count === 'number') { 717 suffix = `<span class="assessor-prompt-history-count">(${count})</span>`; 718 } 719 label.innerHTML = `<i class="fa fa-history" aria-hidden="true"></i> ${base}${suffix ? ' ' + suffix : ''}`; 720 721 if (header) header.classList.toggle('is-loading', !!loading); 722 if (section) { 723 section.classList.toggle('is-loading', !!loading); 724 section.setAttribute('aria-busy', loading ? 'true' : 'false'); 725 } 726} 727 728function showPromptHistoryShell(options) { 729 const opts = options || {}; 730 const section = document.getElementById('promptHistorySection'); 731 if (!section || !isAssessorUserProForPromptHistory()) return; 732 733 section.style.display = ''; 734 updatePromptHistoryHeaderCount( 735 typeof opts.count === 'number' ? opts.count : assessorPromptHistory.length, 736 !!opts.loading 737 ); 738 739 if (!opts.preserveExpansion && !section.dataset.promptShellReady) { 740 const content = document.getElementById('promptHistoryContent'); 741 const showMore = document.getElementById('promptHistoryShowMore'); 742 if (content) content.hidden = true; 743 if (showMore) { 744 showMore.innerHTML = ''; 745 showMore.hidden = true; 746 } 747 promptHistoryExpanded = false; 748 syncPromptHistoryToggleUi(); 749 section.dataset.promptShellReady = '1'; 750 } 751} 752 753function isAssessorUserProForPromptHistory() { 754 return !!(cognijusUserData && cognijusUserData.isPro); 755} 756 757function isPromptHistoryAuthReady() { 758 return !!(cognijusUserToken && cognijusUserData && !window.__cognijusAuthInProgress); 759} 760 761function escapePromptHistoryHtml(text) { 762 const div = document.createElement('div'); 763 div.textContent = text; 764 return div.innerHTML; 765} 766 767async function fetchPromptHistoryFromDB() { 768 if (!isPromptHistoryAuthReady() || !isAssessorUserProForPromptHistory()) return []; 769 770 const orgId = getErpActiveOrgId(); 771 if (orgId && typeof window.cjErpFetchOrgPrompts === 'function') { 772 try { 773 const items = await window.cjErpFetchOrgPrompts(); 774 return mapErpPromptsToAssessorHistory(items); 775 } catch (e) { 776 console.error('Error fetching ERP org prompts:', e); 777 return []; 778 } 779 } 780 781 try { 782 const response = await fetch('/assessor_prompt_history', { 783 method: 'POST', 784 headers: { 785 'Content-Type': 'application/json', 786 'Authorization': `Bearer ${cognijusUserToken}` 787 }, 788 body: JSON.stringify({ 789 funcionalidadeName, 790 codigoUnico: typeof codigoUnico !== 'undefined' ? codigoUnico : undefined 791 }) 792 }); 793 794 if (response.ok) { 795 const data = await response.json(); 796 return data.prompts || []; 797 } 798 return []; 799 } catch (e) { 800 console.error('Error fetching assessor prompt history:', e); 801 return []; 802 } 803} 804 805function formatPromptHistoryShowMoreLabel(remaining) { 806 if (__isEN) { 807 return `Show ${remaining} more prompt${remaining === 1 ? '' : 's'}`; 808 } 809 return `Ver mais ${remaining} prompt${remaining === 1 ? '' : 's'}`; 810} 811 812function buildPromptHistoryItemHTML(item, index) { 813 const date = new Date(item.timestamp); 814 const locale = __isEN ? 'en-US' : 'pt-BR'; 815 const formattedDate = date.toLocaleDateString(locale) + ' ' + date.toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit' }); 816 const promptText = item.prompt || ''; 817 const titleText = String(item.title || '').trim(); 818 const preview = titleText 819 ? escapePromptHistoryHtml(titleText) 820 : escapePromptHistoryHtml(promptText.substring(0, 200)) + (promptText.length > 200 ? 'â¦' : ''); 821 const reuseTitle = __isEN ? 'Reuse this prompt' : 'Reutilizar este prompt'; 822 return ` 823 <div class="assessor-prompt-history-item" data-index="${index}"> 824 <div class="assessor-prompt-history-item-body">
825 <span class="assessor-prompt-history-item-date"><i class="fa fa-clock-o"></i> ${formattedDate}</span> 826 <div class="assessor-prompt-history-item-text">${preview}</div> 827 </div> 828 <div class="assessor-prompt-history-item-actions"> 829 <button class="assessor-prompt-history-item-btn reuse" onclick="reusePromptHistoryItem(${index})" title="${reuseTitle}" type="button"> 830 <i class="fa fa-refresh"></i> 831 </button> 832 </div> 833 </div> 834 `; 835} 836 837function upsertLocalPromptHistory(promptText, promptTitle) { 838 const prompt = String(promptText || '').trim(); 839 if (!prompt) return; 840 const title = String(promptTitle || '').trim(); 841 const existingIdx = assessorPromptHistory.findIndex(item => item.prompt === prompt); 842 if (existingIdx >= 0) assessorPromptHistory.splice(existingIdx, 1); 843 assessorPromptHistory.unshift({ 844 _id: 'local-' + Date.now(), 845 prompt, 846 title, 847 timestamp: new Date().toISOString() 848 }); 849} 850 851function renderPromptHistory(history, showAll) { 852 const section = document.getElementById('promptHistorySection'); 853 const content = document.getElementById('promptHistoryContent'); 854 const showMore = document.getElementById('promptHistoryShowMore'); 855 856 if (!section || !content) return; 857 858 if (!isAssessorUserProForPromptHistory()) { 859 hidePromptHistorySection(); 860 return; 861 } 862 863 showPromptHistoryShell({ count: (history || []).length, loading: false, preserveExpansion: true }); 864 const wasExpanded = promptHistoryExpanded; 865 866 if (!history || history.length === 0) { 867 content.innerHTML = `<div class="assessor-prompt-history-empty">${__isEN 868 ? 'No saved prompts yet. Submit an analysis with additional instructions to build your bank.' 869 : 'Nenhum prompt salvo ainda. Envie uma análise com instruções adicionais para montar seu banco.'}</div>`; 870 } else { 871 const displayHistory = showAll ? history : history.slice(0, PROMPT_HISTORY_INITIAL_LIMIT); 872 content.innerHTML = displayHistory.map((item, index) => buildPromptHistoryItemHTML(item, index)).join(''); 873 874 if (showMore) { 875 if (!showAll && history.length > PROMPT_HISTORY_INITIAL_LIMIT) { 876 const remaining = history.length - PROMPT_HISTORY_INITIAL_LIMIT; 877 showMore.innerHTML = ` 878 <div class="assessor-prompt-history-show-more"> 879 <button class="assessor-prompt-history-show-more-btn" onclick="renderPromptHistory(assessorPromptHistory, true)" type="button"> 880 <i class="fa fa-chevron-down"></i> ${formatPromptHistoryShowMoreLabel(remaining)} 881 </button> 882 </div> 883 `; 884 } else { 885 showMore.innerHTML = ''; 886 } 887 } 888 } 889 890 content.hidden = !wasExpanded; 891 if (showMore) showMore.hidden = !(wasExpanded && showMore.innerHTML); 892 syncPromptHistoryToggleUi(); 893} 894 895function togglePromptHistory() { 896 const content = document.getElementById('promptHistoryContent'); 897 const showMore = document.getElementById('promptHistoryShowMore'); 898 if (!content) return; 899 900 const willExpand = content.hidden; 901 content.hidden = !willExpand; 902 if (showMore) showMore.hidden = !(willExpand && showMore.innerHTML); 903 syncPromptHistoryToggleUi(); 904} 905 906function reusePromptHistoryItem(index) { 907 const item = assessorPromptHistory[index]; 908 const textarea = document.getElementById('additionalPrompt'); 909 if (!item || !textarea) return; 910 911 textarea.value = item.prompt || ''; 912 textarea.scrollIntoView({ behavior: 'smooth', block: 'center' }); 913 setTimeout(() => textarea.focus(), 400); 914} 915 916async function loadAndRenderPromptHistory(forceReload) { 917 if (!isPromptHistoryAuthReady()) return; 918 if (!isAssessorUserProForPromptHistory()) { 919 assessorPromptHistory = []; 920 hidePromptHistorySection(); 921 return; 922 } 923 if (promptHistoryLoading && !forceReload) return; 924 925 const cached = readPromptHistoryCache(); 926 if (cached && !promptHistoryLoadedOnce && !getErpActiveOrgId()) { 927 assessorPromptHistory = cached; 928 promptHistoryLoadedOnce = true; 929 showPromptHistoryShell({ count: cached.length, loading: true }); 930 renderPromptHistory(assessorPromptHistory); 931 } else { 932 showPromptHistoryShell({ count: assessorPromptHistory.length, loading: true }); 933 } 934 935 const seq = ++promptHistoryFetchSeq; 936 promptHistoryLoading = true; 937 938 try { 939 const prompts = await fetchPromptHistoryFromDB(); 940 if (seq !== promptHistoryFetchSeq) return; 941 942 assessorPromptHistory = prompts; 943 promptHistoryLoadedOnce = true; 944 writePromptHistoryCache(prompts); 945 renderPromptHistory(assessorPromptHistory); 946 } finally { 947 if (seq === promptHistoryFetchSeq) { 948 promptHistoryLoading = false;
949 updatePromptHistoryHeaderCount(assessorPromptHistory.length, false); 950 } 951 } 952} 953 954function syncPromptHistoryWithAuth() { 955 if (!cognijusUserToken) { 956 promptHistoryAuthState = 'logged-out'; 957 assessorPromptHistory = []; 958 hidePromptHistorySection(); 959 return; 960 } 961 if (!isPromptHistoryAuthReady()) return; 962 963 const nextState = isAssessorUserProForPromptHistory() ? 'pro' : 'free'; 964 if (nextState === promptHistoryAuthState && nextState !== 'pro') return; 965 966 if (nextState === 'pro') { 967 showPromptHistoryShell({ 968 count: assessorPromptHistory.length, 969 loading: !promptHistoryLoadedOnce 970 }); 971 if (promptHistoryAuthState !== 'pro' || !promptHistoryLoadedOnce) { 972 loadAndRenderPromptHistory(true); 973 } else { 974 renderPromptHistory(assessorPromptHistory); 975 } 976 } else { 977 promptHistoryLoadedOnce = false; 978 assessorPromptHistory = []; 979 hidePromptHistorySection(); 980 } 981 982 promptHistoryAuthState = nextState; 983} 984 985// Legacy individual-only prompt/anonymization restrictions were removed. 986 987// ============================================================================ 988// TABS NAVIGATION - Assessor Unificado 989// ============================================================================ 990 991// Initialize tabs navigation 992function initializeAssessorTabs() { 993 const tabItems = document.querySelectorAll('.assessor-tab-item'); 994 const tabPanes = document.querySelectorAll('.assessor-tab-pane'); 995 996 tabItems.forEach(item => { 997 item.addEventListener('click', function () { 998 tabItems.forEach(t => t.classList.remove('active')); 999 tabPanes.forEach(p => p.classList.remove('active')); 1000 this.classList.add('active'); 1001 const tabId = this.getAttribute('data-tab'); 1002 const targetPane = document.getElementById(tabId); 1003 if (targetPane) targetPane.classList.add('active'); 1004 1005 if (!window.__cjErpModuleSyncLock && typeof window.cjErpActivateModule === 'function') { 1006 window.cjErpActivateModule(tabId); 1007 } 1008 1009 // Scroll to top of tabs so the content is visible 1010 const tabsNav = document.querySelector('.assessor-tabs-navigation'); 1011 if (tabsNav) { 1012 setTimeout(function () { 1013 tabsNav.scrollIntoView({ behavior: 'smooth', block: 'start' }); 1014 }, 50); 1015 } 1016 1017 // Carrega dados quando necessário 1018 if (tabId === 'meus-processos') { 1019 loadProcessosGestao(); 1020 } 1021 }); 1022 }); 1023 1024 // Check URL param for tab selection 1025 const urlParams = new URLSearchParams(window.location.search); 1026 const tabParam = urlParams.get('tab'); 1027 if (tabParam) { 1028 const targetTab = document.querySelector(`.assessor-tab-item[data-tab="${tabParam}"]`); 1029 if (targetTab) { 1030 targetTab.click(); 1031 } 1032 } 1033} 1034 1035// ============================================================================ 1036// GESTÃO PROCESSUAL - Funcionalidades Integradas 1037// ============================================================================ 1038 1039const base_url_gestao = window.location.origin + "/assessor"; 1040let processosDataGestao = []; 1041let selectedUploadFilesGestao = []; 1042let autoRefreshInterval = null; 1043let gestaoSortState = { field: 'uploadDate', direction: 'desc' }; 1044let gestaoNeedsFullRenderAfterInteraction = false; 1045const gestaoRowSelections = new Map(); 1046const gestaoRowParamOverrides = new Map(); 1047 1048// ââ Modelos (aba Modelos do ERP) usados na coluna Modelo da tabela ââ 1049// Só existem no contexto ERP (org-scoped). Fora do ERP o botão fica desabilitado. 1050let gestaoModelsCache = []; 1051let gestaoModelsLoadedOrgId = null; 1052let gestaoModelsLoadingPromise = null; 1053 1054function gestaoModelsCanEdit() { 1055 return !!getErpActiveOrgId() && typeof window.cjErpFetchOrgModels === 'function'; 1056} 1057 1058async function loadGestaoModels(force) { 1059 const orgId = getErpActiveOrgId(); 1060 if (!orgId || typeof window.cjErpFetchOrgModels !== 'function') { 1061 gestaoModelsCache = []; 1062 gestaoModelsLoadedOrgId = null; 1063 return gestaoModelsCache; 1064 } 1065 if (!force && gestaoModelsLoadedOrgId === orgId) return gestaoModelsCache; 1066 if (gestaoModelsLoadingPromise && !force) return gestaoModelsLoadingPromise; 1067 gestaoModelsLoadingPromise = (async () => { 1068 try { 1069 const items = await window.cjErpFetchOrgModels(); 1070 gestaoModelsCache = Array.isArray(items) ? items : []; 1071 gestaoModelsLoadedOrgId = orgId; 1072 } catch (_) { 1073 gestaoModelsCache = []; 1074 } finally { 1075 gestaoModelsLoadingPromise = null; 1076 } 1077 return gestaoModelsCache; 1078 })(); 1079 return gestaoModelsLoadingPromise; 1080} 1081 1082function upsertGestaoModelCache(item) { 1083 if (!item) return; 1084 const id = String(item.id || item._id || ''); 1085 if (!id) return; 1086 const entry = { 1087 id, 1088 title: item.title || '', 1089 modelType: item.modelType || 'general', 1090 sourceFileName: item.sourceFileName || '' 1091 }; 1092 const idx = gestaoModelsCache.findIndex(m => String(m.id) === id); 1093 if (idx >= 0) gestaoModelsCache[idx] = Object.assign({}, gestaoModelsCache[idx], entry); 1094 else gestaoModelsCache.unshift(entry); 1095} 1096 1097function getGestaoModelTitle(modelId, fallbackTitle) { 1098 if (!modelId) return ''; 1099 const found = gestaoModelsCache.find(m => String(m.id) === String(modelId)); 1100 if (found && found.title) return found.title; 1101 return String(fallbackTitle || '').trim(); 1102} 1103 1104function getGestaoModelLabel(modelId, modelTitle) { 1105 const title = getGestaoModelTitle(modelId, modelTitle); 1106 if (title) return title.length > 56 ? `${title.slice(0, 56)}...` : title; 1107 if (modelId) return __isEN ? 'Selected template' : 'Modelo selecionado'; 1108 return __isEN ? 'Add template' : 'Adicionar modelo'; 1109} 1110 1111function updateGestaoModelCell(row) { 1112 if (!row) return; 1113 const idInput = row.querySelector('.row-model-id'); 1114 const titleInput = row.querySelector('.row-model-title'); 1115 const label = row.querySelector('.row-model-label'); 1116 const button = row.querySelector('.row-model-btn'); 1117 const modelId = idInput ? String(idInput.value || '').trim() : ''; 1118 const modelTitle = titleInput ? String(titleInput.value || '').trim() : ''; 1119 const resolvedTitle = getGestaoModelTitle(modelId, modelTitle); 1120 if (titleInput && resolvedTitle && titleInput.value !== resolvedTitle) { 1121 titleInput.value = resolvedTitle; 1122 } 1123 if (label) { 1124 label.textContent = getGestaoModelLabel(modelId, resolvedTitle); 1125 label.title = resolvedTitle; 1126 } 1127 if (button) { 1128 const canEdit = gestaoModelsCanEdit(); 1129 button.classList.toggle('has-model', !!modelId); 1130 button.title = canEdit 1131 ? (resolvedTitle || (__isEN ? 'Add or choose a template' : 'Adicionar ou selecionar um modelo')) 1132 : (__isEN 1133 ? 'Templates are available in Gestão Processual after you select a persona.' 1134 : 'Modelos ficam disponÃveis na Gestão Processual após selecionar uma persona.'); 1135 const icon = button.querySelector('i'); 1136 if (icon) icon.className = modelId ? 'fa fa-pencil' : 'fa fa-plus'; 1137 } 1138} 1139 1140function syncGestaoModelCellsFromCache() {
1141 const knownIds = new Set((gestaoModelsCache || []).map(m => String(m.id))); 1142 gestaoQueryAll('.row-model-id').forEach(input => { 1143 const row = input.closest('tr'); 1144 if (!row) return; 1145 const modelId = String(input.value || '').trim(); 1146 if (modelId && knownIds.size && !knownIds.has(modelId)) { 1147 input.value = ''; 1148 const titleInput = row.querySelector('.row-model-title'); 1149 if (titleInput) titleInput.value = ''; 1150 syncGestaoRowParamsFromDom(row); 1151 } 1152 updateGestaoModelCell(row); 1153 }); 1154} 1155 1156function populateRowModelSelectsGestao() { 1157 syncGestaoModelCellsFromCache(); 1158} 1159 1160async function refreshGestaoModelsFromOrg() { 1161 await loadGestaoModels(true); 1162 syncGestaoModelCellsFromCache(); 1163 const modal = document.getElementById('gestaoModelModal'); 1164 if (modal && !modal.hidden) renderGestaoModelBank(gestaoModelsCache); 1165} 1166 1167window.cjAssessorRefreshGestaoModels = function () { 1168 return refreshGestaoModelsFromOrg().catch(() => { }); 1169}; 1170 1171function getErpAssessorRoot() { 1172 const host = document.querySelector('[data-cj-erp-host]'); 1173 return host && host.shadowRoot ? host.shadowRoot : null; 1174} 1175 1176function gestaoGetById(id) { 1177 return document.getElementById(id) || getErpAssessorRoot()?.getElementById(id) || null; 1178} 1179 1180function gestaoQuery(selector) { 1181 return document.querySelector(selector) || getErpAssessorRoot()?.querySelector(selector) || null; 1182} 1183 1184function gestaoQueryAll(selector) { 1185 const root = getErpAssessorRoot(); 1186 const nodes = [ 1187 ...Array.from(document.querySelectorAll(selector)), 1188 ...(root ? Array.from(root.querySelectorAll(selector)) : []) 1189 ]; 1190 return Array.from(new Set(nodes)); 1191} 1192 1193function createClientOperationIdGestao(prefix) { 1194 const suffix = (window.crypto && typeof window.crypto.randomUUID === 'function') 1195 ? window.crypto.randomUUID() 1196 : `${Date.now()}_${Math.random().toString(36).slice(2)}`; 1197 return `${prefix || 'assessor'}_${suffix}`; 1198} 1199 1200function getGestaoRowId(row) { 1201 const checkbox = row ? row.querySelector('.processo-checkbox') : null; 1202 return checkbox ? String(checkbox.value || '') : ''; 1203} 1204 1205function getGestaoRowSelection(id) { 1206 return gestaoRowSelections.get(String(id || '')) || { selected: false, peca: '' }; 1207} 1208 1209function setGestaoRowSelection(id, patch) { 1210 const key = String(id || ''); 1211 if (!key) return; 1212 const previous = getGestaoRowSelection(key); 1213 const next = Object.assign({}, previous, patch || {}); 1214 next.selected = !!(next.selected && next.peca); 1215 if (!next.selected && !next.peca) { 1216 gestaoRowSelections.delete(key); 1217 return; 1218 } 1219 gestaoRowSelections.set(key, next); 1220} 1221 1222function syncGestaoRowSelectionFromDom(row) { 1223 const id = getGestaoRowId(row); 1224 if (!id) return; 1225 const checkbox = row.querySelector('.processo-checkbox'); 1226 const pecaSelect = row.querySelector('.row-peca-select'); 1227 setGestaoRowSelection(id, { 1228 selected: !!(checkbox && checkbox.checked), 1229 peca: pecaSelect ? pecaSelect.value : '' 1230 }); 1231} 1232 1233function syncAllGestaoRowSelectionsFromDom() { 1234 const tbody = gestaoGetById('tabelaProcessosBody'); 1235 if (!tbody) return; 1236 tbody.querySelectorAll('tr').forEach(syncGestaoRowSelectionFromDom); 1237} 1238 1239function syncGestaoRowParamsFromDom(row) { 1240 const id = getGestaoRowId(row); 1241 if (!id) return; 1242 const promptInput = row.querySelector('.row-additional-prompt'); 1243 const promptTitleInput = row.querySelector('.row-additional-prompt-title'); 1244 const pagesInput = row.querySelector('.row-include-pages'); 1245 const anonCheckbox = row.querySelector('.row-anonymization'); 1246 const modelIdInput = row.querySelector('.row-model-id'); 1247 const modelTitleInput = row.querySelector('.row-model-title'); 1248 const modelId = modelIdInput ? String(modelIdInput.value || '').trim() : ''; 1249 const modelTitle = modelTitleInput ? String(modelTitleInput.value || '').trim() : ''; 1250 gestaoRowParamOverrides.set(id, { 1251 additionalPrompt: promptInput ? promptInput.value.trim() : '', 1252 additionalPromptTitle: promptTitleInput ? promptTitleInput.value.trim() : '', 1253 includePages: pagesInput ? pagesInput.value.trim() : '', 1254 enableAnonymization: anonCheckbox ? !!anonCheckbox.checked : false, 1255 modelId: modelId, 1256 modelTitle: modelId ? getGestaoModelTitle(modelId, modelTitle) : '' 1257 }); 1258} 1259 1260function syncAllGestaoRowParamsFromDom() { 1261 const tbody = gestaoGetById('tabelaProcessosBody'); 1262 if (!tbody) return; 1263 tbody.querySelectorAll('tr').forEach(syncGestaoRowParamsFromDom); 1264} 1265 1266let gestaoInstructionModalRowId = null; 1267let gestaoInstructionModalSelectedIndex = null; 1268let gestaoInstructionModalEscapeHandler = null; 1269 1270function getGestaoPromptLabel(promptText, promptTitle) { 1271 const title = String(promptTitle || '').trim(); 1272 if (title) return title.length > 56 ? `${title.slice(0, 56)}...` : title; 1273 const prompt = String(promptText || '').trim(); 1274 if (!prompt) return __isEN ? 'Add instruction' : 'Adicionar instrução'; 1275 return prompt.length > 56 ? `${prompt.slice(0, 56)}...` : prompt; 1276} 1277 1278function getGestaoPromptTitleFallback(promptText) { 1279 const prompt = String(promptText || '').trim(); 1280 if (!prompt) return ''; 1281 const firstLine = prompt.split(/\n/)[0].trim(); 1282 const base = firstLine || prompt.replace(/\s+/g, ' ').trim(); 1283 return base.length > 80 ? `${base.slice(0, 77)}...` : base; 1284} 1285 1286function updateGestaoPromptCell(row) { 1287 if (!row) return; 1288 const input = row.querySelector('.row-additional-prompt'); 1289 const titleInput = row.querySelector('.row-additional-prompt-title'); 1290 const label = row.querySelector('.row-additional-prompt-label'); 1291 const button = row.querySelector('.row-additional-prompt-btn'); 1292 const prompt = input ? String(input.value || '').trim() : ''; 1293 const promptTitle = titleInput ? String(titleInput.value || '').trim() : ''; 1294 if (label) label.textContent = getGestaoPromptLabel(prompt, promptTitle); 1295 if (label) label.title = promptTitle || prompt; 1296 if (button) { 1297 button.classList.toggle('has-prompt', !!prompt); 1298 button.title = promptTitle || prompt || (__isEN ? 'Add or choose additional instructions' : 'Adicionar ou selecionar instruções adicionais'); 1299 const icon = button.querySelector('i'); 1300 if (icon) icon.className = prompt ? 'fa fa-pencil' : 'fa fa-plus'; 1301 } 1302} 1303 1304function gestaoUploadSuccessMessage(count, uploadMode) { 1305 if (uploadMode === 'merged') { 1306 return __isEN ? '1 unified case uploaded successfully.' : '1 processo unificado inserido com sucesso.'; 1307 } 1308 if (__isEN) { 1309 return count === 1 ? '1 file uploaded successfully.' : `${count} files uploaded successfully.`; 1310 } 1311 return count === 1 ? '1 arquivo inserido com sucesso.' : `${count} arquivos inseridos com sucesso.`; 1312} 1313 1314function gestaoUploadErrorMessage(count) { 1315 if (__isEN) { 1316 return count === 1 ? '1 file failed to upload.' : `${count} files failed to upload.`; 1317 } 1318 return count === 1 ? '1 arquivo com erro.' : `${count} arquivos com erro.`; 1319} 1320 1321function gestaoDeepActiveElement() { 1322 let active = document.activeElement; 1323 while (active && active.shadowRoot && active.shadowRoot.activeElement) { 1324 active = active.shadowRoot.activeElement; 1325 } 1326 return active; 1327} 1328 1329function gestaoIsTableInteractionActive() { 1330 const modal = document.getElementById('gestaoInstructionModal'); 1331 if (modal && !modal.hidden) return true; 1332 const modelModal = document.getElementById('gestaoModelModal'); 1333 if (modelModal && !modelModal.hidden) return true; 1334 const active = gestaoDeepActiveElement(); 1335 const tbody = gestaoGetById('tabelaProcessosBody'); 1336 if (!active || !tbody || !tbody.contains(active)) return false;
1337 return typeof active.matches === 'function' 1338 && active.matches('.row-peca-select, .row-model-btn, .row-include-pages, .row-additional-prompt, .row-anonymization'); 1339} 1340 1341function gestaoSearchNeedle(rawTerm) { 1342 return normalizeGestaoFilename(String(rawTerm == null ? '' : rawTerm).trim()).toLowerCase(); 1343} 1344 1345function gestaoTextMatchesSearch(text, needle) { 1346 if (!needle) return false; 1347 return normalizeGestaoFilename(text || '').toLowerCase().includes(needle); 1348} 1349 1350function gestaoMergedPartMatchesSearch(p, needle) { 1351 if (!needle) return false; 1352 return gestaoResolveMergedParts(p).some(part => 1353 gestaoTextMatchesSearch(part.name, needle) || gestaoTextMatchesSearch(part.originalName, needle) 1354 ); 1355} 1356 1357function gestaoProcessoMatchesSearch(p, needle) { 1358 if (!needle) return true; 1359 if (gestaoTextMatchesSearch(p && p.filename, needle)) return true; 1360 const origins = Array.isArray(p && p.convertedFromWord) ? p.convertedFromWord : []; 1361 if (origins.some(name => gestaoTextMatchesSearch(name, needle))) return true; 1362 return gestaoMergedPartMatchesSearch(p, needle); 1363} 1364 1365function highlightGestaoFilename(text, termo) { 1366 const display = normalizeGestaoFilename(text == null ? '' : text); 1367 const needle = gestaoSearchNeedle(termo); 1368 if (!needle) return escapeGestaoHtml(display); 1369 const hay = display.toLowerCase(); 1370 let out = ''; 1371 let from = 0; 1372 let idx = hay.indexOf(needle, from); 1373 if (idx === -1) return escapeGestaoHtml(display); 1374 while (idx !== -1) { 1375 out += escapeGestaoHtml(display.slice(from, idx)); 1376 out += `<mark class="gp-autos-search-hit">${escapeGestaoHtml(display.slice(idx, idx + needle.length))}</mark>`; 1377 from = idx + needle.length; 1378 idx = hay.indexOf(needle, from); 1379 } 1380 out += escapeGestaoHtml(display.slice(from)); 1381 return out; 1382} 1383 1384function gestaoPartNameHtml(part, termo) { 1385 const name = (part && part.name) || ''; 1386 const needle = gestaoSearchNeedle(termo); 1387 if (!needle) return escapeGestaoHtml(normalizeGestaoFilename(name)); 1388 if (gestaoTextMatchesSearch(name, needle) || !gestaoTextMatchesSearch(part && part.originalName, needle)) { 1389 return highlightGestaoFilename(name, termo); 1390 } 1391 return `<mark class="gp-autos-search-hit">${escapeGestaoHtml(normalizeGestaoFilename(name))}</mark>`; 1392} 1393 1394function gestaoGetSearchTerm() { 1395 const el = typeof gestaoGetById === 'function' ? gestaoGetById('buscaProcesso') : null; 1396 return el ? String(el.value || '').trim() : ''; 1397} 1398 1399function getFilteredSortedProcessosGestao(source) { 1400 const needle = gestaoSearchNeedle(gestaoGetSearchTerm()); 1401 return (Array.isArray(source) ? source : []) 1402 .filter(p => gestaoProcessoMatchesSearch(p, needle)) 1403 .sort((a, b) => compareProcessosGestao(a, b, gestaoSortState.field, gestaoSortState.direction)); 1404} 1405 1406function gestaoExpandUnifiedSearchHits(processos) { 1407 const needle = gestaoSearchNeedle(gestaoGetSearchTerm()); 1408 // Só o match em arquivo individual abre o detalhe. O nome do PDF 1409 // unificado (ex.: 11_arquivos_mesclados.pdf) não basta. 1410 gestaoSearchExpandedPartsRows.forEach(id => gestaoExpandedPartsRows.delete(id)); 1411 gestaoSearchExpandedPartsRows.clear(); 1412 if (!needle) return; 1413 (Array.isArray(processos) ? processos : []).forEach(p => { 1414 if (!gestaoMergedPartMatchesSearch(p, needle)) return; 1415 const id = gestaoProcessoId(p); 1416 gestaoExpandedPartsRows.add(id); 1417 gestaoSearchExpandedPartsRows.add(id); 1418 }); 1419} 1420 1421function gestaoProcessoIdsChanged(a, b) { 1422 if (a.length !== b.length) return true; 1423 return a.some((id, index) => id !== b[index]); 1424} 1425 1426function getGestaoPecasForProcesso(p) { 1427 if (Array.isArray(p?.pecas) && p.pecas.length > 0) return p.pecas; 1428 return p?.hasPeca ? [{ selectedPeca: p.selectedPeca || '' }] : []; 1429} 1430 1431function buildGestaoPecaViewCellHtml(p, processoId) { 1432 const pecas = getGestaoPecasForProcesso(p); 1433 const hasPeca = pecas.length > 0; 1434 const pecaCount = pecas.length; 1435 const isBusy = gestaoRowIsBusy(p); 1436 const isOpening = gestaoPecaViewIsOpening(processoId); 1437 const opacity = hasPeca ? '1' : '0.3';
1438 const cursor = isOpening ? 'wait' : ((hasPeca && !isBusy) ? 'pointer' : 'default'); 1439 const pecasJson = hasPeca ? escapeGestaoHtml(JSON.stringify(pecas)) : ''; 1440 const onclickAttr = (hasPeca && !isBusy && !isOpening) 1441 ? `onclick="verPecaGeradaGestao('${processoId}', JSON.parse(this.dataset.pecas), this)" data-pecas="${pecasJson}"` 1442 : (hasPeca ? `data-pecas="${pecasJson}"` : ''); 1443 const title = isOpening 1444 ? (__isEN ? 'Opening draft...' : 'Abrindo a peça...') 1445 : (hasPeca 1446 ? (pecaCount > 1 ? `${pecaCount} peças geradas` : 'Ver Peça Gerada') 1447 : 'Sem peça gerada'); 1448 const iconClass = isOpening ? 'fa fa-spinner fa-spin peca-view-icon' : 'fa fa-file-text-o peca-view-icon'; 1449 const iconColor = isOpening ? '#1d4ed8' : '#007bff'; 1450 const iconOpacity = (isBusy && hasPeca && !isOpening) ? '0.3' : opacity; 1451 1452 return ` 1453 <span class="peca-view-cell" style="display:inline-flex;align-items:center;gap:3px;"> 1454 <i class="${iconClass}" data-loading="${isOpening ? '1' : '0'}" style="font-size: 1.2rem; color: ${iconColor}; opacity: ${iconOpacity}; cursor: ${cursor};" ${onclickAttr} title="${title}"></i> 1455 ${pecaCount > 1 ? `<span class="peca-view-badge" style="font-size:0.65rem;background:#007bff;color:#fff;border-radius:10px;padding:1px 5px;cursor:${cursor};" ${onclickAttr}>${pecaCount}</span>` : ''} 1456 </span> 1457 `; 1458} 1459 1460function patchGestaoTableRowsInPlace() { 1461 const tbody = gestaoGetById('tabelaProcessosBody'); 1462 if (!tbody) return; 1463 if (gestaoTableHasDataRows(tbody)) setGestaoProcessosTableOverlay('hide'); 1464 1465 const filtrados = getFilteredSortedProcessosGestao(processosDataGestao); 1466 const filtradosById = new Map(filtrados.map(p => [gestaoProcessoId(p), p])); 1467 1468 const rows = tbody.querySelectorAll('tr'); 1469 1470 rows.forEach(row => { 1471 const id = row.querySelector('.processo-checkbox')?.value; 1472 const p = filtradosById.get(id); 1473 if (!p) return; 1474 1475 const displayStatus = gestaoEffectiveStatus(p); 1476 const statusClass = getStatusClassGestao(displayStatus); 1477 const statusLabel = getStatusLabelGestao(displayStatus); 1478 const isProcessing = gestaoRowIsBusy(p); 1479 const jobErrorMessage = p.status === 'erro' ? gestaoResolveJobErrorMessage(p) : ''; 1480 const statusCell = row.querySelector('.gp-status-cell'); 1481 if (statusCell) { 1482 const errorTitle = jobErrorMessage 1483 ? `title="${escapeGestaoHtml(jobErrorMessage).replace(/"/g, '"')}"` 1484 : ''; 1485 const errorHint = jobErrorMessage 1486 ? `<div class="gp-status-error-hint">${escapeGestaoHtml(jobErrorMessage.length > 140 ? jobErrorMessage.slice(0, 137) + '...' : jobErrorMessage)}</div>` 1487 : ''; 1488 statusCell.innerHTML = `<span class="tag-status ${statusClass}" ${errorTitle}>${isProcessing ? '<i class="fa fa-spinner fa-spin"></i>' : ''}${statusLabel}</span>${errorHint}`; 1489 } 1490 1491 const pecaViewCell = row.querySelector('.gp-generated-piece-cell'); 1492 if (pecaViewCell) { 1493 pecaViewCell.innerHTML = buildGestaoPecaViewCellHtml(p, id); 1494 } 1495 applyGestaoRowBusyState(row, p); 1496 updateProcessoRowHighlightGestao(row); 1497 }); 1498} 1499 1500function ensureGestaoInstructionModal() { 1501 let modal = document.getElementById('gestaoInstructionModal'); 1502 if (modal) { 1503 const leftoverClear = modal.querySelector('#gestaoInstructionModalClear'); 1504 if (leftoverClear) leftoverClear.remove(); 1505 return modal; 1506 } 1507 1508 modal = document.createElement('div'); 1509 modal.id = 'gestaoInstructionModal'; 1510 modal.className = 'cj-gp-instruction-modal'; 1511 modal.hidden = true; 1512 modal.setAttribute('data-cj-gp-instruction-modal', ''); 1513 1514 const backdrop = document.createElement('div'); 1515 backdrop.className = 'cj-gp-instruction-modal__backdrop'; 1516 backdrop.addEventListener('click', closeGestaoPromptModal); 1517 1518 const panel = document.createElement('div'); 1519 panel.className = 'cj-gp-instruction-modal__panel'; 1520 panel.setAttribute('role', 'dialog'); 1521 panel.setAttribute('aria-modal', 'true'); 1522 panel.setAttribute('aria-labelledby', 'gestaoInstructionModalTitle'); 1523 1524 const header = document.createElement('header'); 1525 header.className = 'cj-gp-instruction-modal__header'; 1526 1527 const heading = document.createElement('div'); 1528 heading.className = 'cj-gp-instruction-modal__heading'; 1529 1530 const title = document.createElement('h2'); 1531 title.id = 'gestaoInstructionModalTitle'; 1532 title.className = 'cj-gp-instruction-modal__title'; 1533 title.textContent = __isEN ? 'Additional Instructions' : 'Instruções Adicionais'; 1534 1535 const description = document.createElement('p'); 1536 description.className = 'cj-gp-instruction-modal__description'; 1537 description.textContent = __isEN 1538 ? 'Enter a prompt name and instruction, or choose one from your prompt bank.' 1539 : 'Informe o nome do prompt e a instrução, ou escolha uma do seu banco de prompts.'; 1540 1541 heading.appendChild(title); 1542 heading.appendChild(description); 1543 1544 const closeBtn = document.createElement('button'); 1545 closeBtn.type = 'button'; 1546 closeBtn.className = 'cj-gp-instruction-modal__close'; 1547 closeBtn.setAttribute('aria-label', __isEN ? 'Close' : 'Fechar'); 1548 closeBtn.innerHTML = '<i class="fa fa-times" aria-hidden="true"></i>'; 1549 closeBtn.addEventListener('click', closeGestaoPromptModal); 1550 1551 header.appendChild(heading); 1552 header.appendChild(closeBtn); 1553 1554 const content = document.createElement('div'); 1555 content.className = 'cj-gp-instruction-modal__content'; 1556 1557 const titleField = document.createElement('label'); 1558 titleField.className = 'cj-gp-instruction-modal__field'; 1559 const titleFieldLabel = document.createElement('span'); 1560 titleFieldLabel.className = 'cj-gp-instruction-modal__field-label'; 1561 titleFieldLabel.textContent = __isEN ? 'Prompt name' : 'Nome do prompt'; 1562 const titleInput = document.createElement('input'); 1563 titleInput.id = 'gestaoInstructionModalTitleInput'; 1564 titleInput.type = 'text'; 1565 titleInput.className = 'cj-gp-instruction-modal__input'; 1566 titleInput.maxLength = 180; 1567 titleInput.placeholder = __isEN 1568 ? 'Example: Detailed statement of facts' 1569 : 'Exemplo: Narrativa detalhada dos fatos'; 1570 titleField.appendChild(titleFieldLabel); 1571 titleField.appendChild(titleInput); 1572 1573 const field = document.createElement('label'); 1574 field.className = 'cj-gp-instruction-modal__field'; 1575 const fieldLabel = document.createElement('span'); 1576 fieldLabel.className = 'cj-gp-instruction-modal__field-label'; 1577 fieldLabel.textContent = __isEN ? 'Instruction' : 'Instrução'; 1578 const textarea = document.createElement('textarea'); 1579 textarea.id = 'gestaoInstructionModalTextarea'; 1580 textarea.className = 'cj-gp-instruction-modal__textarea'; 1581 textarea.rows = 5; 1582 textarea.placeholder = __isEN 1583 ? 'Example: make the statement of facts as detailed as possible.' 1584 : 'Exemplo: quero que a narrativa dos fatos seja o mais detalhada possÃvel.'; 1585 1586 // Wrapper posicionado para o popup de menção @arquivo. 1587 const mentionWrapper = document.createElement('div'); 1588 mentionWrapper.className = 'cj-gp-instruction-modal__mention-wrapper'; 1589 const mentionList = document.createElement('div'); 1590 mentionList.id = 'gestaoInstructionModalMentionList'; 1591 mentionList.className = 'cj-gp-instruction-modal__mention-list'; 1592 mentionList.hidden = true; 1593 mentionWrapper.appendChild(textarea); 1594 mentionWrapper.appendChild(mentionList); 1595 1596 const mentionHint = document.createElement('span'); 1597 mentionHint.id = 'gestaoInstructionModalMentionHint';
1598 mentionHint.className = 'cj-gp-instruction-modal__mention-hint'; 1599 mentionHint.hidden = true; 1600 1601 field.appendChild(fieldLabel); 1602 field.appendChild(mentionWrapper); 1603 field.appendChild(mentionHint); 1604 bindGestaoMentionEvents(textarea); 1605 1606 const bankSection = document.createElement('section'); 1607 bankSection.className = 'cj-gp-instruction-modal__bank'; 1608 const bankTitle = document.createElement('h3'); 1609 bankTitle.className = 'cj-gp-instruction-modal__bank-title'; 1610 bankTitle.textContent = __isEN ? 'Saved prompts' : 'Prompts salvos'; 1611 const bank = document.createElement('div'); 1612 bank.id = 'gestaoInstructionModalBank'; 1613 bank.className = 'cj-gp-instruction-modal__bank-list'; 1614 bankSection.appendChild(bankTitle); 1615 bankSection.appendChild(bank); 1616 1617 content.appendChild(titleField); 1618 content.appendChild(field); 1619 content.appendChild(bankSection); 1620 1621 const footer = document.createElement('footer'); 1622 footer.className = 'cj-gp-instruction-modal__footer'; 1623 1624 const cancelBtn = document.createElement('button'); 1625 cancelBtn.type = 'button'; 1626 cancelBtn.id = 'gestaoInstructionModalCancel'; 1627 cancelBtn.className = 'cj-gp-instruction-modal__btn cj-gp-instruction-modal__btn--ghost'; 1628 cancelBtn.textContent = __isEN ? 'Cancel' : 'Cancelar'; 1629 cancelBtn.addEventListener('click', closeGestaoPromptModal); 1630 1631 const applyBtn = document.createElement('button'); 1632 applyBtn.type = 'button'; 1633 applyBtn.id = 'gestaoInstructionModalApply'; 1634 applyBtn.className = 'cj-gp-instruction-modal__btn cj-gp-instruction-modal__btn--primary'; 1635 applyBtn.textContent = __isEN ? 'Apply' : 'Aplicar'; 1636 applyBtn.addEventListener('click', saveGestaoPromptModal); 1637 1638 footer.appendChild(cancelBtn); 1639 footer.appendChild(applyBtn); 1640 1641 panel.appendChild(header); 1642 panel.appendChild(content); 1643 panel.appendChild(footer); 1644 modal.appendChild(backdrop); 1645 modal.appendChild(panel); 1646 document.body.appendChild(modal); 1647 return modal; 1648} 1649 1650// Menção @arquivo: candidatos são os arquivos que formaram o processo unificado 1651// da linha aberta. A menção só contextualiza (o servidor traduz em faixa de 1652// páginas no prompt); não restringe a análise. 1653let gestaoMentionCandidates = []; 1654let gestaoMentionMatches = []; 1655let gestaoMentionActiveIndex = 0; 1656 1657function gestaoMentionTokenAtCaret(textarea) { 1658 if (!textarea) return null; 1659 const caret = textarea.selectionStart; 1660 if (caret !== textarea.selectionEnd) return null; 1661 const before = textarea.value.slice(0, caret); 1662 const match = /(^|[\s(\[])@([^\n@]*)$/.exec(before); 1663 if (!match) return null; 1664 return { query: match[2], start: caret - match[2].length - 1, end: caret }; 1665} 1666 1667function closeGestaoMentionList() { 1668 const list = document.getElementById('gestaoInstructionModalMentionList'); 1669 if (!list) return; 1670 list.hidden = true; 1671 list.innerHTML = ''; 1672 gestaoMentionMatches = []; 1673 gestaoMentionActiveIndex = 0; 1674} 1675 1676function renderGestaoMentionList(textarea) { 1677 const list = document.getElementById('gestaoInstructionModalMentionList'); 1678 if (!list) return; 1679 if (!gestaoMentionMatches.length) { 1680 closeGestaoMentionList(); 1681 return; 1682 } 1683 list.innerHTML = ''; 1684 gestaoMentionMatches.forEach((part, index) => { 1685 const item = document.createElement('button'); 1686 item.type = 'button'; 1687 item.className = 'cj-gp-instruction-modal__mention-item' 1688 + (index === gestaoMentionActiveIndex ? ' is-active' : ''); 1689 const name = document.createElement('span'); 1690 name.className = 'cj-gp-instruction-modal__mention-name'; 1691 name.textContent = part.name; 1692 item.appendChild(name); 1693 const pagesLabel = gestaoPartPagesLabel(part); 1694 if (pagesLabel) { 1695 const pages = document.createElement('span'); 1696 pages.className = 'cj-gp-instruction-modal__mention-pages'; 1697 pages.textContent = pagesLabel; 1698 item.appendChild(pages); 1699 } 1700 // mousedown para não perder o caret do textarea antes de inserir. 1701 item.addEventListener('mousedown', (e) => { 1702 e.preventDefault(); 1703 applyGestaoMention(textarea, part); 1704 }); 1705 list.appendChild(item); 1706 }); 1707 list.hidden = false;
1708 const active = list.children[gestaoMentionActiveIndex]; 1709 if (active && typeof active.scrollIntoView === 'function') { 1710 active.scrollIntoView({ block: 'nearest' }); 1711 } 1712} 1713 1714function updateGestaoMentionList(textarea) { 1715 if (!gestaoMentionCandidates.length) { 1716 closeGestaoMentionList(); 1717 return; 1718 } 1719 const token = gestaoMentionTokenAtCaret(textarea); 1720 if (!token) { 1721 closeGestaoMentionList(); 1722 return; 1723 } 1724 const query = normalizeGestaoFilename(token.query).trim().toLowerCase(); 1725 gestaoMentionMatches = gestaoMentionCandidates 1726 .filter(part => !query || part.name.toLowerCase().includes(query)); 1727 gestaoMentionActiveIndex = 0; 1728 renderGestaoMentionList(textarea); 1729} 1730 1731function applyGestaoMention(textarea, part) { 1732 if (!textarea || !part) return; 1733 const token = gestaoMentionTokenAtCaret(textarea); 1734 if (!token) return; 1735 const insertion = '@' + part.name + ' '; 1736 const value = textarea.value; 1737 textarea.value = value.slice(0, token.start) + insertion + value.slice(token.end); 1738 const caret = token.start + insertion.length; 1739 textarea.setSelectionRange(caret, caret); 1740 closeGestaoMentionList(); 1741 textarea.focus(); 1742} 1743 1744function bindGestaoMentionEvents(textarea) { 1745 if (!textarea || textarea.dataset.mentionBound === 'true') return; 1746 textarea.dataset.mentionBound = 'true'; 1747 1748 textarea.addEventListener('input', () => updateGestaoMentionList(textarea)); 1749 textarea.addEventListener('click', () => updateGestaoMentionList(textarea)); 1750 textarea.addEventListener('blur', () => setTimeout(closeGestaoMentionList, 120)); 1751 textarea.addEventListener('keydown', (e) => { 1752 const list = document.getElementById('gestaoInstructionModalMentionList'); 1753 if (!list || list.hidden || !gestaoMentionMatches.length) return; 1754 if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { 1755 e.preventDefault(); 1756 const delta = e.key === 'ArrowDown' ? 1 : -1; 1757 const total = gestaoMentionMatches.length; 1758 gestaoMentionActiveIndex = (gestaoMentionActiveIndex + delta + total) % total; 1759 renderGestaoMentionList(textarea); 1760 return; 1761 } 1762 if (e.key === 'Enter' || e.key === 'Tab') { 1763 e.preventDefault(); 1764 applyGestaoMention(textarea, gestaoMentionMatches[gestaoMentionActiveIndex]); 1765 return; 1766 } 1767 if (e.key === 'Escape') { 1768 // Esc fecha só a lista; o modal continua aberto. 1769 e.preventDefault(); 1770 e.stopPropagation(); 1771 closeGestaoMentionList(); 1772 } 1773 }); 1774} 1775 1776function setGestaoMentionCandidates(parts) { 1777 gestaoMentionCandidates = Array.isArray(parts) ? parts : []; 1778 closeGestaoMentionList(); 1779 const hint = document.getElementById('gestaoInstructionModalMentionHint'); 1780 if (!hint) return; 1781 if (!gestaoMentionCandidates.length) { 1782 hint.hidden = true; 1783 hint.textContent = ''; 1784 return; 1785 } 1786 hint.hidden = false; 1787 hint.innerHTML = `${__isEN 1788 ? 'Type @ to reference one of the files in this unified case.' 1789 : 'Digite @ para referenciar um dos arquivos deste processo unificado.'}`; 1790} 1791 1792function openGestaoInstructionModal() { 1793 const modal = ensureGestaoInstructionModal(); 1794 modal.hidden = false; 1795 modal.classList.add('is-open'); 1796 document.body.classList.add('cj-gp-instruction-modal-open'); 1797 if (gestaoInstructionModalEscapeHandler) { 1798 document.removeEventListener('keydown', gestaoInstructionModalEscapeHandler); 1799 } 1800 gestaoInstructionModalEscapeHandler = (event) => { 1801 if (event.key === 'Escape') closeGestaoPromptModal(); 1802 }; 1803 document.addEventListener('keydown', gestaoInstructionModalEscapeHandler); 1804 return modal; 1805} 1806 1807function closeGestaoInstructionModal() { 1808 const modal = document.getElementById('gestaoInstructionModal'); 1809 if (!modal) return; 1810 closeGestaoMentionList(); 1811 modal.hidden = true; 1812 modal.classList.remove('is-open'); 1813 document.body.classList.remove('cj-gp-instruction-modal-open'); 1814 if (gestaoInstructionModalEscapeHandler) { 1815 document.removeEventListener('keydown', gestaoInstructionModalEscapeHandler); 1816 gestaoInstructionModalEscapeHandler = null; 1817 } 1818 gestaoInstructionModalRowId = null; 1819 gestaoInstructionModalSelectedIndex = null; 1820} 1821 1822function findGestaoPromptBankIndex(promptText, promptTitle, prompts) { 1823 const list = Array.isArray(prompts) ? prompts.filter(item => item && item.prompt) : []; 1824 const prompt = String(promptText || '').trim(); 1825 if (!prompt) return -1; 1826 const title = String(promptTitle || '').trim(); 1827 let idx = list.findIndex(item => 1828 String(item.prompt || '').trim() === prompt 1829 && String(item.title || '').trim() === title 1830 ); 1831 if (idx < 0) { 1832 idx = list.findIndex(item => String(item.prompt || '').trim() === prompt); 1833 } 1834 return idx; 1835} 1836 1837function setGestaoPromptModalSelection(index) { 1838 gestaoInstructionModalSelectedIndex = (index == null || index < 0) ? null : Number(index); 1839 const bankEl = document.getElementById('gestaoInstructionModalBank'); 1840 if (!bankEl) return; 1841 bankEl.querySelectorAll('.cj-gp-instruction-modal__bank-item').forEach(btn => { 1842 const btnIndex = Number(btn.dataset.promptIndex); 1843 btn.classList.toggle( 1844 'is-selected', 1845 gestaoInstructionModalSelectedIndex != null && btnIndex === gestaoInstructionModalSelectedIndex 1846 ); 1847 }); 1848} 1849 1850function renderGestaoInstructionBank(prompts) { 1851 const bankEl = document.getElementById('gestaoInstructionModalBank'); 1852 if (!bankEl) return; 1853 bankEl.innerHTML = ''; 1854 const list = Array.isArray(prompts) ? prompts.filter(item => item && item.prompt) : []; 1855 if (!list.length) { 1856 const empty = document.createElement('p'); 1857 empty.className = 'cj-gp-instruction-modal__empty'; 1858 empty.textContent = __isEN 1859 ? 'No saved instructions yet. Type a new one above.' 1860 : 'Nenhuma instrução salva ainda. Digite uma nova acima.'; 1861 bankEl.appendChild(empty); 1862 return; 1863 } 1864 list.forEach((item, index) => { 1865 const btn = document.createElement('button'); 1866 btn.type = 'button'; 1867 btn.className = 'cj-gp-instruction-modal__bank-item'; 1868 btn.dataset.promptIndex = String(index); 1869 const span = document.createElement('span'); 1870 const promptTitle = String(item.title || '').trim(); 1871 span.textContent = promptTitle || getGestaoPromptTitleFallback(item.prompt || ''); 1872 btn.title = item.prompt || ''; 1873 btn.appendChild(span); 1874 if (gestaoInstructionModalSelectedIndex != null && gestaoInstructionModalSelectedIndex === index) { 1875 btn.classList.add('is-selected'); 1876 } 1877 btn.addEventListener('click', () => { 1878 const idx = Number(btn.dataset.promptIndex); 1879 const textarea = document.getElementById('gestaoInstructionModalTextarea'); 1880 const titleEl = document.getElementById('gestaoInstructionModalTitleInput'); 1881 if (gestaoInstructionModalSelectedIndex != null && gestaoInstructionModalSelectedIndex === idx) { 1882 if (textarea) textarea.value = ''; 1883 if (titleEl) titleEl.value = ''; 1884 setGestaoPromptModalSelection(null); 1885 if (textarea) textarea.focus(); 1886 return; 1887 } 1888 if (textarea && list[idx]) { 1889 textarea.value = list[idx].prompt || ''; 1890 if (titleEl) {
1891 titleEl.value = String(list[idx].title || '').trim() || getGestaoPromptTitleFallback(list[idx].prompt || ''); 1892 } 1893 textarea.focus(); 1894 } 1895 setGestaoPromptModalSelection(idx); 1896 }); 1897 bankEl.appendChild(btn); 1898 }); 1899} 1900 1901async function loadGestaoInstructionBank() { 1902 ensureGestaoInstructionModal(); 1903 const bankEl = document.getElementById('gestaoInstructionModalBank'); 1904 if (!bankEl) return; 1905 bankEl.innerHTML = ''; 1906 const loading = document.createElement('p'); 1907 loading.className = 'cj-gp-instruction-modal__empty'; 1908 loading.textContent = __isEN ? 'Loading...' : 'Carregando...'; 1909 bankEl.appendChild(loading); 1910 const shouldFetch = !!getErpActiveOrgId() || !promptHistoryLoadedOnce || assessorPromptHistory.length === 0; 1911 if (shouldFetch) { 1912 const prompts = await fetchPromptHistoryFromDB(); 1913 assessorPromptHistory = prompts; 1914 promptHistoryLoadedOnce = true; 1915 writePromptHistoryCache(prompts); 1916 } 1917 renderGestaoInstructionBank(assessorPromptHistory); 1918 const textarea = document.getElementById('gestaoInstructionModalTextarea'); 1919 const titleEl = document.getElementById('gestaoInstructionModalTitleInput'); 1920 const selectedIdx = findGestaoPromptBankIndex( 1921 textarea && textarea.value, 1922 titleEl && titleEl.value, 1923 assessorPromptHistory 1924 ); 1925 setGestaoPromptModalSelection(selectedIdx); 1926} 1927 1928function closeGestaoPromptModal() { 1929 closeGestaoInstructionModal(); 1930} 1931 1932function openGestaoPromptModal(row) { 1933 const id = getGestaoRowId(row); 1934 if (!id) return; 1935 const pecaSelect = row.querySelector('.row-peca-select'); 1936 if (gestaoIsAutomaticPeca(pecaSelect?.value)) { 1937 funcMsg( 1938 'info', 1939 __L.attn, 1940 __isEN 1941 ? 'Additional prompts are disabled for Automatic. Choose Free to write your own instructions.' 1942 : 'Prompts adicionais ficam desativados na opção Automática. Escolha Livre para escrever suas próprias instruções.', 1943 '', 1944 '', 1945 undefined, 1946 gestaoInsightOptions('instruction_prompt_automatic_disabled', { 1947 errorCode: 'validation:prompt_automatic_disabled' 1948 }) 1949 ); 1950 return; 1951 } 1952 openGestaoInstructionModal(); 1953 const textarea = document.getElementById('gestaoInstructionModalTextarea'); 1954 const titleInput = document.getElementById('gestaoInstructionModalTitleInput'); 1955 const input = row.querySelector('.row-additional-prompt'); 1956 const titleHidden = row.querySelector('.row-additional-prompt-title'); 1957 if (!textarea || !input) return; 1958 gestaoInstructionModalRowId = id; 1959 textarea.value = input.value || ''; 1960 if (titleInput) { 1961 titleInput.value = titleHidden ? (titleHidden.value || '') : ''; 1962 } 1963 const processo = processosDataGestao.find(p => gestaoProcessoId(p) === id); 1964 // Nomes homônimos aparecem uma vez na lista de menção (o servidor cita as 1965 // duas faixas de página), mas seguem individuais no detalhe da tabela. 1966 const mentionParts = []; 1967 gestaoResolveMergedParts(processo).forEach(part => { 1968 if (!mentionParts.some(existing => existing.name === part.name)) mentionParts.push(part); 1969 }); 1970 setGestaoMentionCandidates(mentionParts); 1971 gestaoInstructionModalSelectedIndex = findGestaoPromptBankIndex( 1972 input.value, 1973 titleHidden && titleHidden.value, 1974 assessorPromptHistory 1975 ); 1976 loadGestaoInstructionBank(); 1977 setTimeout(() => { 1978 const focusEl = String(textarea.value || '').trim() ? textarea : (titleInput || textarea); 1979 focusEl.focus(); 1980 }, 50); 1981} 1982 1983async function persistGestaoPromptBankEntry(title, prompt) { 1984 if (!prompt || !isPromptHistoryAuthReady() || !isAssessorUserProForPromptHistory()) return; 1985 try { 1986 const orgId = getErpActiveOrgId(); 1987 if (orgId && typeof window.cjErpSaveOrgPrompt === 'function') { 1988 await window.cjErpSaveOrgPrompt(title, prompt); 1989 upsertLocalPromptHistory(prompt, title); 1990 writePromptHistoryCache(assessorPromptHistory); 1991 if (typeof window.cjErpNotifyPromptsChanged === 'function') { 1992 window.cjErpNotifyPromptsChanged(); 1993 } 1994 return; 1995 } 1996 1997 const response = await fetch('/assessor_prompt_bank', { 1998 method: 'POST', 1999 headers: { 2000 'Content-Type': 'application/json', 2001 'Authorization': `Bearer ${cognijusUserToken}` 2002 }, 2003 body: JSON.stringify({ title, prompt }) 2004 }); 2005 if (!response.ok) return; 2006 upsertLocalPromptHistory(prompt, title); 2007 writePromptHistoryCache(assessorPromptHistory); 2008 } catch (err) { 2009 console.error('Error saving assessor prompt bank entry:', err); 2010 } 2011} 2012 2013function saveGestaoPromptModal() { 2014 if (!gestaoInstructionModalRowId) return;
2015 const row = gestaoQuery(`.processo-checkbox[value="${gestaoInstructionModalRowId}"]`)?.closest('tr'); 2016 const textarea = document.getElementById('gestaoInstructionModalTextarea'); 2017 const titleInput = document.getElementById('gestaoInstructionModalTitleInput'); 2018 const input = row ? row.querySelector('.row-additional-prompt') : null; 2019 const titleHidden = row ? row.querySelector('.row-additional-prompt-title') : null; 2020 if (!row || !textarea || !input) return; 2021 2022 const prompt = textarea.value.trim(); 2023 const title = titleInput ? titleInput.value.trim() : ''; 2024 2025 input.value = prompt; 2026 if (titleHidden) titleHidden.value = title; 2027 syncGestaoRowParamsFromDom(row); 2028 updateGestaoPromptCell(row); 2029 if (prompt && title) { 2030 persistGestaoPromptBankEntry(title, prompt); 2031 } 2032 closeGestaoPromptModal(); 2033} 2034 2035let gestaoModelModalRowId = null; 2036let gestaoModelModalSelectedId = null; 2037let gestaoModelModalSourceName = ''; 2038let gestaoModelModalSourceMime = ''; 2039let gestaoModelModalEscapeHandler = null; 2040 2041function gestaoModelEditorHasContent(editor) { 2042 if (!editor) return false; 2043 return String(editor.innerText || '').trim().length > 0; 2044} 2045 2046function syncGestaoModelEditorEmptyState() { 2047 const editor = document.getElementById('gestaoModelModalEditor'); 2048 if (!editor) return; 2049 editor.classList.toggle('is-empty', !gestaoModelEditorHasContent(editor)); 2050} 2051 2052function setGestaoModelModalSelection(modelId) { 2053 gestaoModelModalSelectedId = modelId ? String(modelId) : null; 2054 const bankEl = document.getElementById('gestaoModelModalBank'); 2055 if (!bankEl) return; 2056 bankEl.querySelectorAll('.cj-gp-instruction-modal__bank-item').forEach(btn => { 2057 btn.classList.toggle('is-selected', !!(gestaoModelModalSelectedId && btn.dataset.modelId === gestaoModelModalSelectedId)); 2058 }); 2059} 2060 2061function clearGestaoModelModalEditor(meta) { 2062 const editor = document.getElementById('gestaoModelModalEditor'); 2063 const titleInput = document.getElementById('gestaoModelModalTitleInput'); 2064 const fileInput = document.getElementById('gestaoModelModalFile'); 2065 const fileLabel = document.getElementById('gestaoModelModalFileLabel'); 2066 const status = document.getElementById('gestaoModelModalImportStatus'); 2067 const uploader = document.getElementById('gestaoModelModalUploader'); 2068 if (editor) editor.innerHTML = ''; 2069 gestaoModelModalSourceName = (meta && meta.sourceFileName) || ''; 2070 gestaoModelModalSourceMime = (meta && meta.sourceMimeType) || ''; 2071 if (titleInput && meta && meta.title && !String(titleInput.value || '').trim()) { 2072 titleInput.value = meta.title; 2073 } 2074 if (fileInput) fileInput.value = ''; 2075 if (fileLabel) fileLabel.textContent = gestaoModelModalSourceName; 2076 if (status) { 2077 status.textContent = gestaoModelModalSourceName 2078 ? (__isEN ? 'File loaded in the editor. Review and apply.' : 'Arquivo carregado no editor. Revise e aplique.') 2079 : ''; 2080 } 2081 if (uploader) uploader.classList.toggle('has-file', !!gestaoModelModalSourceName); 2082 syncGestaoModelEditorEmptyState(); 2083} 2084 2085function applyGestaoModelToRow(row, modelId, modelTitle) { 2086 if (!row) return; 2087 const idInput = row.querySelector('.row-model-id'); 2088 const titleInput = row.querySelector('.row-model-title'); 2089 const resolvedTitle = getGestaoModelTitle(modelId, modelTitle); 2090 if (idInput) idInput.value = modelId || ''; 2091 if (titleInput) titleInput.value = modelId ? resolvedTitle : ''; 2092 syncGestaoRowParamsFromDom(row); 2093 updateGestaoModelCell(row); 2094} 2095 2096function ensureGestaoModelModal() { 2097 let modal = document.getElementById('gestaoModelModal'); 2098 if (modal) { 2099 const leftoverClear = modal.querySelector('#gestaoModelModalClear'); 2100 if (leftoverClear) leftoverClear.remove(); 2101 return modal; 2102 } 2103 2104 modal = document.createElement('div'); 2105 modal.id = 'gestaoModelModal'; 2106 modal.className = 'cj-gp-instruction-modal cj-gp-model-modal'; 2107 modal.hidden = true; 2108 modal.setAttribute('data-cj-gp-model-modal', ''); 2109 2110 const backdrop = document.createElement('div'); 2111 backdrop.className = 'cj-gp-instruction-modal__backdrop'; 2112 backdrop.addEventListener('click', closeGestaoModelModal); 2113 2114 const panel = document.createElement('div'); 2115 panel.className = 'cj-gp-instruction-modal__panel cj-gp-model-modal__panel'; 2116 panel.setAttribute('role', 'dialog'); 2117 panel.setAttribute('aria-modal', 'true'); 2118 panel.setAttribute('aria-labelledby', 'gestaoModelModalTitle'); 2119 2120 const header = document.createElement('header'); 2121 header.className = 'cj-gp-instruction-modal__header'; 2122 2123 const heading = document.createElement('div'); 2124 heading.className = 'cj-gp-instruction-modal__heading'; 2125 2126 const title = document.createElement('h2'); 2127 title.id = 'gestaoModelModalTitle'; 2128 title.className = 'cj-gp-instruction-modal__title'; 2129 title.textContent = __isEN ? 'Draft template' : 'Modelo da peça'; 2130 2131 const description = document.createElement('p'); 2132 description.className = 'cj-gp-instruction-modal__description'; 2133 description.textContent = __isEN 2134 ? 'Choose a saved template or add a new one for this case.' 2135 : 'Escolha um modelo salvo ou adicione um novo para este processo.'; 2136 2137 heading.appendChild(title); 2138 heading.appendChild(description); 2139 2140 const closeBtn = document.createElement('button'); 2141 closeBtn.type = 'button'; 2142 closeBtn.className = 'cj-gp-instruction-modal__close'; 2143 closeBtn.setAttribute('aria-label', __isEN ? 'Close' : 'Fechar'); 2144 closeBtn.innerHTML = '<i class="fa fa-times" aria-hidden="true"></i>'; 2145 closeBtn.addEventListener('click', closeGestaoModelModal); 2146 2147 header.appendChild(heading); 2148 header.appendChild(closeBtn); 2149 2150 const content = document.createElement('div'); 2151 content.className = 'cj-gp-instruction-modal__content'; 2152 2153 const titleField = document.createElement('label'); 2154 titleField.className = 'cj-gp-instruction-modal__field'; 2155 const titleFieldLabel = document.createElement('span'); 2156 titleFieldLabel.className = 'cj-gp-instruction-modal__field-label'; 2157 titleFieldLabel.textContent = __isEN ? 'Template name' : 'Nome do modelo'; 2158 const titleInput = document.createElement('input'); 2159 titleInput.id = 'gestaoModelModalTitleInput'; 2160 titleInput.type = 'text'; 2161 titleInput.className = 'cj-gp-instruction-modal__input'; 2162 titleInput.maxLength = 180; 2163 titleInput.placeholder = __isEN ? 'Example: Initial petition' : 'Exemplo: Petição inicial'; 2164 titleField.appendChild(titleFieldLabel); 2165 titleField.appendChild(titleInput); 2166 2167 const uploader = document.createElement('label'); 2168 uploader.id = 'gestaoModelModalUploader'; 2169 uploader.className = 'cj-gp-model-modal__uploader'; 2170 uploader.innerHTML = `
2171 <span class="cj-gp-model-modal__uploader-icon"><i class="fa fa-cloud-upload" aria-hidden="true"></i></span> 2172 <span class="cj-gp-model-modal__uploader-title">${__isEN ? 'Upload Word or PDF' : 'Enviar Word ou PDF'}</span> 2173 <span class="cj-gp-model-modal__uploader-hint">${__isEN ? '.docx or .pdf · up to 20 MB' : '.docx ou .pdf · até 20 MB'}</span> 2174 <input id="gestaoModelModalFile" type="file" accept=".docx,.doc,.pdf,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"> 2175 <span id="gestaoModelModalFileLabel" class="cj-gp-model-modal__uploader-file"></span> 2176 `; 2177 const importStatus = document.createElement('p'); 2178 importStatus.id = 'gestaoModelModalImportStatus'; 2179 importStatus.className = 'cj-gp-model-modal__import-status'; 2180 2181 const editorWrap = document.createElement('div'); 2182 editorWrap.className = 'cj-gp-model-modal__editor-wrap'; 2183 const editorLabel = document.createElement('div'); 2184 editorLabel.className = 'cj-gp-instruction-modal__field-label'; 2185 editorLabel.textContent = __isEN ? 'Template content' : 'Conteúdo do modelo'; 2186 const toolbar = document.createElement('div'); 2187 toolbar.className = 'cj-gp-model-modal__toolbar'; 2188 [ 2189 ['bold', 'fa-bold', __isEN ? 'Bold' : 'Negrito'], 2190 ['italic', 'fa-italic', __isEN ? 'Italic' : 'Itálico'], 2191 ['underline', 'fa-underline', __isEN ? 'Underline' : 'Sublinhado'], 2192 ['insertUnorderedList', 'fa-list-ul', __isEN ? 'List' : 'Lista'], 2193 ['insertOrderedList', 'fa-list-ol', __isEN ? 'Numbered list' : 'Lista numerada'], 2194 ['removeFormat', 'fa-eraser', __isEN ? 'Clear formatting' : 'Limpar formatação'] 2195 ].forEach(([cmd, icon, label]) => { 2196 const btn = document.createElement('button'); 2197 btn.type = 'button'; 2198 btn.dataset.modelCmd = cmd; 2199 btn.title = label; 2200 btn.innerHTML = `<i class="fa ${icon}" aria-hidden="true"></i>`; 2201 toolbar.appendChild(btn); 2202 }); 2203 const editor = document.createElement('div'); 2204 editor.id = 'gestaoModelModalEditor'; 2205 editor.className = 'cj-gp-model-modal__editor is-empty'; 2206 editor.contentEditable = 'true'; 2207 editor.setAttribute('role', 'textbox'); 2208 editor.setAttribute('aria-multiline', 'true'); 2209 editor.dataset.placeholder = __isEN 2210 ? 'Paste from Word or type the templateâ¦' 2211 : 'Cole do Word ou digite o conteúdoâ¦'; 2212 editor.addEventListener('input', () => { 2213 setGestaoModelModalSelection(null); 2214 syncGestaoModelEditorEmptyState(); 2215 }); 2216 editor.addEventListener('paste', () => { 2217 setTimeout(() => { 2218 setGestaoModelModalSelection(null); 2219 syncGestaoModelEditorEmptyState(); 2220 }, 0); 2221 }); 2222 toolbar.addEventListener('click', (event) => { 2223 const btn = event.target.closest('[data-model-cmd]'); 2224 if (!btn) return; 2225 event.preventDefault(); 2226 document.execCommand(btn.dataset.modelCmd, false, null); 2227 editor.focus(); 2228 setGestaoModelModalSelection(null); 2229 syncGestaoModelEditorEmptyState(); 2230 }); 2231 editorWrap.appendChild(editorLabel); 2232 editorWrap.appendChild(toolbar); 2233 editorWrap.appendChild(editor); 2234 2235 const bankSection = document.createElement('section'); 2236 bankSection.className = 'cj-gp-instruction-modal__bank'; 2237 const bankTitle = document.createElement('h3'); 2238 bankTitle.className = 'cj-gp-instruction-modal__bank-title'; 2239 bankTitle.textContent = __isEN ? 'Saved templates' : 'Modelos salvos'; 2240 const bank = document.createElement('div'); 2241 bank.id = 'gestaoModelModalBank'; 2242 bank.className = 'cj-gp-instruction-modal__bank-list'; 2243 bankSection.appendChild(bankTitle); 2244 bankSection.appendChild(bank); 2245 2246 content.appendChild(titleField); 2247 content.appendChild(uploader); 2248 content.appendChild(importStatus); 2249 content.appendChild(editorWrap); 2250 content.appendChild(bankSection); 2251 2252 const footer = document.createElement('footer'); 2253 footer.className = 'cj-gp-instruction-modal__footer'; 2254 2255 const cancelBtn = document.createElement('button'); 2256 cancelBtn.type = 'button'; 2257 cancelBtn.className = 'cj-gp-instruction-modal__btn cj-gp-instruction-modal__btn--ghost'; 2258 cancelBtn.textContent = __isEN ? 'Cancel' : 'Cancelar'; 2259 cancelBtn.addEventListener('click', closeGestaoModelModal); 2260 2261 const applyBtn = document.createElement('button'); 2262 applyBtn.type = 'button'; 2263 applyBtn.id = 'gestaoModelModalApply'; 2264 applyBtn.className = 'cj-gp-instruction-modal__btn cj-gp-instruction-modal__btn--primary'; 2265 applyBtn.textContent = __isEN ? 'Apply' : 'Aplicar'; 2266 applyBtn.addEventListener('click', () => { 2267 saveGestaoModelModal().catch(() => { }); 2268 }); 2269 2270 footer.appendChild(cancelBtn); 2271 footer.appendChild(applyBtn); 2272 2273 panel.appendChild(header); 2274 panel.appendChild(content); 2275 panel.appendChild(footer); 2276 modal.appendChild(backdrop); 2277 modal.appendChild(panel); 2278 document.body.appendChild(modal); 2279 2280 const fileInput = document.getElementById('gestaoModelModalFile'); 2281 if (fileInput) { 2282 fileInput.addEventListener('change', () => { 2283 const file = fileInput.files && fileInput.files[0]; 2284 if (file) importGestaoModelModalFile(file); 2285 }); 2286 } 2287 ['dragenter', 'dragover'].forEach(type => { 2288 uploader.addEventListener(type, (event) => { 2289 event.preventDefault(); 2290 uploader.classList.add('is-dragover'); 2291 }); 2292 }); 2293 ['dragleave', 'drop'].forEach(type => { 2294 uploader.addEventListener(type, (event) => { 2295 event.preventDefault(); 2296 uploader.classList.remove('is-dragover'); 2297 }); 2298 }); 2299 uploader.addEventListener('drop', (event) => { 2300 const file = event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]; 2301 if (file) importGestaoModelModalFile(file); 2302 }); 2303 2304 return modal; 2305} 2306 2307function renderGestaoModelBank(models) { 2308 const bankEl = document.getElementById('gestaoModelModalBank'); 2309 if (!bankEl) return; 2310 bankEl.innerHTML = ''; 2311 const list = Array.isArray(models) ? models.filter(item => item && (item.id || item._id)) : []; 2312 if (!list.length) { 2313 const empty = document.createElement('p'); 2314 empty.className = 'cj-gp-instruction-modal__empty'; 2315 empty.textContent = __isEN 2316 ? 'No saved templates yet. Add a new one above.' 2317 : 'Nenhum modelo salvo ainda. Adicione um novo acima.'; 2318 bankEl.appendChild(empty); 2319 return; 2320 } 2321 list.forEach((item) => { 2322 const id = String(item.id || item._id || ''); 2323 const btn = document.createElement('button'); 2324 btn.type = 'button'; 2325 btn.className = 'cj-gp-instruction-modal__bank-item'; 2326 btn.dataset.modelId = id; 2327 const span = document.createElement('span'); 2328 span.textContent = item.title || (__isEN ? 'Untitled template' : 'Modelo sem tÃtulo'); 2329 btn.title = item.sourceFileName || span.textContent; 2330 btn.appendChild(span); 2331 if (gestaoModelModalSelectedId && gestaoModelModalSelectedId === id) { 2332 btn.classList.add('is-selected'); 2333 } 2334 btn.addEventListener('click', () => { 2335 if (gestaoModelModalSelectedId && gestaoModelModalSelectedId === id) { 2336 const titleEl = document.getElementById('gestaoModelModalTitleInput'); 2337 if (titleEl) titleEl.value = ''; 2338 setGestaoModelModalSelection(null); 2339 return; 2340 } 2341 const titleEl = document.getElementById('gestaoModelModalTitleInput'); 2342 if (titleEl) titleEl.value = item.title || ''; 2343 const editor = document.getElementById('gestaoModelModalEditor'); 2344 if (editor) editor.innerHTML = ''; 2345 gestaoModelModalSourceName = ''; 2346 gestaoModelModalSourceMime = ''; 2347 const fileLabel = document.getElementById('gestaoModelModalFileLabel'); 2348 const status = document.getElementById('gestaoModelModalImportStatus'); 2349 const fileInput = document.getElementById('gestaoModelModalFile'); 2350 const uploader = document.getElementById('gestaoModelModalUploader'); 2351 if (fileLabel) fileLabel.textContent = ''; 2352 if (status) status.textContent = ''; 2353 if (fileInput) fileInput.value = ''; 2354 if (uploader) uploader.classList.remove('has-file'); 2355 syncGestaoModelEditorEmptyState(); 2356 setGestaoModelModalSelection(id); 2357 }); 2358 bankEl.appendChild(btn); 2359 }); 2360} 2361 2362async function loadGestaoModelBank() { 2363 ensureGestaoModelModal(); 2364 const bankEl = document.getElementById('gestaoModelModalBank'); 2365 if (!bankEl) return; 2366 bankEl.innerHTML = ''; 2367 const loading = document.createElement('p'); 2368 loading.className = 'cj-gp-instruction-modal__empty'; 2369 loading.textContent = __isEN ? 'Loading...' : 'Carregando...'; 2370 bankEl.appendChild(loading); 2371 const models = await loadGestaoModels(true); 2372 renderGestaoModelBank(models); 2373 setGestaoModelModalSelection(gestaoModelModalSelectedId); 2374} 2375 2376function openGestaoModelModalDialog() { 2377 const modal = ensureGestaoModelModal(); 2378 modal.hidden = false;
2379 modal.classList.add('is-open'); 2380 document.body.classList.add('cj-gp-instruction-modal-open'); 2381 if (gestaoModelModalEscapeHandler) { 2382 document.removeEventListener('keydown', gestaoModelModalEscapeHandler); 2383 } 2384 gestaoModelModalEscapeHandler = (event) => { 2385 if (event.key === 'Escape') closeGestaoModelModal(); 2386 }; 2387 document.addEventListener('keydown', gestaoModelModalEscapeHandler); 2388 return modal; 2389} 2390 2391function closeGestaoModelModal() { 2392 const modal = document.getElementById('gestaoModelModal'); 2393 if (!modal) return; 2394 modal.hidden = true; 2395 modal.classList.remove('is-open'); 2396 document.body.classList.remove('cj-gp-instruction-modal-open'); 2397 if (gestaoModelModalEscapeHandler) { 2398 document.removeEventListener('keydown', gestaoModelModalEscapeHandler); 2399 gestaoModelModalEscapeHandler = null; 2400 } 2401 gestaoModelModalRowId = null; 2402 gestaoModelModalSelectedId = null; 2403 gestaoModelModalSourceName = ''; 2404 gestaoModelModalSourceMime = ''; 2405} 2406 2407function openGestaoModelModal(row) { 2408 const id = getGestaoRowId(row); 2409 if (!id) return; 2410 if (!gestaoModelsCanEdit()) { 2411 funcMsg( 2412 'info', 2413 __L.attn, 2414 __isEN 2415 ? 'Templates are available in Gestão Processual after you select a persona.' 2416 : 'Modelos ficam disponÃveis na Gestão Processual após selecionar uma persona.', 2417 '', 2418 '', 2419 undefined, 2420 gestaoInsightOptions('instruction_model_org_required', { 2421 errorCode: 'validation:model_org_required' 2422 }) 2423 ); 2424 return; 2425 } 2426 openGestaoModelModalDialog(); 2427 const titleInput = document.getElementById('gestaoModelModalTitleInput'); 2428 const idHidden = row.querySelector('.row-model-id'); 2429 const titleHidden = row.querySelector('.row-model-title'); 2430 gestaoModelModalRowId = id; 2431 const currentId = idHidden ? String(idHidden.value || '').trim() : ''; 2432 const currentTitle = titleHidden ? String(titleHidden.value || '').trim() : ''; 2433 if (titleInput) titleInput.value = getGestaoModelTitle(currentId, currentTitle); 2434 clearGestaoModelModalEditor(null); 2435 setGestaoModelModalSelection(currentId || null); 2436 loadGestaoModelBank(); 2437 setTimeout(() => { 2438 const focusEl = currentId ? titleInput : titleInput; 2439 if (focusEl) focusEl.focus(); 2440 }, 50); 2441} 2442 2443async function importGestaoModelModalFile(file) { 2444 const status = document.getElementById('gestaoModelModalImportStatus'); 2445 const applyBtn = document.getElementById('gestaoModelModalApply'); 2446 if (typeof window.cjErpImportOrgModel !== 'function') return; 2447 if (status) status.textContent = __isEN ? 'Importing...' : 'Importando...'; 2448 if (applyBtn) applyBtn.disabled = true; 2449 try { 2450 const item = await window.cjErpImportOrgModel(file); 2451 const editor = document.getElementById('gestaoModelModalEditor'); 2452 const titleInput = document.getElementById('gestaoModelModalTitleInput'); 2453 if (editor) editor.innerHTML = (item && item.content) || ''; 2454 if (titleInput && item && item.title && !String(titleInput.value || '').trim()) { 2455 titleInput.value = item.title; 2456 } 2457 gestaoModelModalSourceName = (item && item.sourceFileName) || (file && file.name) || ''; 2458 gestaoModelModalSourceMime = (item && item.sourceMimeType) || (file && file.type) || ''; 2459 const fileLabel = document.getElementById('gestaoModelModalFileLabel'); 2460 const uploader = document.getElementById('gestaoModelModalUploader'); 2461 if (fileLabel) fileLabel.textContent = gestaoModelModalSourceName; 2462 if (status) { 2463 status.textContent = __isEN 2464 ? 'File loaded in the editor. Review and apply.' 2465 : 'Arquivo carregado no editor. Revise e aplique.'; 2466 } 2467 if (uploader) uploader.classList.add('has-file'); 2468 setGestaoModelModalSelection(null); 2469 syncGestaoModelEditorEmptyState(); 2470 } catch (err) { 2471 if (status) status.textContent = ''; 2472 funcMsg( 2473 'error', 2474 __L.attn, 2475 err && err.message 2476 ? err.message 2477 : (__isEN ? 'Could not import this file.' : 'Não foi possÃvel importar este arquivo.'), 2478 '', 2479 '', 2480 undefined, 2481 gestaoInsightOptions('instruction_model_import_failed', { 2482 errorCode: 'model:import_failed' 2483 }) 2484 ); 2485 } finally {
2486 if (applyBtn) applyBtn.disabled = false; 2487 } 2488} 2489 2490async function persistGestaoModelBankEntry(title, content) { 2491 if (!title || !content || typeof window.cjErpSaveOrgModel !== 'function') return null; 2492 try { 2493 const item = await window.cjErpSaveOrgModel({ 2494 title, 2495 content, 2496 contentFormat: 'html', 2497 sourceFileName: gestaoModelModalSourceName, 2498 sourceMimeType: gestaoModelModalSourceMime 2499 }); 2500 if (!item) return null; 2501 upsertGestaoModelCache(item); 2502 if (typeof window.cjErpNotifyModelsChanged === 'function') { 2503 window.cjErpNotifyModelsChanged(); 2504 } 2505 return { 2506 id: String(item.id || item._id || ''), 2507 title: item.title || title 2508 }; 2509 } catch (err) { 2510 console.error('Error saving assessor model bank entry:', err); 2511 throw err; 2512 } 2513} 2514 2515async function saveGestaoModelModal() { 2516 if (!gestaoModelModalRowId) return; 2517 const row = gestaoQuery(`.processo-checkbox[value="${gestaoModelModalRowId}"]`)?.closest('tr'); 2518 const titleInput = document.getElementById('gestaoModelModalTitleInput'); 2519 const editor = document.getElementById('gestaoModelModalEditor'); 2520 const applyBtn = document.getElementById('gestaoModelModalApply'); 2521 if (!row || !titleInput || !editor) return; 2522 2523 const selectedId = gestaoModelModalSelectedId; 2524 const title = titleInput.value.trim(); 2525 const html = String(editor.innerHTML || '').trim(); 2526 const hasContent = gestaoModelEditorHasContent(editor); 2527 2528 if (selectedId) { 2529 applyGestaoModelToRow(row, selectedId, title); 2530 closeGestaoModelModal(); 2531 return; 2532 } 2533 2534 if (title && hasContent) { 2535 if (applyBtn) { 2536 applyBtn.disabled = true; 2537 applyBtn.textContent = __isEN ? 'Saving...' : 'Salvando...'; 2538 } 2539 try { 2540 const saved = await persistGestaoModelBankEntry(title, html); 2541 if (!saved || !saved.id) { 2542 throw new Error(__isEN ? 'Could not save this template.' : 'Não foi possÃvel salvar este modelo.'); 2543 } 2544 applyGestaoModelToRow(row, saved.id, saved.title); 2545 closeGestaoModelModal(); 2546 } catch (err) { 2547 funcMsg( 2548 'error', 2549 __L.attn, 2550 err && err.message 2551 ? err.message 2552 : (__isEN ? 'Could not save this template.' : 'Não foi possÃvel salvar este modelo.'), 2553 '', 2554 '', 2555 undefined, 2556 gestaoInsightOptions('instruction_model_save_failed', { 2557 errorCode: 'model:save_failed' 2558 }) 2559 ); 2560 } finally { 2561 if (applyBtn) { 2562 applyBtn.disabled = false; 2563 applyBtn.textContent = __isEN ? 'Apply' : 'Aplicar'; 2564 } 2565 } 2566 return; 2567 } 2568 2569 applyGestaoModelToRow(row, '', ''); 2570 closeGestaoModelModal(); 2571} 2572 2573function gestaoRowIsBusy(p) { 2574 return gestaoEffectiveStatus(p) === 'processando'; 2575} 2576 2577function applyGestaoRowBusyState(row, p) { 2578 if (!row || !p) return; 2579 const busy = gestaoRowIsBusy(p); 2580 const checkbox = row.querySelector('.processo-checkbox'); 2581 const pecaSelect = row.querySelector('.row-peca-select'); 2582 const deleteBtn = row.querySelector('.btn-delete-processo'); 2583 const pecaIcon = row.querySelector('.peca-view-icon'); 2584 const pecaBadge = row.querySelector('.peca-view-badge'); 2585 2586 const promptInput = row.querySelector('.row-additional-prompt'); 2587 const promptBtn = row.querySelector('.row-additional-prompt-btn'); 2588 const pagesInput = row.querySelector('.row-include-pages'); 2589 const anonCheckbox = row.querySelector('.row-anonymization'); 2590 const modelBtn = row.querySelector('.row-model-btn'); 2591 2592 if (checkbox) checkbox.disabled = busy; 2593 if (pecaSelect) pecaSelect.disabled = busy; 2594 if (promptInput) promptInput.disabled = busy; 2595 if (promptBtn) promptBtn.disabled = busy; 2596 if (pagesInput) pagesInput.disabled = busy; 2597 if (anonCheckbox) anonCheckbox.disabled = busy; 2598 if (modelBtn) { 2599 modelBtn.disabled = busy || !gestaoModelsCanEdit(); 2600 updateGestaoModelCell(row); 2601 } 2602 if (deleteBtn) { 2603 deleteBtn.disabled = busy; 2604 deleteBtn.style.opacity = busy ? '0.5' : ''; 2605 deleteBtn.style.cursor = busy ? 'not-allowed' : 'pointer'; 2606 } 2607 if (pecaIcon) { 2608 if (busy) { 2609 pecaIcon.style.pointerEvents = 'none'; 2610 pecaIcon.style.opacity = '0.3'; 2611 pecaIcon.removeAttribute('onclick'); 2612 } 2613 } 2614 if (pecaBadge && busy) { 2615 pecaBadge.style.pointerEvents = 'none'; 2616 pecaBadge.removeAttribute('onclick'); 2617 } 2618 applyGestaoPromptAvailability(row); 2619} 2620 2621function applyGestaoRowSelections(processos) { 2622 const currentIds = new Set((processos || []).map(gestaoProcessoI
2622d).filter(Boolean)); 2623 Array.from(gestaoRowSelections.keys()).forEach(id => { 2624 if (!currentIds.has(id)) gestaoRowSelections.delete(id); 2625 }); 2626 Array.from(gestaoRowParamOverrides.keys()).forEach(id => { 2627 if (!currentIds.has(id)) gestaoRowParamOverrides.delete(id); 2628 }); 2629 (processos || []).forEach(p => { 2630 const id = gestaoProcessoId(p); 2631 const selection = getGestaoRowSelection(id); 2632 p._selected = !!(selection.selected && selection.peca); 2633 p._selectedPeca = selection.peca || ''; 2634 }); 2635} 2636 2637function getGestaoPieceStatus() { 2638 const rows = gestaoQueryAll('#tabelaProcessosBody tr').filter(row => !!getGestaoRowId(row)); 2639 const withPiece = rows.filter(row => rowHasPecaGestao(row)).length; 2640 return { 2641 withPiece, 2642 withoutPiece: Math.max(0, rows.length - withPiece) 2643 }; 2644} 2645 2646function buildGestaoInsightContext(action, extra) { 2647 const personaSelect = gestaoGetById('personaSelectGestao'); 2648 const checked = gestaoQueryAll('.processo-checkbox:checked'); 2649 const context = { 2650 flowStep: 'gestao_processual', 2651 action, 2652 personaSelected: !!(personaSelect && personaSelect.value), 2653 selectedCount: checked.length, 2654 processCount: processosDataGestao.length, 2655 fileCount: selectedUploadFilesGestao.length, 2656 mainUploadFileCount: Array.isArray(selectedFiles) ? selectedFiles.length : 0, 2657 pieceStatus: getGestaoPieceStatus() 2658 }; 2659 return Object.assign(context, extra || {}); 2660} 2661 2662function gestaoInsightOptions(action, extra) { 2663 const opts = extra || {}; 2664 return { 2665 notifyAdmin: opts.notifyAdmin !== false, 2666 source: opts.source || 'assessor-ui', 2667 category: opts.category || 'validation', 2668 flowStep: opts.flowStep || 'gestao_processual', 2669 action, 2670 errorCode: opts.errorCode, 2671 requestId: opts.requestId, 2672 operationId: opts.operationId, 2673 context: buildGestaoInsightContext(action, opts.context) 2674 }; 2675} 2676 2677function gestaoTableHasDataRows(tbody) { 2678 if (!tbody) return false; 2679 return !!tbody.querySelector('tr'); 2680} 2681 2682function gestaoProcessosTableContainer() { 2683 const tbody = gestaoGetById('tabelaProcessosBody'); 2684 return tbody && tbody.closest('.gestao-processual-table-container'); 2685} 2686 2687function gestaoProcessosTableScrollEl() { 2688 return gestaoQuery('.gestao-processual-table-scroll'); 2689} 2690 2691function gestaoProcessosTableHTrack() { 2692 return gestaoQuery('.gp-table-htrack'); 2693} 2694 2695function updateGestaoProcessosTableOverflow() { 2696 const scroller = gestaoProcessosTableScrollEl(); 2697 const wrap = scroller && scroller.closest('.gestao-processual-table-scroll-wrap'); 2698 const track = gestaoProcessosTableHTrack(); 2699 const thumb = track && track.querySelector('.gp-table-hthumb'); 2700 if (!scroller || !wrap) return; 2701 const maxScroll = scroller.scrollWidth - scroller.clientWidth; 2702 const overflowing = maxScroll > 1; 2703 wrap.classList.toggle('can-scroll-right', overflowing && scroller.scrollLeft < maxScroll - 1); 2704 wrap.classList.toggle('can-scroll-left', overflowing && scroller.scrollLeft > 1); 2705 if (!track) return; 2706 const overlayOn = wrap.closest('.gestao-processual-table-container'); 2707 const hideTrack = !overflowing || (overlayOn && (overlayOn.classList.contains('is-loading') || overlayOn.classList.contains('is-empty'))); 2708 track.hidden = hideTrack; 2709 if (hideTrack || !thumb) return; 2710 const vBar = Math.max(0, scroller.offsetWidth - scroller.clientWidth); 2711 track.style.right = vBar ? vBar + 'px' : '0px'; 2712 const trackW = track.clientWidth; 2713 const thumbW = Math.max(32, Math.round(trackW * (scroller.clientWidth / scroller.scrollWidth))); 2714 const maxThumbLeft = Math.max(0, trackW - thumbW); 2715 const left = maxScroll > 0 ? Math.round((scroller.scrollLeft / maxScroll) * maxThumbLeft) : 0; 2716 thumb.style.width = thumbW + 'px'; 2717 thumb.style.transform = 'translateX(' + left + 'px)'; 2718 thumb.setAttribute('aria-valuemin', '0'); 2719 thumb.setAttribute('aria-valuemax', String(Math.round(maxScroll))); 2720 thumb.setAttribute('aria-valuenow', String(Math.round(scroller.scrollLeft))); 2721} 2722 2723function bindGestaoProcessosTableHTrack() { 2724 const scroller = gestaoProcessosTableScrollEl(); 2725 const track = gestaoProcessosTableHTrack(); 2726 const thumb = track && track.querySelector('.gp-table-hthumb'); 2727 if (!scroller || !track || !thumb || track.dataset.gpHtrackBound === 'true') return; 2728 track.dataset.gpHtrackBound = 'true'; 2729 let dragging = false;
2730 let dragStartX = 0; 2731 let dragStartScroll = 0; 2732 2733 function maxScrollLeft() { 2734 return Math.max(0, scroller.scrollWidth - scroller.clientWidth); 2735 } 2736 2737 function maxThumbLeft() { 2738 return Math.max(0, track.clientWidth - thumb.offsetWidth); 2739 } 2740 2741 track.addEventListener('pointerdown', function (event) { 2742 if (event.button != null && event.button !== 0) return; 2743 const maxScroll = maxScrollLeft(); 2744 if (maxScroll <= 0) return; 2745 if (event.target !== thumb) { 2746 const rect = track.getBoundingClientRect(); 2747 const ratio = (event.clientX - rect.left - (thumb.offsetWidth / 2)) / Math.max(1, track.clientWidth - thumb.offsetWidth); 2748 scroller.scrollLeft = Math.max(0, Math.min(maxScroll, ratio * maxScroll)); 2749 } 2750 dragging = true; 2751 dragStartX = event.clientX; 2752 dragStartScroll = scroller.scrollLeft; 2753 thumb.classList.add('is-dragging'); 2754 try { track.setPointerCapture(event.pointerId); } catch (e) { /* ignore */ } 2755 event.preventDefault(); 2756 }); 2757 2758 track.addEventListener('pointermove', function (event) { 2759 if (!dragging) return; 2760 const span = maxThumbLeft(); 2761 if (span <= 0) return; 2762 const delta = event.clientX - dragStartX; 2763 scroller.scrollLeft = dragStartScroll + (delta / span) * maxScrollLeft(); 2764 }); 2765 2766 function stopDrag(event) { 2767 if (!dragging) return; 2768 dragging = false; 2769 thumb.classList.remove('is-dragging'); 2770 if (event && event.pointerId != null) { 2771 try { track.releasePointerCapture(event.pointerId); } catch (e) { /* ignore */ } 2772 } 2773 } 2774 2775 track.addEventListener('pointerup', stopDrag); 2776 track.addEventListener('pointercancel', stopDrag); 2777} 2778 2779function bindGestaoProcessosTableOverflow() { 2780 const scroller = gestaoProcessosTableScrollEl(); 2781 if (!scroller) return; 2782 bindGestaoProcessosTableHTrack(); 2783 if (scroller.dataset.gpOverflowBound === 'true') { 2784 updateGestaoProcessosTableOverflow(); 2785 return; 2786 } 2787 scroller.dataset.gpOverflowBound = 'true'; 2788 scroller.addEventListener('scroll', updateGestaoProcessosTableOverflow, { passive: true }); 2789 if (typeof ResizeObserver === 'function') { 2790 const observer = new ResizeObserver(updateGestaoProcessosTableOverflow); 2791 observer.observe(scroller); 2792 const table = scroller.querySelector('.gestao-processual-table'); 2793 if (table) observer.observe(table); 2794 } 2795 window.addEventListener('resize', updateGestaoProcessosTableOverflow); 2796 updateGestaoProcessosTableOverflow(); 2797} 2798 2799function scheduleGestaoProcessosTableOverflowUpdate() { 2800 bindGestaoProcessosTableOverflow(); 2801 if (typeof requestAnimationFrame === 'function') { 2802 requestAnimationFrame(updateGestaoProcessosTableOverflow); 2803 } else { 2804 updateGestaoProcessosTableOverflow(); 2805 } 2806} 2807 2808function setGestaoProcessosTableOverlay(mode, text) { 2809 const container = gestaoProcessosTableContainer(); 2810 const overlay = gestaoGetById('gestaoProcessosTableOverlay'); 2811 if (!container || !overlay) return; 2812 container.classList.toggle('is-loading', mode === 'loading'); 2813 container.classList.toggle('is-empty', mode === 'empty'); 2814 if (mode === 'hide') { 2815 overlay.innerHTML = ''; 2816 overlay.setAttribute('aria-hidden', 'true'); 2817 scheduleGestaoProcessosTableOverflowUpdate(); 2818 return; 2819 } 2820 overlay.removeAttribute('aria-hidden'); 2821 if (mode === 'loading') { 2822 overlay.innerHTML = '<span class="gp-table-loading"><i class="fa fa-spinner fa-spin" aria-hidden="true"></i> ' 2823 + (__isEN ? 'Loading cases...' : 'Carregando processos...') 2824 + '</span>'; 2825 scheduleGestaoProcessosTableOverflowUpdate(); 2826 return; 2827 } 2828 overlay.innerHTML = '<span class="gp-table-empty">' + escapeGestaoHtml(text || '') + '</span>'; 2829 scheduleGestaoProcessosTableOverflowUpdate(); 2830} 2831 2832function showGestaoProcessosTableLoading() { 2833 const tbody = gestaoGetById('tabelaProcessosBody'); 2834 if (!tbody || gestaoTableHasDataRows(tbody)) return; 2835 setGestaoProcessosTableOverlay('loading'); 2836} 2837 2838function showGestaoProcessosTableMessage(text) { 2839 const tbody = gestaoGetById('tabelaProcessosBody'); 2840 if (!tbody || gestaoTableHasDataRows(tbody)) return; 2841 tbody.innerHTML = ''; 2842 setGestaoProcessosTableOverlay('empty', text); 2843} 2844 2845// Load processos from API 2846async function loadProcessosGestao() { 2847 const token = localStorage.getItem('CogniJUS_User_Token'); 2848 if (!token) return; 2849 2850 const tbody = gestaoGetById('tabelaProcessosBody'); 2851 if (tbody && !gestaoTableHasDataRows(tbody)) showGestaoProcessosTableLoading(); 2852 2853 try { 2854 const orgId = getErpActiveOrgId(); 2855 const response = await fetch(base_url_gestao + '/processos/list', { 2856 method: 'POST', 2857 headers: { 'x-access-token': token, 'Content-Type': 'application/json' }, 2858 body: JSON.stringify(orgId ? { organizationId: orgId } : {}) 2859 }); 2860 const data = await response.json(); 2861 2862 if (!response.ok || !Array.isArray(data.processos)) { 2863 showGestaoProcessosTableMessage( 2864 __isEN ? 'Could not load the cases.' : 'Não foi possÃvel carregar os processos.' 2865 ); 2866 return; 2867 } 2868 2869 if (data.processos) { 2870 const prevFilteredIds = getFilteredSortedProcessosGestao(processosDataGestao).map(gestaoProcessoI
2870d); 2871 const prevSnapshot = new Map( 2872 processosDataGestao.map(p => [ 2873 gestaoProcessoId(p), 2874 { 2875 status: p.status, 2876 pecaCount: gestaoPecaCount(p) 2877 } 2878 ]) 2879 ); 2880 2881 data.processos.forEach(p => { 2882 const id = gestaoProcessoId(p); 2883 const prev = prevSnapshot.get(id); 2884 const pecaCount = gestaoPecaCount(p); 2885 2886 if (gestaoWatchedJobs.has(id)) { 2887 gestaoProcessWatchedJob(p); 2888 return; 2889 } 2890 2891 if (!prev) return; 2892 2893 const status = p.status || 'pendente'; 2894 const wasActive = prev.status === 'pendente' || prev.status === 'processando'; 2895 if (prev.status !== status && wasActive) { 2896 const copy = gestaoStatusToastCopy(status, gestaoDisplayFilename(p.filename)); 2897 if (status === 'concluido' && pecaCount > prev.pecaCount && pecaCount > 0) { 2898 gestaoNotifyCompleted(p); 2899 } else if (status === 'erro') { 2900 gestaoNotifyJobError(p, gestaoDisplayFilename(p.filename)); 2901 } else if (status !== 'concluido') { 2902 showGestaoToast(copy.title, copy.msg); 2903 } 2904 return; 2905 } 2906 2907 if (wasActive && status === 'concluido' && pecaCount > prev.pecaCount) { 2908 gestaoNotifyCompleted(p); 2909 } 2910 }); 2911 2912 applyGestaoRowSelections(data.processos); 2913 processosDataGestao = data.processos; 2914 const nextFilteredIds = getFilteredSortedProcessosGestao(processosDataGestao).map(gestaoProcessoId); 2915 const tbody = gestaoGetById('tabelaProcessosBody'); 2916 const tbodyMissingDataRows = !!(tbody && !gestaoTableHasDataRows(tbody)); 2917 const needsFullRender = gestaoNeedsFullRenderAfterInteraction 2918 || gestaoProcessoIdsChanged(prevFilteredIds, nextFilteredIds) 2919 || tbodyMissingDataRows; 2920 const tableInteractionActive = gestaoIsTableInteractionActive(); 2921 if (needsFullRender && !tableInteractionActive) { 2922 applyFiltersAndRenderGestao(); 2923 gestaoNeedsFullRenderAfterInteraction = false; 2924 } else { 2925 if (needsFullRender) gestaoNeedsFullRenderAfterInteraction = true; 2926 patchGestaoTableRowsInPlace(); 2927 } 2928 2929 const hasPendingJobs = data.processos.some( 2930 p => p.status === 'pendente' || p.status === 'processando' 2931 ); 2932 if (hasPendingJobs && !autoRefreshInterval) { 2933 autoRefreshInterval = setInterval(() => { 2934 loadProcessosGestao(); 2935 }, 5000); 2936 } else if (!hasPendingJobs && autoRefreshInterval) { 2937 clearInterval(autoRefreshInterval); 2938 autoRefreshInterval = null; 2939 } 2940 } 2941 } catch (error) { 2942 console.error('Erro ao listar processos:', error); 2943 showGestaoProcessosTableMessage( 2944 __isEN ? 'Could not load the cases.' : 'Não foi possÃvel carregar os processos.' 2945 ); 2946 } 2947} 2948 2949function compareProcessosGestao(a, b, field, direction) { 2950 let result = 0; 2951 switch (field) { 2952 case 'filename': 2953 result = (a.filename || '').localeCompare(b.filename || ''); 2954 break; 2955 case 'fileSize': 2956 result = (a.fileSize || 0) - (b.fileSize || 0); 2957 break; 2958 case 'status': 2959 result = (a.status || '').localeCompare(b.status || ''); 2960 break; 2961 case 'uploadDate': 2962 result = new Date(a.uploadDate) - new Date(b.uploadDate); 2963 break; 2964 } 2965 return direction === 'desc' ? -result : result; 2966} 2967 2968function updateGestaoSortIndicators() { 2969 gestaoQueryAll('.gestao-processual-table th.sortable').forEach(th => { 2970 const field = th.dataset.sortField; 2971 const icon = th.querySelector('.sort-icon'); 2972 if (!icon) return; 2973 icon.classList.remove('fa-sort', 'fa-sort-asc', 'fa-sort-desc'); 2974 if (field === gestaoSortState.field) { 2975 icon.classList.add(gestaoSortState.direction === 'asc' ? 'fa-sort-asc' : 'fa-sort-desc'); 2976 } else { 2977 icon.classList.add('fa-sort'); 2978 } 2979 }); 2980} 2981 2982function handleGestaoColumnSort(field) { 2983 if (gestaoSortState.field === field) { 2984 gestaoSortState.direction = gestaoSortState.direction === 'asc' ? 'desc' : 'asc'; 2985 } else { 2986 gestaoSortState.field = field; 2987 gestaoSortState.direction = 'asc'; 2988 }
2989 applyFiltersAndRenderGestao(); 2990} 2991 2992function applyFiltersAndRenderGestao(fromSearch) { 2993 if (!gestaoGetById('buscaProcesso')) return; 2994 const filtrados = getFilteredSortedProcessosGestao(processosDataGestao); 2995 if (fromSearch) gestaoExpandUnifiedSearchHits(filtrados); 2996 2997 syncAllGestaoRowSelectionsFromDom(); 2998 syncAllGestaoRowParamsFromDom(); 2999 renderTabelaProcessosGestao(filtrados); 3000 updateGestaoSortIndicators(); 3001} 3002 3003// Estado do caret de "Processo unificado". A tabela é recriada a cada refresh 3004// (polling de status), então o que está aberto precisa sobreviver ao render. 3005const gestaoExpandedPartsRows = new Set(); 3006const gestaoSearchExpandedPartsRows = new Set(); 3007 3008function buildGestaoPartsDetailRow(processoId, parts) { 3009 const row = document.createElement('tr'); 3010 row.className = 'gp-autos-detail-row'; 3011 row.dataset.partsFor = processoId; 3012 const searchTerm = gestaoGetSearchTerm(); 3013 3014 const items = parts.map((part, position) => { 3015 const pagesLabel = gestaoPartPagesLabel(part); 3016 const sizeLabel = part.size 3017 ? (part.size >= 1048576 ? (part.size / 1048576).toFixed(1) + ' MB' : (part.size / 1024).toFixed(0) + ' KB') 3018 : ''; 3019 const wordBadge = part.convertedFromWord 3020 ? `<span class="gp-autos-detail-badge" title="${escapeGestaoHtml(__isEN ? 'Some component files were converted from Word to PDF.' : 'Há arquivos componentes convertidos de Word para PDF.')}">Word</span>` 3021 : ''; 3022 const meta = [pagesLabel, sizeLabel].filter(Boolean) 3023 .map(text => `<span class="gp-autos-detail-meta">${escapeGestaoHtml(text)}</span>`) 3024 .join(''); 3025 return `<li class="gp-autos-detail-item"> 3026 <span class="gp-autos-detail-order">${position + 1}</span> 3027 <span class="gp-autos-detail-name" title="${escapeGestaoHtml(part.originalName)}">${gestaoPartNameHtml(part, searchTerm)}</span> 3028 ${wordBadge}${meta} 3029 </li>`; 3030 }).join(''); 3031 3032 const hint = __isEN 3033 ? 'Use @ in Instructions to point the AI at a specific file.' 3034 : 'Use @ em Instruções para direcionar a IA a um arquivo especÃfico.'; 3035 3036 row.innerHTML = `<td colspan="12" class="gp-autos-detail-cell"> 3037 <div class="gp-autos-detail-title">${__isEN ? 'Files in this unified case' : 'Arquivos que formam este processo unificado'}</div> 3038 <ul class="gp-autos-detail-list">${items}</ul> 3039 <div class="gp-autos-detail-hint">${hint}</div> 3040 </td>`; 3041 return row; 3042} 3043 3044function toggleGestaoPartsRow(processoId, row) { 3045 if (!processoId || !row) return; 3046 const toggle = row.querySelector('.gp-autos-toggle'); 3047 const existing = row.nextElementSibling; 3048 const isOpen = existing && existing.classList.contains('gp-autos-detail-row'); 3049 3050 if (isOpen) { 3051 existing.remove(); 3052 gestaoExpandedPartsRows.delete(processoId); 3053 gestaoSearchExpandedPartsRows.delete(processoId); 3054 if (toggle) { 3055 toggle.classList.remove('is-open'); 3056 toggle.setAttribute('aria-expanded', 'false'); 3057 } 3058 return; 3059 } 3060 3061 const processo = processosDataGestao.find(p => gestaoProcessoId(p) === processoId); 3062 const parts = gestaoResolveMergedParts(processo); 3063 if (!parts.length) return; 3064 row.parentNode.insertBefore(buildGestaoPartsDetailRow(processoId, parts), row.nextSibling); 3065 gestaoExpandedPartsRows.add(processoId); 3066 if (toggle) { 3067 toggle.classList.add('is-open'); 3068 toggle.setAttribute('aria-expanded', 'true'); 3069 } 3070} 3071 3072function renderTabelaProcessosGestao(processos) { 3073 const tbody = gestaoGetById('tabelaProcessosBody'); 3074 if (!tbody) return; 3075 3076 if (processos.length === 0) { 3077 tbody.innerHTML = ''; 3078 setGestaoProcessosTableOverlay( 3079 'empty', 3080 __isEN ? 'No cases found.' : 'Nenhum processo encontrado.' 3081 ); 3082 return; 3083 } 3084 3085 setGestaoProcessosTableOverlay('hide'); 3086 3087 const fragment = document.createDocumentFragment(); 3088 const searchTerm = gestaoGetSearchTerm(); 3089 3090 processos.forEach(p => {
3091 const row = document.createElement('tr'); 3092 const processoId = gestaoProcessoId(p); 3093 const dataUpload = new Date(p.uploadDate).toLocaleDateString(__isEN ? 'en-US' : 'pt-BR'); 3094 const displayStatus = gestaoEffectiveStatus(p); 3095 const statusClass = getStatusClassGestao(displayStatus); 3096 const statusLabel = getStatusLabelGestao(displayStatus); 3097 const selection = getGestaoRowSelection(processoId); 3098 const isChecked = selection.selected && selection.peca ? 'checked' : ''; 3099 3100 const isBusy = gestaoRowIsBusy(p); 3101 const isProcessing = isBusy; 3102 const jobErrorMessage = p.status === 'erro' ? gestaoResolveJobErrorMessage(p) : ''; 3103 const errorTitle = jobErrorMessage ? `title="${escapeGestaoHtml(jobErrorMessage).replace(/"/g, '"')}"` : ''; 3104 const errorHint = jobErrorMessage 3105 ? `<div class="gp-status-error-hint">${escapeGestaoHtml(jobErrorMessage.length > 140 ? jobErrorMessage.slice(0, 137) + '...' : jobErrorMessage)}</div>` 3106 : ''; 3107 const busyAttr = isBusy ? 'disabled' : ''; 3108 const deleteBusyStyle = isBusy ? 'opacity: 0.5; cursor: not-allowed;' : ''; 3109 3110 // Processo compartilhado pelo dono da Persona: colaborador vê e gera peças, 3111 // mas não pode excluir o que não subiu. 3112 const ownedByMe = p.ownedByMe !== false; 3113 const sharedBadge = !ownedByMe 3114 ? `<span class="gp-shared-badge" title="${__isEN ? 'Shared by the Persona owner' : 'Compartilhado pelo dono da Persona'}"><i class="fa fa-users" aria-hidden="true"></i></span>` 3115 : ''; 3116 const deleteCell = ownedByMe 3117 ? `<button class="btn-icon btn-delete-processo" style="color: #d32f2f; background: none; border: 1px solid #e2e8f0; padding: 6px; border-radius: 4px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; ${deleteBusyStyle}" title="Excluir Processo" onclick="deleteProcessGestao('${processoId}')" ${busyAttr}> 3118 <i class="fa fa-trash"></i> 3119 </button>` 3120 : `<span class="btn-icon" style="color:#cbd5e1; background:none; border:1px solid #eef2f7; padding:6px; border-radius:4px; display:inline-flex; align-items:center; justify-content:center; cursor:not-allowed;" title="${__isEN ? 'Only the owner can delete this case' : 'Somente o dono pode excluir este processo'}"> 3121 <i class="fa fa-lock"></i> 3122 </span>`; 3123 3124 // Verifica se veio da Chrome Extension 3125 const isFromExtension = p.source === 'chrome-extension'; 3126 const hasRealFile = p.fileId && !p.filename?.endsWith('.txt'); 3127 const canDownload = !!hasRealFile; 3128 const fileIcon = isFromExtension ? 'fa-chrome' : 'fa-file-pdf-o'; 3129 const fileColor = canDownload ? '#007bff' : '#64748b'; 3130 const fileCursor = canDownload ? 'pointer' : 'default'; 3131 const fileOnclick = canDownload ? `onclick="verProcessoGestao('${p.fileId}')"` : ''; 3132 const fileTitle = canDownload ? (isFromExtension ? 'Ver PDF (via PJe Extension)' : 'Ver PDF') : 'Sem PDF disponÃvel'; 3133 const extensionBadge = isFromExtension ? '<span style="font-size: 0.7rem; color: #94a3b8; margin-left: 4px;">(Ext)</span>' : ''; 3134 3135 // Parâmetros herdados (ex.: da análise individual do mesmo PDF). 3136 // Exibidos como colunas editáveis para o usuário ajustar ou remover 3137 // antes de gerar a peça em lote. 3138 const escGpAttr = (s) => String(s == null ? '' : s) 3139 .replace(/&/g, '&').replace(/"/g, '"') 3140 .replace(/</g, '<').replace(/>/g, '>'); 3141 const paramOverride = gestaoRowParamOverrides.get(processoId); 3142 const inheritedPrompt = escGpAttr(paramOverride ? paramOverride.additionalPrompt : (p.additionalPrompt || '')); 3143 const inheritedPromptTitle = escGpAttr(paramOverride ? paramOverride.additionalPromptTitle : (p.additionalPromptTitle || '')); 3144 const inheritedPages = escGpAttr(paramOverride ? paramOverride.includePages : (p.includePages || '')); 3145 const isAnon = paramOverride ? !!paramOverride.enableAnonymization : !!p.enableAnonymization; 3146 const rawPrompt = paramOverride ? paramOverride.additionalPrompt : (p.additionalPrompt || ''); 3147 const rawPromptTitle = paramOverride ? paramOverride.additionalPromptTitle : (p.additionalPromptTitle || ''); 3148 const promptSummary = escGpAttr(getGestaoPromptLabel(rawPrompt, rawPromptTitle)); 3149 const promptIcon = String(rawPrompt || '').trim() ? 'fa-pencil' : 'fa-plus'; 3150 const promptClass = String(rawPrompt || '').trim() ? ' has-prompt' : ''; 3151 const inheritedModelId = escGpAttr(paramOverride ? (paramOverride.modelId || '') : (p.modelId || '')); 3152 const inheritedModelTitleRaw = paramOverride ? (paramOverride.modelTitle || '') : (p.modelTitle || ''); 3153 const resolvedModelTitle = getGestaoModelTitle(inheritedModelId, inheritedModelTitleRaw); 3154 const inheritedModelTitle = escGpAttr(resolvedModelTitle); 3155 const modelSummary = escGpAttr(getGestaoModelLabel(inheritedModelId, resolvedModelTitle)); 3156 const modelIcon = inheritedModelId ? 'fa-pencil' : 'fa-plus'; 3157 const modelClass = inheritedModelId ? ' has-model' : ''; 3158 const modelBusyAttr = (isBusy || !gestaoModelsCanEdit()) ? 'disabled' : ''; 3159 3160 // Origem Word em upload não unificado: o nome exibido é o do PDF convert
3160ido. 3161 const wordOrigins = Array.isArray(p.convertedFromWord) ? p.convertedFromWord.filter(Boolean) : []; 3162 const wordBadge = wordOrigins.length 3163 ? `<span class="gp-autos-detail-badge" style="margin-left:4px;" title="${escapeGestaoHtml(__isEN ? 'Some component files were converted from Word to PDF.' : 'Há arquivos componentes convertidos de Word para PDF.')}">Word</span>` 3164 : ''; 3165 3166 // Processo unificado: caret abre a linha de detalhe com cada arquivo de origem. 3167 const mergedParts = gestaoResolveMergedParts(p); 3168 const partsExpanded = gestaoExpandedPartsRows.has(processoId); 3169 const partsToggle = mergedParts.length 3170 ? `<button type="button" class="gp-autos-toggle${partsExpanded ? ' is-open' : ''}" aria-expanded="${partsExpanded ? 'true' : 'false'}" title="${__isEN ? 'Show the files that make up this unified case' : 'Ver os arquivos que formam este processo unificado'}"><span class="gp-autos-toggle-count">${mergedParts.length}</span><i class="fa fa-caret-down" aria-hidden="true"></i></button>` 3171 : ''; 3172 3173 row.innerHTML = ` 3174 <td><input type="checkbox" class="processo-checkbox" value="${processoId}" ${isChecked} ${busyAttr}></td> 3175 <td class="gp-status-cell"><span class="tag-status ${statusClass}" ${errorTitle}>${isProcessing ? '<i class="fa fa-spinner fa-spin"></i>' : ''}${statusLabel}</span>${errorHint}</td> 3176 <td class="gp-autos-cell ${canDownload ? 'clickable-cell' : ''}" ${fileOnclick} style="cursor: ${fileCursor}; color: ${fileColor}; font-weight: 600;" title="${fileTitle}"> 3177 <i class="fa ${fileIcon}" style="margin-right: 5px;"></i><span class="gp-autos-name">${highlightGestaoFilename(p.filename || 'Sem nome', searchTerm)}</span>${extensionBadge}${wordBadge}${sharedBadge}${partsToggle} 3178 </td> 3179 <td class="gp-size-cell">${buildGestaoSizeCellHtml(p)}</td> 3180 <td class="gp-peca-cell"> 3181 <select class="row-peca-select" ${busyAttr}> 3182 <option value="">Selecione a peça</option> 3183 </select> 3184 </td> 3185 <td class="gp-param-cell gp-param-cell--instruction"> 3186 <div class="row-additional-prompt-control"> 3187 <input type="hidden" class="row-additional-prompt" value="${inheritedPrompt}"> 3188 <input type="hidden" class="row-additional-prompt-title" value="${inheritedPromptTitle}"> 3189 <button type="button" class="row-additional-prompt-btn${promptClass}" title="${inheritedPromptTitle || inheritedPrompt || (__isEN ? 'Add or choose additional instructions' : 'Adicionar ou selecionar instruções adicionais')}" ${busyAttr}> 3190 <i class="fa ${promptIcon}" aria-hidden="true"></i> 3191 </button> 3192 <span class="row-additional-prompt-label" title="${inheritedPromptTitle || inheritedPrompt}">${promptSummary}</span> 3193 </div> 3194 </td> 3195 <td class="gp-param-cell gp-param-cell--model"> 3196 <div class="row-model-control"> 3197 <input type="hidden" class="row-model-id" value="${inheritedModelId}"> 3198 <input type="hidden" class="row-model-title" value="${inheritedModelTitle}"> 3199 <button type="button" class="row-model-btn${modelClass}" title="${inheritedModelTitle || (__isEN ? 'Add or choose a template' : 'Adicionar ou selecionar um modelo')}" ${modelBusyAttr}> 3200 <i class="fa ${modelIcon}" aria-hidden="true"></i> 3201 </button> 3202 <span class="row-model-label" title="${inheritedModelTitle}">${modelSummary}</span> 3203 </div> 3204 </td> 3205 <td class="gp-param-cell gp-param-cell--center gp-param-cell--pages"> 3206 <input type="text" class="row-include-pages gp-param-input gp-param-input--pages" value="${inheritedPages}" placeholder="${__isEN ? 'All' : 'Todas'}" ${busyAttr}> 3207 </td> 3208 <td class="gp-param-cell gp-param-cell--center"> 3209 <input type="checkbox" class="row-anonymization" ${isAnon ? 'checked' : ''} ${busyAttr}> 3210 </td> 3211 <td class="gp-generated-piece-cell" style="text-align: center;"> 3212 ${buildGestaoPecaViewCellHtml(p, processoId)} 3213 </td> 3214 <td>${dataUpload}</td> 3215 <td style="vertical-align: middle; text-align: center;"> 3216 ${deleteCell} 3217 </td> 3218 `; 3219 bindGestaoRowEvents(row); 3220 fragment.appendChild(row); 3221 if (mergedParts.length && partsExpanded) { 3222 fragment.appendChild(buildGestaoPartsDetailRow(processoId, mergedParts)); 3223 } 3224 }); 3225 3226 tbody.innerHTML = ''; 3227 tbody.appendChild(fragment); 3228 3229 // Populate peca selects 3230 const personaSelect = gestaoGetById('personaSelectGestao'); 3231 if (personaSelect) { 3232 populateRowPecaSelectsGestao(personaSelect.value); 3233 } 3234
3235 // Populate model selects (usa cache atual e recarrega em background). 3236 populateRowModelSelectsGestao(); 3237 loadGestaoModels().then(() => populateRowModelSelectsGestao()).catch(() => { }); 3238 3239 tbody.querySelectorAll('tr').forEach(updateProcessoRowHighlightGestao); 3240 scheduleGestaoProcessosTableOverflowUpdate(); 3241} 3242 3243function updateProcessoRowHighlightGestao(row) { 3244 if (!row) return; 3245 const checkbox = row.querySelector('.processo-checkbox'); 3246 row.classList.toggle('processo-row-selected', !!(checkbox && checkbox.checked)); 3247} 3248 3249function rowHasPecaGestao(row) { 3250 const pecaSelect = row ? row.querySelector('.row-peca-select') : null; 3251 return !!(pecaSelect && pecaSelect.value); 3252} 3253 3254function bindGestaoRowEvents(row) { 3255 if (!row || row.dataset.gestaoEventsBound === 'true') return; 3256 row.dataset.gestaoEventsBound = 'true'; 3257 3258 const pecaSelect = row.querySelector('.row-peca-select'); 3259 const checkbox = row.querySelector('.processo-checkbox'); 3260 const promptInput = row.querySelector('.row-additional-prompt'); 3261 const promptBtn = row.querySelector('.row-additional-prompt-btn'); 3262 const pagesInput = row.querySelector('.row-include-pages'); 3263 const anonCheckbox = row.querySelector('.row-anonymization'); 3264 const modelBtn = row.querySelector('.row-model-btn'); 3265 3266 if (pecaSelect) { 3267 pecaSelect.addEventListener('mousedown', (e) => { 3268 if (personaSelectedGestao()) return; 3269 e.preventDefault(); 3270 e.stopPropagation(); 3271 pecaSelect.blur(); 3272 warnPersonaRequiredGestao(); 3273 }); 3274 pecaSelect.addEventListener('focus', () => { 3275 if (gestaoBlockPecaFocus || personaSelectedGestao()) return; 3276 pecaSelect.blur(); 3277 focusPersonaSelectGestao(); 3278 }); 3279 pecaSelect.addEventListener('change', () => { 3280 if (!personaSelectedGestao()) { 3281 revertPecaSelectGestao(pecaSelect); 3282 warnPersonaRequiredGestao(); 3283 return; 3284 } 3285 if (checkbox) { 3286 setProcessoCheckboxGestao(checkbox, !!pecaSelect.value); 3287 } else { 3288 syncGestaoRowSelectionFromDom(row); 3289 } 3290 applyGestaoPromptAvailability(row); 3291 syncSelecionarTodosGestao(); 3292 }); 3293 } 3294 3295 if (checkbox) { 3296 checkbox.addEventListener('change', () => { 3297 if (checkbox.checked && !rowHasPecaGestao(row)) { 3298 setProcessoCheckboxGestao(checkbox, false); 3299 const personaSelect = gestaoGetById('personaSelectGestao'); 3300 if (!personaSelect || !personaSelect.value) { 3301 warnPersonaRequiredGestao(); 3302 } else if (pecaSelect) { 3303 highlightPecaSelectGestao(pecaSelect); 3304 } 3305 return; 3306 } 3307 setProcessoCheckboxGestao(checkbox, checkbox.checked); 3308 syncSelecionarTodosGestao(); 3309 }); 3310 } 3311 3312 [promptInput, pagesInput, anonCheckbox].forEach(el => { 3313 if (!el) return; 3314 el.addEventListener('change', () => syncGestaoRowParamsFromDom(row)); 3315 }); 3316 if (modelBtn) modelBtn.addEventListener('click', () => openGestaoModelModal(row)); 3317 const modelLabel = row.querySelector('.row-model-label'); 3318 if (modelLabel) { 3319 modelLabel.addEventListener('click', () => { 3320 if (modelBtn && modelBtn.disabled) return; 3321 openGestaoModelModal(row); 3322 }); 3323 } 3324 // A célula "Autos" inteira abre o PDF, então o caret precisa parar a propagação. 3325 const partsToggleBtn = row.querySelector('.gp-autos-toggle'); 3326 if (partsToggleBtn) { 3327 partsToggleBtn.addEventListener('click', (e) => { 3328 e.preventDefault(); 3329 e.stopPropagation(); 3330 toggleGestaoPartsRow(getGestaoRowId(row), row); 3331 }); 3332 } 3333 3334 if (promptBtn) promptBtn.addEventListener('click', () => openGestaoPromptModal(row)); 3335 if (promptInput) { 3336 promptInput.addEventListener('input', () => { 3337 syncGestaoRowParamsFromDom(row); 3338 updateGestaoPromptCell(row); 3339 }); 3340 } 3341 [pagesInput].forEach(el => { 3342 if (!el) return; 3343 el.addEventListener('input', () => syncGestaoRowParamsFromDom(row)); 3344 }); 3345} 3346 3347function highlightPecaSelectGestao(select) { 3348 if (!select) return; 3349 select.classList.remove('row-peca-select--attention'); 3350 void select.offsetWidth; 3351 select.classList.add('row-peca-select--attention'); 3352 select.focus(); 3353 select.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); 3354 setTimeout(() => select.classList.remove('row-peca-select--attention'), 2500); 3355} 3356 3357function scrollToPersonaSelectGestao() { 3358 const personasSection = gestaoQuery('#meus-processos .personas-section') || gestaoGetById('personaSelectGestao'); 3359 if (personasSection) { 3360 personasSection.scrollIntoView({ behavior: 'smooth', block: 'center' }); 3361 } 3362} 3363 3364let gestaoPersonaWarnTs = 0; 3365let gestaoBlockPecaFocus = false;
3366 3367function focusPersonaSelectGestao() { 3368 scrollToPersonaSelectGestao(); 3369 const personaSelect = gestaoGetById('personaSelectGestao'); 3370 if (!personaSelect) return; 3371 gestaoBlockPecaFocus = true; 3372 try { 3373 personaSelect.focus({ preventScroll: true }); 3374 } catch (_) { 3375 personaSelect.focus(); 3376 } 3377 requestAnimationFrame(() => { 3378 gestaoBlockPecaFocus = false; 3379 }); 3380} 3381 3382function warnPersonaRequiredGestao() { 3383 const now = Date.now(); 3384 focusPersonaSelectGestao(); 3385 if (now - gestaoPersonaWarnTs < 800) return; 3386 gestaoPersonaWarnTs = now; 3387 funcMsg('warning', __L.attn, __isEN 3388 ? 'Please select a Persona before choosing a draft.' 3389 : 'Selecione um Perfil (Persona) antes de escolher a peça.', '', '', 'personaSelectGestao', gestaoInsightOptions('choose_piece_without_persona', { 3390 errorCode: 'validation:persona_required', 3391 context: { missingFields: ['persona'], trigger: 'piece_select' } 3392 })); 3393} 3394 3395function personaSelectedGestao() { 3396 const personaSelect = gestaoGetById('personaSelectGestao'); 3397 return !!(personaSelect && personaSelect.value); 3398} 3399 3400function revertPecaSelectGestao(select) { 3401 if (!select) return; 3402 select.value = ''; 3403 const row = select.closest('tr'); 3404 const checkbox = row ? row.querySelector('.processo-checkbox') : null; 3405 if (checkbox) setProcessoCheckboxGestao(checkbox, false); 3406 if (row) syncGestaoRowSelectionFromDom(row); 3407 if (document.activeElement === select) select.blur(); 3408 syncSelecionarTodosGestao(); 3409} 3410 3411function setProcessoCheckboxGestao(checkbox, checked) { 3412 if (!checkbox) return; 3413 checkbox.checked = checked; 3414 const row = checkbox.closest('tr'); 3415 updateProcessoRowHighlightGestao(row); 3416 syncGestaoRowSelectionFromDom(row); 3417} 3418 3419function syncSelecionarTodosGestao() { 3420 updateGerarPecasButtonText(); 3421 const checkboxSelecionarTodos = gestaoGetById('selecionarTodos'); 3422 const checkboxes = gestaoQueryAll('.processo-checkbox'); 3423 if (!checkboxSelecionarTodos || checkboxes.length === 0) return; 3424 checkboxSelecionarTodos.checked = Array.from(checkboxes).every(cb => cb.checked); 3425 checkboxSelecionarTodos.indeterminate = !checkboxSelecionarTodos.checked && 3426 Array.from(checkboxes).some(cb => cb.checked); 3427} 3428 3429function getGestaoPersonaPlaceholder() { 3430 return __isEN ? 'Select a persona' : 'Selecione uma persona'; 3431} 3432 3433function populatePersonaSelectGestaoOptions(select, personas) { 3434 if (!select) return; 3435 const previousValue = select.value; 3436 select.innerHTML = ''; 3437 const emptyOption = document.createElement('option'); 3438 emptyOption.value = ''; 3439 emptyOption.textContent = getGestaoPersonaPlaceholder(); 3440 select.appendChild(emptyOption); 3441 (personas || []).forEach(persona => { 3442 const option = document.createElement('option'); 3443 option.value = persona; 3444 option.textContent = persona; 3445 select.appendChild(option); 3446 }); 3447 if (previousValue && personas.includes(previousValue)) { 3448 select.value = previousValue; 3449 } 3450} 3451 3452function getErpPersonaPolicyGestao() { 3453 if (typeof window.cjErpPersonaPolicy !== 'function') return null; 3454 try { 3455 return window.cjErpPersonaPolicy(); 3456 } catch (_) { 3457 return null; 3458 } 3459} 3460 3461function applyErpPersonaPolicyGestao() { 3462 if (!gestaoAssessorConfigLoaded) return; 3463 const personaSelect = gestaoGetById('personaSelectGestao'); 3464 if (!personaSelect) return; 3465 3466 const policy = getErpPersonaPolicyGestao(); 3467 const allowed = policy && Array.isArray(policy.allowed) && policy.allowed.length 3468 ? policy.allowed.filter(persona => gestaoUniquePersonas.includes(persona)) 3469 : gestaoUniquePersonas.slice(); 3470 const selected = policy && policy.selected && allowed.includes(policy.selected) ? policy.selected : ''; 3471 const locked = !!(policy && policy.locked && selected); 3472 3473 const previousValue = personaSelect.value; 3474 populatePersonaSelectGestaoOptions(personaSelect, allowed); 3475 if (selected) { 3476 personaSelect.value = selected; 3477 } else if (previousValue && allowed.includes(previousValue)) { 3478 personaSelect.value = previousValue; 3479 } 3480 personaSelect.disabled = locked; 3481 personaSelect.title = locked ? 'Persona definida pelo perfil da organização' : ''; 3482 if (personaSelect.value !== previousValue) { 3483 personaSelect.dispatchEvent(new Event('change', { bubbles: true })); 3484 } else if (personaSelect.value) { 3485 populateRowPecaSelectsGestao(personaSelect.value); 3486 } 3487} 3488 3489async function loadGestaoAssessorConfig() { 3490 try { 3491 const url = '/extension/assessor-config' + (__isEN ? '?lang=en' : ''); 3492 const response = await fetch(url); 3493 const data = await response.json(); 3494 if (!response.ok || !data.success || !Array.isArray(data.buttonConfig)) { 3495 console.error('[Gestão Processual] erro ao carregar assessor-config'); 3496 return; 3497 } 3498 buttonConfig = data.buttonConfig; 3499 gestaoUniquePersonas = [...new Set(buttonConfig.flatMap(c => (c.personas || []).map(p => p.label)))].sort(); 3500 gestaoAssessorConfigLoaded = true; 3501 applyErpPersonaPolicyGestao(); 3502 } catch (err) { 3503 console.error('[Gestão Processual] erro ao carregar assessor-config:', err); 3504 } 3505} 3506 3507window.cjApplyErpPersonaPolicy = applyErpPersonaPolicyGestao; 3508window.cjAssessorLoadProcessos = loadProcessosGestao; 3509 3510function gestaoPecaLabelFromButton(button) { 3511 if (!button) return ''; 3512 const routeKey = String(button).split('-')[0]; 3513 if (routeKey === 'outra') return __isEN ? 'Automatic' : 'Automática'; 3514 if (routeKey === 'other') return 'Automatic'; 3515 if (routeKey === 'livre') return __isEN ? 'Free' : 'Livre'; 3516 if (routeKey === 'free') return 'Free'; 3517 const config = buttonConfig; 3518 for (const c of config) { 3519 for (const persona of c.personas || []) { 3520 if (persona.button === button) return c.text; 3521 } 3522 } 3523 return button; 3524} 3525
3526function gestaoPecaRouteKey(value) { 3527 return String(value || '').split('-')[0] || ''; 3528} 3529 3530function gestaoIsAutomaticPeca(value) { 3531 const key = gestaoPecaRouteKey(value); 3532 return key === 'outra' || key === 'other'; 3533} 3534 3535function gestaoIsLivrePeca(value) { 3536 const key = gestaoPecaRouteKey(value); 3537 return key === 'livre' || key === 'free'; 3538} 3539 3540function applyGestaoPromptAvailability(row) { 3541 if (!row) return; 3542 const pecaSelect = row.querySelector('.row-peca-select'); 3543 const promptBtn = row.querySelector('.row-additional-prompt-btn'); 3544 const promptInput = row.querySelector('.row-additional-prompt'); 3545 const titleInput = row.querySelector('.row-additional-prompt-title'); 3546 if (!pecaSelect || !promptBtn) return; 3547 3548 const peca = pecaSelect.value || ''; 3549 const isAuto = gestaoIsAutomaticPeca(peca); 3550 const rowId = getGestaoRowId(row); 3551 const processo = processosDataGestao.find(item => gestaoProcessoId(item) === rowId); 3552 const busy = processo ? gestaoRowIsBusy(processo) : false; 3553 3554 if (isAuto) { 3555 if (promptInput && String(promptInput.value || '').trim()) { 3556 promptInput.value = ''; 3557 if (titleInput) titleInput.value = ''; 3558 syncGestaoRowParamsFromDom(row); 3559 updateGestaoPromptCell(row); 3560 } 3561 promptBtn.disabled = true; 3562 promptBtn.classList.add('is-disabled'); 3563 promptBtn.title = __isEN 3564 ? 'Additional prompts are disabled for Automatic' 3565 : 'Prompts adicionais desativados na opção Automática'; 3566 return; 3567 } 3568 3569 promptBtn.disabled = busy; 3570 promptBtn.classList.remove('is-disabled'); 3571 const existingPrompt = promptInput ? String(promptInput.value || '').trim() : ''; 3572 const existingTitle = titleInput ? String(titleInput.value || '').trim() : ''; 3573 promptBtn.title = existingPrompt 3574 ? (existingTitle || existingPrompt) 3575 : (__isEN ? 'Add or choose additional instructions' : 'Adicionar ou selecionar instruções adicionais'); 3576} 3577 3578function populateRowPecaSelectsGestao(persona) { 3579 const selects = gestaoQueryAll('.row-peca-select'); 3580 const config = buttonConfig; 3581 3582 selects.forEach(select => { 3583 const row = select.closest('tr'); 3584 const checkbox = row ? row.querySelector('.processo-checkbox') : null; 3585 const rowId = getGestaoRowId(row); 3586 const savedSelection = getGestaoRowSelection(rowId); 3587 3588 select.innerHTML = `<option value="">${__L.selectPiece2}</option>`; 3589 3590 if (persona && config.length > 0) { 3591 const availablePieces = config 3592 .filter(c => c.personas.some(p => p.label === persona)) 3593 .map(c => ({ 3594 text: c.text, 3595 button: c.personas.find(p => p.label === persona).button 3596 })) 3597 .sort((a, b) => a.text.localeCompare(b.text)); 3598 3599 const _otherKey = __isEN ? 'other' : 'outra'; 3600 const _livreKey = __isEN ? 'free' : 'livre'; 3601 const sampleBtn = availablePieces.length > 0 3602 ? availablePieces[0].button.split('-').slice(1).join('-') 3603 : persona.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/\s+/g, '_'); 3604 const undefinedGroup = document.createElement('optgroup'); 3605 undefinedGroup.label = __isEN ? 'Undefined' : 'Indefinida'; 3606 3607 const otherOpt = document.createElement('option'); 3608 otherOpt.value = _otherKey + '-' + sampleBtn; 3609 otherOpt.textContent = __isEN ? 'Automatic' : 'Automática'; 3610 undefinedGroup.appendChild(otherOpt); 3611 3612 const livreOpt = document.createElement('option'); 3613 livreOpt.value = _livreKey + '-' + sampleBtn; 3614 livreOpt.textContent = __isEN ? 'Free' : 'Livre'; 3615 undefinedGroup.appendChild(livreOpt); 3616 3617 select.appendChild(undefinedGroup); 3618 3619 const definedGroup = document.createElement('optgroup'); 3620 definedGroup.label = __isEN ? 'Defined' : 'Definida'; 3621 availablePieces.forEach(piece => { 3622 const option = document.createElement('option'); 3623 option.value = piece.button; 3624 option.textContent = piece.text; 3625 definedGroup.appendChild(option); 3626 }); 3627 select.appendChild(definedGroup); 3628 } 3629 3630 const savedPecaExists = !!(savedSelection.peca && 3631 Array.from(select.options).some(option => option.value === savedSelection.peca)); 3632 if (savedPecaExists) { 3633 select.value = savedSelection.peca; 3634 if (checkbox) setProcessoCheckboxGestao(checkbox, !!savedSelection.selected); 3635 } else if (savedSelection.peca) { 3636 const staleOpt = document.createElement('option'); 3637 staleOpt.value = savedSelection.peca; 3638 staleOpt.textContent = gestaoPecaLabelFromButton(savedSelection.peca); 3639 select.appendChild(staleOpt); 3640 select.value = savedSelection.peca; 3641 if (checkbox) setProcessoCheckboxGestao(checkbox, !!savedSelection.selected); 3642 } else { 3643 select.value = ''; 3644 if (checkbox) { 3645 checkbox.checked = false;
3646 updateProcessoRowHighlightGestao(row); 3647 } 3648 } 3649 const processo = processosDataGestao.find(item => gestaoProcessoId(item) === rowId); 3650 if (processo) applyGestaoRowBusyState(row, processo); 3651 applyGestaoPromptAvailability(row); 3652 updateProcessoRowHighlightGestao(row); 3653 }); 3654 3655 syncSelecionarTodosGestao(); 3656} 3657 3658function gestaoIsInQueue(p) { 3659 if (!p || p.status !== 'pendente') return false; 3660 return p.source === 'gestao-lote' || p.source === 'analise-individual'; 3661} 3662 3663function gestaoEffectiveStatus(p) { 3664 const status = p.status || 'pendente'; 3665 if (status === 'processando' || gestaoIsInQueue(p)) return 'processando'; 3666 return status; 3667} 3668 3669function getStatusClassGestao(status) { 3670 switch (status) { 3671 case 'concluido': return 'concluido'; 3672 case 'processando': return 'processando'; 3673 case 'erro': return 'erro'; 3674 default: return 'pendente'; 3675 } 3676} 3677 3678function getStatusLabelGestao(status) { 3679 if (__isEN) { 3680 switch (status) { 3681 case 'concluido': return 'Completed'; 3682 case 'processando': return 'Processing'; 3683 case 'erro': return 'Error'; 3684 default: return 'Pending'; 3685 } 3686 } 3687 switch (status) { 3688 case 'concluido': return 'ConcluÃdo'; 3689 case 'processando': return 'Processando'; 3690 case 'erro': return 'Erro'; 3691 default: return 'Pendente'; 3692 } 3693} 3694 3695// Upload functions 3696function initializeUploadEventsGestao() { 3697 const customFileSelector = gestaoGetById('customFileSelector'); 3698 const customUploadArea = gestaoGetById('customUploadArea'); 3699 const btnAdicionarProcesso = gestaoGetById('btnAdicionarProcesso'); 3700 const uploadModeInputs = gestaoQueryAll('input[name="gestaoUploadMode"]'); 3701 3702 if (customFileSelector && customUploadArea) { 3703 customUploadArea.addEventListener('dragover', (e) => { 3704 e.preventDefault(); 3705 customUploadArea.classList.add('dragover'); 3706 }); 3707 customUploadArea.addEventListener('dragleave', () => { 3708 customUploadArea.classList.remove('dragover'); 3709 }); 3710 customUploadArea.addEventListener('drop', (e) => { 3711 e.preventDefault(); 3712 customUploadArea.classList.remove('dragover'); 3713 handleFilesSelectGestao(e.dataTransfer.files); 3714 }); 3715 3716 customFileSelector.addEventListener('change', () => { 3717 handleFilesSelectGestao(customFileSelector.files); 3718 }); 3719 } 3720 3721 if (btnAdicionarProcesso) { 3722 btnAdicionarProcesso.addEventListener('click', uploadSelectedFilesGestao); 3723 } 3724 3725 uploadModeInputs.forEach(input => { 3726 input.addEventListener('change', () => { 3727 renderSelectedFilesListGestao(); 3728 updateGestaoUploadButtonText(); 3729 }); 3730 }); 3731 3732 updateGestaoUploadButtonText(); 3733} 3734 3735function getGestaoUploadMode() { 3736 const selectedMode = gestaoQuery('input[name="gestaoUploadMode"]:checked'); 3737 return selectedMode && selectedMode.value === 'merged' ? 'merged' : 'separate'; 3738} 3739 3740function getGestaoUploadButtonLabel() { 3741 const count = selectedUploadFilesGestao.length; 3742 const uploadMode = getGestaoUploadMode(); 3743 // Word também é aceito, então o rótulo fala em "arquivos", não em "PDFs". 3744 const fileLabel = __isEN 3745 ? (count === 1 ? 'file' : 'files') 3746 : (count === 1 ? 'arquivo' : 'arquivos'); 3747 const processLabel = count === 1 ? 'processo' : 'processos diferentes'; 3748 if (uploadMode === 'merged') { 3749 if (count === 0) { 3750 return __isEN ? 'Merge files and upload' : 'Unificar arquivos e cadastrar'; 3751 } 3752 if (count === 1) { 3753 return __isEN ? `Register ${count} ${fileLabel}` : `Cadastrar ${count} ${fileLabel}`; 3754 } 3755 return __isEN 3756 ? `Merge ${count} ${fileLabel} and upload` 3757 : `Unificar ${count} ${fileLabel} e cadastrar`; 3758 } 3759 3760 if (count === 0) { 3761 return __isEN ? 'Register different cases' : 'Cadastrar processos diferentes'; 3762 } 3763 return __isEN 3764 ? (count === 1 ? `Register ${count} case` : `Register ${count} different cases`) 3765 : `Cadastrar ${count} ${processLabel}`; 3766} 3767 3768function getGestaoUploadModeHint(count, uploadMode, totalBytes) { 3769 const fileLabel = __isEN 3770 ? (count === 1 ? 'file' : 'files') 3771 : (count === 1 ? 'arquivo' : 'arquivos'); 3772 if (uploadMode === 'merged') { 3773 return __isEN 3774 ? `${count} ${fileLabel} ${count === 1 ? 'will be merged' : 'will be merged'} into 1 case (${formatAssessorFileSizeLabel(totalBytes)})` 3775 : `${count} ${fileLabel} ${count === 1 ? 'será mesclado' : 'serão mesclados'} em 1 processo (${formatAssessorFileSizeLabel(totalBytes)})`;
3776 } 3777 return __isEN 3778 ? `${count} ${fileLabel} ${count === 1 ? 'will be registered as 1 case' : 'will become different cases'}` 3779 : `${count} ${fileLabel} ${count === 1 ? 'será cadastrado como 1 processo' : 'serão cadastrados como processos diferentes'}`; 3780} 3781 3782function getGestaoUploadButtonHtml(isLoading) { 3783 if (isLoading) { 3784 const loadingLabel = getGestaoUploadMode() === 'merged' 3785 ? (__isEN ? 'Merging...' : 'Unificando...') 3786 : (__isEN ? 'Uploading...' : 'Cadastrando...'); 3787 return '<i class="fa fa-spinner fa-spin"></i> ' + loadingLabel; 3788 } 3789 return getGestaoUploadButtonLabel(); 3790} 3791 3792function updateGestaoUploadButtonText(isLoading) { 3793 const btnAdicionarProcesso = gestaoGetById('btnAdicionarProcesso'); 3794 if (btnAdicionarProcesso) { 3795 btnAdicionarProcesso.innerHTML = getGestaoUploadButtonHtml(!!isLoading); 3796 } 3797} 3798 3799function getGerarPecasButtonLabel() { 3800 const count = gestaoQueryAll('.processo-checkbox:checked').length; 3801 if (__isEN) { 3802 if (count === 0) return 'GENERATE DRAFT'; 3803 if (count === 1) return 'GENERATE 1 DRAFT'; 3804 return `GENERATE ${count} DRAFTS`; 3805 } 3806 if (count === 0) return 'GERAR PEÃA'; 3807 if (count === 1) return 'GERAR 1 PEÃA'; 3808 return `GERAR ${count} PEÃAS`; 3809} 3810 3811function updateGerarPecasButtonText() { 3812 const btn = gestaoGetById('btnGerarPecasLote'); 3813 if (!btn || btn.dataset.loading === 'true') return; 3814 const count = gestaoQueryAll('.processo-checkbox:checked').length; 3815 btn.disabled = count === 0; 3816 btn.innerHTML = '<i class="fa fa-magic"></i> ' + getGerarPecasButtonLabel(); 3817} 3818 3819const GESTAO_DOCX_MIME = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; 3820const GESTAO_DOC_MIMES = ['application/msword', 'application/doc', 'application/vnd.ms-word']; 3821 3822// Alguns sistemas operacionais entregam file.type vazio ou genérico para Word, 3823// então a extensão vale como fallback. 3824function gestaoUploadFileKind(file) { 3825 const name = String(file?.name || '').toLowerCase(); 3826 const type = String(file?.type || '').toLowerCase(); 3827 if (type === 'application/pdf' || name.endsWith('.pdf')) return 'pdf'; 3828 if (type === GESTAO_DOCX_MIME || name.endsWith('.docx')) return 'docx'; 3829 if (GESTAO_DOC_MIMES.includes(type) || name.endsWith('.doc')) return 'doc'; 3830 return ''; 3831} 3832 3833function handleFilesSelectGestao(files) { 3834 if (!files || files.length === 0) return; 3835 3836 Array.from(files).forEach(file => { 3837 if (!gestaoUploadFileKind(file)) { 3838 funcMsg( 3839 'warning', 3840 'Atenção', 3841 __isEN 3842 ? `The file ${file.name} is not a PDF or Word document.` 3843 : `O arquivo ${file.name} não é PDF nem Word (DOCX/DOC).`, 3844 '', 3845 '' 3846 ); 3847 return; 3848 } 3849 if (!validateAssessorFileForStorage(file, { useFuncMsg: true })) { 3850 return; 3851 } 3852 if (!selectedUploadFilesGestao.some(f => f.name === file.name && f.size === file.size)) { 3853 selectedUploadFilesGestao.push(file); 3854 } 3855 }); 3856 3857 renderSelectedFilesListGestao(); 3858 updateGestaoUploadButtonText(); 3859 const customFileSelector = gestaoGetById('customFileSelector'); 3860 if (customFileSelector) customFileSelector.value = ''; 3861} 3862
3863function renderSelectedFilesListGestao() { 3864 const listaArquivos = gestaoGetById('listaArquivosSelecionadosGestao'); 3865 if (!listaArquivos) return; 3866 3867 listaArquivos.innerHTML = ''; 3868 3869 if (selectedUploadFilesGestao.length === 0) { 3870 updateGestaoUploadButtonText(); 3871 return; 3872 } 3873 3874 const escHtmlGestao = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); 3875 const uploadMode = getGestaoUploadMode(); 3876 const totalBytes = selectedUploadFilesGestao.reduce((sum, file) => sum + (file.size || 0), 0); 3877 if (selectedUploadFilesGestao.length > 1) { 3878 const modeHint = document.createElement('div'); 3879 modeHint.className = 'gestao-upload-mode-hint'; 3880 modeHint.innerHTML = uploadMode === 'merged' 3881 ? `<i class="fa fa-compress" aria-hidden="true"></i><span>${getGestaoUploadModeHint(selectedUploadFilesGestao.length, uploadMode, totalBytes)}</span>` 3882 : `<i class="fa fa-files-o" aria-hidden="true"></i><span>${getGestaoUploadModeHint(selectedUploadFilesGestao.length, uploadMode, totalBytes)}</span>`; 3883 listaArquivos.appendChild(modeHint); 3884 } 3885 3886 selectedUploadFilesGestao.forEach((file, index) => { 3887 const item = document.createElement('div'); 3888 item.className = 'arquivo-item'; 3889 const isWord = gestaoUploadFileKind(file) !== 'pdf'; 3890 const itemIcon = isWord ? 'fa-file-word-o' : 'fa-file-pdf-o'; 3891 const conversionNote = isWord 3892 ? ` <span class="arquivo-item-meta">${__isEN ? 'Converted to PDF' : 'Será convertido em PDF'}</span>` 3893 : ''; 3894 item.innerHTML = ` 3895 <div class="arquivo-item-body"> 3896 <i class="fa ${itemIcon} arquivo-item-icon" aria-hidden="true"></i> 3897 <div class="arquivo-item-text"> 3898 <span class="arquivo-item-name">${escHtmlGestao(normalizeGestaoFilename(file.name))}</span> 3899 <span class="arquivo-item-meta">(${Math.round(file.size / 1024)} KB)</span>${conversionNote} 3900 </div> 3901 </div> 3902 <button type="button" class="btn-remove-pdf" title="Remover" onclick="removeSelectedFileGestao(${index})"> 3903 <i class="fa fa-trash" aria-hidden="true"></i> 3904 </button> 3905 `; 3906 listaArquivos.appendChild(item); 3907 }); 3908} 3909 3910window.removeSelectedFileGestao = function (index) { 3911 selectedUploadFilesGestao.splice(index, 1); 3912 renderSelectedFilesListGestao(); 3913}; 3914 3915async function uploadSelectedFilesGestao() { 3916 if (selectedUploadFilesGestao.length === 0) { 3917 funcMsg('warning', 'Atenção', 'Selecione pelo menos um arquivo para inserir.', '', '', undefined, gestaoInsightOptions('upload_process_files_empty', { 3918 errorCode: 'validation:process_files_required', 3919 context: { missingFields: ['files'] } 3920 })); 3921 return; 3922 } 3923 3924 if (!checkAuthenticationBeforeAction()) { 3925 return; 3926 } 3927 3928 const token = cognijusUserToken || localStorage.getItem('CogniJUS_User_Token'); 3929 3930 const btnAdicionarProcesso = gestaoGetById('btnAdicionarProcesso'); 3931 if (btnAdicionarProcesso) { 3932 btnAdicionarProcesso.disabled = true; 3933 updateGestaoUploadButtonText(true); 3934 } 3935 3936 const uploadMode = getGestaoUploadMode(); 3937 let successCount = 0; 3938 let errorCount = 0; 3939 let lastErrorMessage = ''; 3940 3941 if (uploadMode === 'merged' && !validateAssessorFilesForAnalysis(selectedUploadFilesGestao, { useFuncMsg: true })) { 3942 if (btnAdicionarProcesso) { 3943 btnAdicionarProcesso.disabled = false; 3944 updateGestaoUploadButtonText(false); 3945 } 3946 return; 3947 } 3948 3949 if (uploadMode === 'merged') { 3950 const formData = new FormData(); 3951 selectedUploadFilesGestao.forEach(file => { 3952 formData.append('pdfFiles', file); 3953 }); 3954 formData.append('uploadMode', 'merged'); 3955 formData.append('form_objKey', JSON.stringify({ 3956 funcionalidadeName: 'Gestão Processual', 3957 uploadMode: 'merged', 3958 pdfCount: selectedUploadFilesGestao.length 3959 })); 3960 3961 try { 3962 const response = await fetch(base_url_gestao + '/processos/upload', { 3963 method: 'POST', 3964 headers: { 'x-access-token': token }, 3965 body: formData 3966 }); 3967 3968 if (response.ok) { 3969 successCount++; 3970 } else { 3971 errorCount++; 3972 const data = await response.json().catch(() => ({})); 3973 lastErrorMessage = data.msg || lastErrorMessage; 3974 } 3975 } catch (error) { 3976 errorCount++; 3977 lastErrorMessage = error.message || lastErrorMessage; 3978 } 3979 } else { 3980 for (const file of selectedUploadFilesGestao) { 3981 const formData = new FormData(); 3982 formData.append('pdfFile', file); 3983 formData.append('uploadMode', 'separate'); 3984 formData.append('form_objKey', JSON.stringify({ 3985 funcionalidadeName: 'Gestão Processual', 3986 uploadMode: 'separate', 3987 pdfCount: 1 3988 })); 3989 3990 try { 3991 const response = await fetch(base_url_gestao + '/processos/upload', { 3992 method: 'POST', 3993 headers: { 'x-access-token': token }, 3994 body: formData 3995 }); 3996 3997 if (response.ok) { 3998 successCount++; 3999 } else { 4000 errorCount++; 4001 const data = await response.json().catch(() => ({})); 4002 lastErrorMessage = data.msg || lastErrorMessage; 4003 } 4004 } catch (error) { 4005 errorCount++; 4006 lastErrorMessage = error.message || lastErrorMessage; 4007 } 4008 } 4009 } 4010 4011 if (btnAdicionarProcesso) { 4012 btnAdicionarProcesso.disabled = false;
4013 updateGestaoUploadButtonText(false); 4014 } 4015 4016 if (successCount > 0) { 4017 selectedUploadFilesGestao = []; 4018 renderSelectedFilesListGestao(); 4019 loadProcessosGestao(); 4020 if (typeof window.cjErpRefreshAfterAssessor === 'function') window.cjErpRefreshAfterAssessor(); 4021 const successMessage = gestaoUploadSuccessMessage(successCount, uploadMode); 4022 funcMsg('success', __isEN ? 'Success' : 'Sucesso', successMessage, '', ''); 4023 const tableContainer = gestaoQuery('.gestao-processual-table-container'); 4024 if (tableContainer) tableContainer.scrollIntoView({ behavior: 'smooth', block: 'start' }); 4025 } 4026 if (errorCount > 0) { 4027 funcMsg('warning', __L.attn, lastErrorMessage || gestaoUploadErrorMessage(errorCount), '', ''); 4028 } 4029} 4030 4031// Global functions for table actions 4032window.verProcessoGestao = function (fileId) { 4033 if (!fileId || fileId === 'undefined') { 4034 funcMsg('error', 'Erro', 'Arquivo não disponÃvel.', '', ''); 4035 return; 4036 } 4037 if (!checkAuthenticationBeforeAction()) { 4038 return; 4039 } 4040 const token = cognijusUserToken || localStorage.getItem('CogniJUS_User_Token'); 4041 window.open(base_url_gestao + '/processos/download/' + fileId + '?token=' + encodeURIComponent(token), '_blank'); 4042}; 4043 4044window.deleteProcessGestao = async function (id) { 4045 if (!checkAuthenticationBeforeAction()) { 4046 return; 4047 } 4048 const processo = processosDataGestao.find(p => gestaoProcessoId(p) === String(id || '')); 4049 if (processo && gestaoRowIsBusy(processo)) { 4050 return; 4051 } 4052 const token = cognijusUserToken || localStorage.getItem('CogniJUS_User_Token'); 4053 if (!confirm('Tem certeza que deseja excluir este processo?')) return; 4054 4055 try { 4056 const response = await fetch(base_url_gestao + '/processos/delete', { 4057 method: 'POST', 4058 headers: { 4059 'Content-Type': 'application/json', 4060 'x-access-token': token 4061 }, 4062 body: JSON.stringify({ processoId: id }) 4063 }); 4064 4065 if (response.ok) { 4066 gestaoRowSelections.delete(String(id || '')); 4067 funcMsg('success', 'Sucesso', 'Processo excluÃdo.', '', ''); 4068 loadProcessosGestao(); 4069 } else { 4070 funcMsg('error', 'Erro', 'Erro ao excluir processo.', '', '', undefined, gestaoInsightOptions('delete_process', { 4071 category: 'server', 4072 errorCode: `server:delete_process_http_${response.status}`, 4073 context: { processoId: id, httpStatus: response.status } 4074 })); 4075 } 4076 } catch (error) { 4077 funcMsg('error', 'Erro', 'Erro de conexão.', '', error && error.message ? error.message : '', undefined, gestaoInsightOptions('delete_process', { 4078 category: 'network', 4079 errorCode: 'network:delete_process_failed', 4080 context: { processoId: id, errorName: error && error.name ? error.name : undefined } 4081 })); 4082 } 4083}; 4084 4085// Seletor de minuta (substitui prompt()). Resolve com o Ãndice do array 4086// escolhido, ou null se cancelado. Estilo inline para funcionar na página do 4087// Assessor e no shadow DOM do ERP (o overlay vai para document.body). 4088function gestaoPickPecaIndex(pecas) { 4089 if (typeof window.pickAssessorPecaIndex === 'function') { 4090 return window.pickAssessorPecaIndex(pecas); 4091 } 4092 const list = Array.isArray(pecas) ? pecas : []; 4093 return Promise.resolve(list.length ? 0 : null); 4094} 4095window.gestaoPickPecaIndex = gestaoPickPecaIndex; 4096 4097function applyAssessorPecaListRename(processId, pecaIndex, title) { 4098 const id = String(processId || ''); 4099 const idx = parseInt(pecaIndex, 10); 4100 if (!id || !Number.isInteger(idx) || !Array.isArray(processosDataGestao)) return; 4101 processosDataGestao.forEach(p => { 4102 if (gestaoProcessoId(p) !== id || !Array.isArray(p.pecas)) return; 4103 p.pecas.forEach((peca, i) => { 4104 if (i === idx) peca.title = title || ''; 4105 }); 4106 }); 4107} 4108 4109(function bindAssessorPecaRename() { 4110 function onMsg(data) { 4111 if (!data || data.type !== 'cognijus-peca-renamed') return; 4112 applyAssessorPecaListRename(data.processId, data.pecaIndex, data.title); 4113 } 4114 window.addEventListener('message', function (ev) { 4115 if (ev.origin !== window.location.origin && ev.origin !== 'null') return; 4116 onMsg(ev.data); 4117 }); 4118 try { 4119 const ch = new BroadcastChannel('cognijus-peca'); 4120 ch.onmessage = function (ev) { onMsg(ev.data); }; 4121 } catch (e) { } 4122})(); 4123 4124window.verPecaGeradaGestao = async function (id, pecasParam, triggerEl, meta) { 4125 meta = meta || {}; 4126 if (!checkAuthenticationBeforeAction()) { 4127 return; 4128 } 4129 const token = cognijusUserToken || localStorage.getItem('CogniJUS_User_Token'); 4130 const processo = processosDataGestao.find(p =>
4130 gestaoProcessoId(p) === id); 4131 const filename = (processo && processo.filename) || meta.filename || 'Documento analisado'; 4132 const pecasFromProcess = (processo && Array.isArray(processo.pecas) && processo.pecas.length) 4133 ? processo.pecas 4134 : pecasParam; 4135 4136 // Se houver múltiplas peças, abre o seletor (modal) em vez de prompt(). 4137 let chosenPecaIndex = Number.isInteger(meta.pecaIndex) ? meta.pecaIndex : 0; 4138 if (!Number.isInteger(meta.pecaIndex) && pecasFromProcess && pecasFromProcess.length > 1) { 4139 const idx = await gestaoPickPecaIndex(pecasFromProcess); 4140 if (idx == null || idx < 0 || idx >= pecasFromProcess.length) return; 4141 chosenPecaIndex = idx; 4142 } 4143 4144 if (!setPecaViewLoadingGestao(triggerEl, true, id, chosenPecaIndex)) return; 4145 4146 try { 4147 const response = await fetch(base_url_gestao + '/processos/get-peca', { 4148 method: 'POST', 4149 headers: { 4150 'Content-Type': 'application/json', 4151 'x-access-token': token 4152 }, 4153 body: JSON.stringify({ processId: id, pecaIndex: chosenPecaIndex }) 4154 }); 4155 4156 const data = await response.json(); 4157 4158 if (response.ok && data.relatorioHtml) { 4159 // Peças antigas gravadas com falha do LLM como "conteúdo" (bug corrigido no worker). 4160 const rawHtml = String(data.relatorioHtml || '').trim(); 4161 const looksLikeProcessingError = /^(Erro no processamento\. Tente novamente\.|O processamento (excedeu o tempo limite|demorou mais que o esperado))/i.test(rawHtml.replace(/<[^>]+>/g, '').trim()); 4162 if (looksLikeProcessingError) { 4163 funcMsg( 4164 'error', 4165 __isEN ? 'Processing failed' : 'Falha na geração', 4166 __isEN 4167 ? 'This draft failed during AI generation. Please generate the piece again.' 4168 : 'Esta peça falhou na geração pela IA. Gere a peça novamente (o status aparece como concluÃdo, mas o conteúdo não foi gerado).', 4169 '', 4170 '' 4171 ); 4172 return; 4173 } 4174 if (typeof window.openAssessorPecaWindow !== 'function') { 4175 funcMsg('error', 'Erro', 'Não foi possÃvel abrir a peça.', '', ''); 4176 return; 4177 } 4178 const _wBaseUrl = (typeof base_url !== 'undefined' && base_url) ? base_url : window.location.origin; 4179 const _wAssetQ = (function () { 4180 try { 4181 var nodes = document.querySelectorAll('script[src], link[rel="stylesheet"][href]'); 4182 for (var i = 0; i < nodes.length; i++) { 4183 var href = nodes[i].src || nodes[i].href || ''; 4184 var m = String(href).match(/[?&]v=([^&]+)/); 4185 if (m) return '?v=' + encodeURIComponent(decodeURIComponent(m[1])); 4186 } 4187 } catch (e) { } 4188 return ''; 4189 })(); 4190 window.openAssessorPecaWindow({ 4191 html: assessorHtmlNewlinesToBr(data.relatorioHtml), 4192 filename: filename, 4193 selectedPeca: data.selectedPeca || (pecasFromProcess && pecasFromProcess[chosenPecaIndex] && pecasFromProcess[chosenPecaIndex].selectedPeca) || 'Minuta', 4194 title: data.title || (pecasFromProcess && pecasFromProcess[chosenPecaIndex] && pecasFromProcess[chosenPecaIndex].title) || '', 4195 precedentes: data.precedentes || '', 4196 processId: id, 4197 pecaIndex: chosenPecaIndex, 4198 canEdit: data.canEdit === true, 4199 token: cognijusUserToken || '', 4200 codigoUnico: (typeof codigoUnico !== 'undefined' ? codigoUnico : '') || '', 4201 adminCodigoUnico: '', 4202 funcName: funcionalidadeName || 'Assessor jurÃdico', 4203 llmModel: data.llmModel || (typeof getSelectedLLMModel === 'function' ? getSelectedLLMModel() : '') || 'chatgpt', 4204 isEN: typeof __isEN !== 'undefined' ? __isEN : false, 4205 baseUrl: _wBaseUrl, 4206 assetQ: _wAssetQ 4207 }); 4208 } else { 4209 funcMsg('error', 'Erro', 'Não foi possÃvel carregar a peça.', '', ''); 4210 } 4211 } catch (error) { 4212 funcMsg('error', 'Erro', 'Erro de conexão.', '', ''); 4213 } finally { 4214 setPecaViewLoadingGestao(triggerEl, false, id, chosenPecaIndex); 4215 } 4216}; 4217 4218function getPecaViewIconGestao(triggerEl) { 4219 if (!triggerEl || !triggerEl.classList) return null; 4220 if (triggerEl.classList.contains('peca-view-icon')) return triggerEl; 4221 const cell = triggerEl.closest ? triggerEl.closest('.peca-view-cell') : null; 4222 return cell ? cell.querySelector('.peca-view-icon') : null; 4223} 4224 4225function pecaViewLoadingKey(processId, pecaIndex) { 4226 const id = String(processId || ''); 4227 if (!id) return ''; 4228 if (Number.isInteger(pecaIndex)) return id + ':' + pecaIndex; 4229 return id; 4230} 4231 4232const gestaoPecaViewLoading = new Set(); 4233 4234function gestaoPecaViewIsOpening(processId, pecaIndex) { 4235 const id = String(processId || ''); 4236 if (!id || !gestaoPecaViewLoading.size) return false;
4237 if (Number.isInteger(pecaIndex)) return gestaoPecaViewLoading.has(id + ':' + pecaIndex); 4238 if (gestaoPecaViewLoading.has(id)) return true; 4239 const prefix = id + ':'; 4240 for (const key of gestaoPecaViewLoading) { 4241 if (key === id || key.indexOf(prefix) === 0) return true; 4242 } 4243 return false; 4244} 4245window.gestaoPecaViewIsOpening = gestaoPecaViewIsOpening; 4246 4247function applyPecaViewIconLoading(iconEl, loading) { 4248 if (!iconEl) return; 4249 if (loading) { 4250 iconEl.dataset.loading = '1'; 4251 iconEl.className = 'fa fa-spinner fa-spin peca-view-icon'; 4252 iconEl.style.color = '#1d4ed8'; 4253 iconEl.style.cursor = 'wait'; 4254 iconEl.style.opacity = '1'; 4255 iconEl.setAttribute('title', __isEN ? 'Opening draft...' : 'Abrindo a peça...'); 4256 return; 4257 } 4258 iconEl.dataset.loading = '0'; 4259 iconEl.className = 'fa fa-file-text-o peca-view-icon'; 4260 iconEl.style.color = '#007bff'; 4261 iconEl.style.cursor = 'pointer'; 4262 iconEl.setAttribute('title', 'Ver Peça Gerada'); 4263} 4264 4265function setErpPecaBtnLoading(btn, loading) { 4266 if (!btn || !btn.classList || !btn.classList.contains('cj-erp-gp-peca-btn')) return; 4267 btn.classList.toggle('is-loading', !!loading); 4268 btn.disabled = !!loading; 4269 btn.setAttribute('aria-busy', loading ? 'true' : 'false'); 4270 let spin = btn.querySelector('.cj-erp-gp-peca-spin'); 4271 if (loading) { 4272 if (!spin) { 4273 spin = document.createElement('i'); 4274 spin.className = 'fa fa-spinner fa-spin cj-erp-gp-peca-spin'; 4275 spin.setAttribute('aria-hidden', 'true'); 4276 btn.insertBefore(spin, btn.firstChild); 4277 } 4278 return; 4279 } 4280 if (spin) spin.remove(); 4281} 4282 4283function findLivePecaViewIcons(processId) { 4284 const id = String(processId || ''); 4285 const found = []; 4286 if (!id) return found; 4287 const tbody = typeof gestaoGetById === 'function' ? gestaoGetById('tabelaProcessosBody') : document.getElementById('tabelaProcessosBody'); 4288 if (tbody) { 4289 tbody.querySelectorAll('tr').forEach(row => { 4290 const cb = row.querySelector('.processo-checkbox'); 4291 if (!cb || String(cb.value) !== id) return; 4292 const icon = row.querySelector('.peca-view-icon'); 4293 if (icon) found.push(icon); 4294 }); 4295 } 4296 return found; 4297} 4298 4299function findLiveErpPecaButtons(processId, pecaIndex) { 4300 const id = String(processId || ''); 4301 const found = []; 4302 if (!id) return found; 4303 const host = document.getElementById('cj-erp-shadow-host'); 4304 const root = host && host.shadowRoot; 4305 if (!root) return found; 4306 root.querySelectorAll('[data-gp-peca="' + id + '"]').forEach(btn => { 4307 if (!Number.isInteger(pecaIndex)) { 4308 found.push(btn); 4309 return; 4310 } 4311 if (parseInt(btn.getAttribute('data-gp-peca-index'), 10) === pecaIndex) found.push(btn); 4312 }); 4313 return found; 4314} 4315 4316function setPecaViewLoadingGestao(triggerEl, loading, processId, pecaIndex) { 4317 const id = String(processId || ''); 4318 const key = pecaViewLoadingKey(id, pecaIndex); 4319 if (loading) { 4320 if (key && gestaoPecaViewLoading.has(key)) return false; 4321 if (key) gestaoPecaViewLoading.add(key); 4322 else { 4323 const iconEl = getPecaViewIconGestao(triggerEl); 4324 if (iconEl && iconEl.dataset.loading === '1') return false; 4325 } 4326 } else if (key) { 4327 gestaoPecaViewLoading.delete(key); 4328 } 4329 4330 const iconEl = getPecaViewIconGestao(triggerEl); 4331 const iconStill = loading || gestaoPecaViewIsOpening(id); 4332 applyPecaViewIconLoading(iconEl, iconStill); 4333 findLivePecaViewIcons(id).forEach(icon => applyPecaViewIconLoading(icon, iconStill)); 4334 4335 setErpPecaBtnLoading(triggerEl, loading); 4336 findLiveErpPecaButtons(id, pecaIndex).forEach(btn => setErpPecaBtnLoading(btn, loading)); 4337 return true; 4338} 4339 4340// Batch generation 4341function initializeBatchGenerationGestao() { 4342 const btnGerarPecasLote = gestaoGetById('btnGerarPecasLote'); 4343 const checkboxSelecionarTodos = gestaoGetById('selecionarTodos'); 4344 4345 if (btnGerarPecasLote) { 4346 btnGerarPecasLote.addEventListener('click', async () => { 4347 if (!checkAuthenticationBeforeAction()) { 4348 return; 4349 } 4350 4351 const checkboxes = gestaoQueryAll('.processo-checkbox:checked'); 4352 const token = cognijusUserToken || localStorage.getItem('CogniJUS_User_Token'); 4353 const personaSelect = gestaoGetById('personaSelectGestao'); 4354 4355 if (!personaSelect || !personaSelect.value) { 4356 funcMsg('warning', __L.attn, __isEN ? 'Please select a Persona before generating drafts in batch.' : 'Selecione um Perfil (Persona) antes de gerar as peças em lote.', '', '', undefined, gestaoInsightOptions('batch_generate_without_persona', { 4357 errorCode: 'validation:persona_required', 4358 context: { missingFields: ['persona'] } 4359 })); 4360 const personasSection = gestaoQuery('#meus-processos .personas-section') || gestaoGetById('personaSelectGestao'); 4361 if (personasSection) { 4362 personasSection.scrollIntoView({ behavior: 'smooth', block: 'center' }); 4363 } 4364 return; 4365 } 4366 4367 if (checkboxes.length === 0) { 4368 funcMsg('warning', __L.attn, __isEN ? 'Please select at least one case.' : 'Selecione ao menos um processo.', '', '', undefined, gestaoInsightOptions('batch_generate_without_selection', { 4369 errorCode: 'validation:process_selection_required', 4370 context: { missingFields: ['processSelection'] } 4371 })); 4372 return; 4373 } 4374 4375 if (checkboxes.length > 5) { 4376 funcMsg('warning', __L.attn, __isEN ? 'You can generate documents for up to 5 cases at a time.' : 'Você pode gerar peças para no máximo 5 processos por vez.', '', '', undefined, gestaoInsightOptions('batch_generate_limit_exceeded', { 4377 errorCode: 'validation:batch_limit_exceeded', 4378 context: { maxSelected: 5, attemptedSelected: checkboxes.length } 4379 })); 4380 return; 4381 } 4382 4383 // Plano gratuito: pode gerar 1 peça por vez; lote (2+) é exclusivo do PRO. 4384 if (!isAssessorUserPro() && checkboxes.length > 1) { 4385 if (typeof openCogniJUSProModal === 'function') { 4386 openCogniJUSProModal(__isEN 4387 ? 'Batch generation is available only for PRO users. On the free plan, generate one draft at a time.' 4388 : 'A geração em lote está disponÃvel apenas para usuários PRO. No plano gratuito, gere uma peça por vez.'); 4389 } 4390 return; 4391 } 4392 4393 const processosData = Array.from(checkboxes).map(cb => {
4394 const row = cb.closest('tr'); 4395 const pecaSelect = row.querySelector('.row-peca-select'); 4396 const promptInput = row.querySelector('.row-additional-prompt'); 4397 const promptTitleInput = row.querySelector('.row-additional-prompt-title'); 4398 const pagesInput = row.querySelector('.row-include-pages'); 4399 const anonCheckbox = row.querySelector('.row-anonymization'); 4400 const modelIdInput = row.querySelector('.row-model-id'); 4401 const modelTitleInput = row.querySelector('.row-model-title'); 4402 const modelBtn = row.querySelector('.row-model-btn'); 4403 const modelBusy = !!(modelBtn && modelBtn.disabled); 4404 const modelId = !modelBusy && modelIdInput ? String(modelIdInput.value || '').trim() : ''; 4405 const modelTitle = modelTitleInput ? String(modelTitleInput.value || '').trim() : ''; 4406 return { 4407 id: cb.value, 4408 peca: pecaSelect ? pecaSelect.value : '', 4409 additionalPrompt: promptInput ? promptInput.value.trim() : '', 4410 additionalPromptTitle: promptTitleInput ? promptTitleInput.value.trim() : '', 4411 includePages: pagesInput ? pagesInput.value.trim() : '', 4412 enableAnonymization: anonCheckbox ? !!anonCheckbox.checked : false, 4413 modelId: modelId, 4414 modelTitle: modelId ? getGestaoModelTitle(modelId, modelTitle) : '' 4415 }; 4416 }); 4417 4418 const processosSemPeca = processosData.filter(p => !p.peca || p.peca === ''); 4419 if (processosSemPeca.length > 0) { 4420 const firstRow = gestaoQuery(`.processo-checkbox[value="${processosSemPeca[0].id}"]`)?.closest('tr'); 4421 const pecaSelect = firstRow ? firstRow.querySelector('.row-peca-select') : null; 4422 funcMsg('warning', 'Atenção', __isEN 4423 ? 'Please select a draft for each marked case before generating.' 4424 : 'Selecione a peça desejada para cada processo marcado.', '', '', undefined, gestaoInsightOptions('batch_generate_missing_piece', { 4425 errorCode: 'validation:piece_required', 4426 context: { 4427 missingFields: ['piece'], 4428 missingPieceCount: processosSemPeca.length, 4429 selectedCount: checkboxes.length 4430 } 4431 })); 4432 if (pecaSelect) highlightPecaSelectGestao(pecaSelect); 4433 return; 4434 } 4435 4436 const uploadMaxBytes = getAssessorUploadMaxBytes(); 4437 const oversizedBatch = processosData 4438 .map(entry => processosDataGestao.find(p => gestaoProcessoId(p) === entry.id)) 4439 .filter(p => p && (p.fileSize || 0) > uploadMaxBytes); 4440 if (oversizedBatch.length > 0) { 4441 const sample = oversizedBatch[0]; 4442 const limitMb = getAssessorUploadMaxMbLabel(); 4443 const msg = __isEN 4444 ? `${oversizedBatch.length} selected case(s) exceed the ${limitMb} MB limit (e.g. "${gestaoDisplayFilename(sample.filename)}"). Use Focus Pages or a smaller PDF.` 4445 : `${oversizedBatch.length} processo(s) selecionado(s) excedem o limite de ${limitMb} MB (ex.: "${gestaoDisplayFilename(sample.filename)}"). Use Páginas em Foco ou um PDF menor.`; 4446 funcMsg('warning', __L.attn, msg, '', '', undefined, gestaoInsightOptions('batch_generate_file_too_large', { 4447 errorCode: 'validation:file_too_large', 4448 context: { 4449 oversizedCount: oversizedBatch.length, 4450 maxMb: limitMb 4451 } 4452 })); 4453 return; 4454 } 4455 4456 const persona = personaSelect ? personaSelect.value : null; 4457 const llmModel = typeof getSelectedLLMModel === 'function' ? getSelectedLLMModel() : 'chatgpt'; 4458 4459 btnGerarPecasLote.dataset.loading = 'true'; 4460 btnGerarPecasLote.disabled = true; 4461 btnGerarPecasLote.innerHTML = '<i class="fa fa-spinner fa-spin"></i> ' + (__isEN ? 'Queuing...' : 'Enfileirando...'); 4462 4463 try { 4464 const response = await fetch(base_url_gestao + '/processos/gerar-lote', { 4465 method: 'POST', 4466 headers: { 4467 'Content-Type': 'application/json', 4468 'x-access-token': token 4469 }, 4470 body: JSON.stringify({ 4471 processos: processosData, 4472 persona, 4473 llmModel 4474 }) 4475 }); 4476 4477 const data = await response.json(); 4478 4479 if (response.ok) { 4480 const detalhesLote = Array.isArray(data.detalhes) ? data.detalhes : []; 4481 const errosDetalhe = detalhesLote.filter(d => d && d.status === 'erro'); 4482 const errosUnicos = [...new Set(errosDetalhe.map(d =>
4482 d.error).filter(Boolean))]; 4483 const causaErro = errosUnicos.length === 1 4484 ? errosUnicos[0] 4485 : (errosUnicos.length > 1 ? errosUnicos.join(' ') : ''); 4486 4487 if (data.mode === 'queued') { 4488 const enfileirados = data.enfileirados || 0; 4489 const totalErros = data.erros || 0; 4490 const msgEnfileirados = enfileirados === 1 4491 ? (__isEN ? '1 draft queued' : '1 peça enfileirada') 4492 : (__isEN ? `${enfileirados} drafts queued` : `${enfileirados} peças enfileiradas`); 4493 const msgErros = totalErros === 1 4494 ? (__isEN ? '1 case could not be queued' : '1 processo não enfileirado') 4495 : (__isEN ? `${totalErros} cases could not be queued` : `${totalErros} processos não enfileirados`); 4496 const bgMsg = __isEN 4497 ? 'Processing continues in the background. The list refreshes automatically.' 4498 : 'O processamento continua em segundo plano. A lista atualiza automaticamente.'; 4499 4500 if (enfileirados > 0 && totalErros === 0) { 4501 detalhesLote.filter(d => d && d.status === 'enfileirado').forEach(d => gestaoTrackJob(d.id)); 4502 gestaoSuccessNotify( 4503 __isEN ? 'Queued' : 'Enfileirado', 4504 `${msgEnfileirados}. ${bgMsg}`, 4505 '', 4506 JSON.stringify(detalhesLote) 4507 ); 4508 } else if (enfileirados > 0 && totalErros > 0) { 4509 detalhesLote.filter(d => d && d.status === 'enfileirado').forEach(d => gestaoTrackJob(d.id)); 4510 const msgParcial = causaErro 4511 ? `${msgEnfileirados}. ${msgErros}: ${causaErro}. ${bgMsg}` 4512 : `${msgEnfileirados}. ${msgErros}. ${bgMsg}`; 4513 funcMsg('warning', __isEN ? 'Partially queued' : 'Enfileirado com ressalvas', msgParcial, '', JSON.stringify(detalhesLote), undefined, gestaoInsightOptions('batch_queue_partial', { 4514 notifyAdmin: false, 4515 category: 'server', 4516 errorCode: 'server:batch_queue_partial', 4517 context: { successCount: enfileirados, errorCount: totalErros, uniqueErrors: errosUnicos } 4518 })); 4519 } else { 4520 // Quando o único motivo é "já está sendo processado", é estado esperado 4521 // (não erro): rebaixa para informativo e não notifica o admin. 4522 const apenasJaProcessando = errosDetalhe.length > 0 4523 && errosDetalhe.every(d => d.reason === 'already_processing'); 4524 if (apenasJaProcessando) { 4525 const msgJa = __isEN 4526 ? `${msgErros} already being processed. Please wait â the list refreshes automatically.` 4527 : `${msgErros} já em processamento. Aguarde a conclusão â a lista atualiza automaticamente.`; 4528 funcMsg('info', __isEN ? 'Already processing' : 'Já em processamento', msgJa, '', JSON.stringify(detalhesLote), undefined, gestaoInsightOptions('batch_queue_already_processing', { 4529 notifyAdmin: false, 4530 category: 'validation', 4531 errorCode: 'validation:batch_queue_already_processing', 4532 context: { successCount: enfileirados, errorCount: totalErros, uniqueErrors: errosUnicos } 4533 })); 4534 } else { 4535 const msgFalha = causaErro 4536 ? (__isEN ? `No drafts were queued. ${msgErros}: ${causaErro}.` : `Nenhuma peça enfileirada. ${msgErros}: ${causaErro}.`) 4537 : (__isEN ? 'No drafts were queued.' : 'Nenhuma peça enfileirada.'); 4538 funcMsg('error', __isEN ? 'Queue failed' : 'Erro ao enfileirar', msgFalha, '', JSON.stringify(detalhesLote), undefined, gestaoInsightOptions('batch_queue_failed', { 4539 category: 'server', 4540 errorCode: 'server:batch_queue_failed', 4541 context: { successCount: enfileirados, errorCount: totalErros, uniqueErrors: errosUnicos } 4542 })); 4543 } 4544 } 4545 } else { 4546 const totalSucessos = data.sucessos || 0; 4547 const totalErros = data.erros || 0; 4548 const todosRetryable = errosDetalhe.length > 0 && errosDetalhe.every(d => d.retryable); 4549 const msgPecasOk = totalSucessos === 1 4550 ? '1 peça gerada com sucesso' 4551 : `${totalSucessos} peças geradas com sucesso`; 4552 const msgProcessosErro = totalErros === 1 4553 ? '1 processo com erro' 4554 : `${totalErros} processos com erro`; 4555 const msgComCausa = causaErro 4556 ? `Nenhuma peça foi gerada. ${msgProcessosErro}: ${causaErro}` 4557 : `Nenhuma peça foi gerada. ${msgProcessosErro}.`; 4558 if (totalSucessos === 0) { 4559 funcMsg( 4560 todosRetryable ? 'warning' : 'error', 4561 todosRetryable ? (__isEN ? 'Server busy' : 'Servidor ocupado') : 'Erro ao gerar peças', 4562 msgComCausa, 4563 '', 4564 JSON.stringify(detalhesLote), 4565 undefined, 4566 gestaoInsightOptions('batch_generate_failed', { 4567 notifyAdmin: !todosRetryable, 4568 category: todosRetryable ? 'server_retryable' : 'server', 4569 errorCode: todosRetryable ? 'server:batch_generate_retryable' : 'server:batch_generate_failed', 4570 context: { successCount: totalSucessos, errorCount: totalErros, uniqueErrors: errosUnicos, retryable: todosRetryable } 4571 }) 4572 ); 4573 } else if (totalErros > 0) { 4574 const msgParcial = causaErro 4575 ? `${msgPecasOk}. ${msgProcessosErro}: ${causaErro}` 4576 : `${msgPecasOk} e ${msgProcessosErro}.`; 4577 funcMsg('warning', 'ConcluÃdo com erros', msgParcial, '', JSON.stringify(detalhesLote), undefined, gestaoInsightOptions('batch_generate_partial', { 4578 category: 'server', 4579 errorCode: 'server:batch_generate_partial', 4580 context: { successCount: totalSucessos, errorCount: totalErros, uniqueErrors: errosUnicos } 4581 })); 4582 } else { 4583 gestaoSuccessNotify('Sucesso', `${msgPecasOk}.`, '', JSON.stringify(detalhesLote)); 4584 } 4585 } 4586 loadProcessosGestao();
4587 checkboxes.forEach(cb => { 4588 setProcessoCheckboxGestao(cb, false); 4589 }); 4590 if (checkboxSelecionarTodos) { 4591 checkboxSelecionarTodos.checked = false; 4592 checkboxSelecionarTodos.indeterminate = false; 4593 } 4594 } else if (response.status === 403 && data.error === 'PRO_REQUIRED') { 4595 if (typeof openCogniJUSProModal === 'function') openCogniJUSProModal(data.msg || 'Esta funcionalidade requer assinatura PRO.'); 4596 } else if (response.status === 403 && data.error === 'ITERATION_LIMIT') { 4597 if (typeof openCogniJUSProModal === 'function') openCogniJUSProModal(__isEN 4598 ? 'You reached the free weekly limit. Upgrade to PRO for unlimited use.' 4599 : 'Você atingiu o limite semanal do plano gratuito. Assine o PRO para uso ilimitado.'); 4600 } else if (response.status === 400) { 4601 funcMsg('warning', 'Atenção', data.msg || 'Erro ao processar lote.', '', '', undefined, gestaoInsightOptions('batch_generate_bad_request', { 4602 notifyAdmin: false, 4603 category: 'validation', 4604 errorCode: 'validation:batch_generate_bad_request', 4605 context: { httpStatus: response.status, serverMessage: data.msg || data.error } 4606 })); 4607 } else { 4608 funcMsg('error', 'Erro', data.msg || 'Erro ao processar lote.', '', '', undefined, gestaoInsightOptions('batch_generate_http_error', { 4609 category: 'server', 4610 errorCode: `server:batch_generate_http_${response.status}`, 4611 context: { httpStatus: response.status, serverMessage: data.msg || data.error } 4612 })); 4613 } 4614 } catch (error) { 4615 funcMsg('error', 'Erro', 'Falha ao processar lote.', '', error && error.message ? error.message : '', undefined, gestaoInsightOptions('batch_generate_network_error', { 4616 category: 'network', 4617 errorCode: 'network:batch_generate_failed', 4618 context: { errorName: error && error.name ? error.name : undefined } 4619 })); 4620 } 4621 4622 delete btnGerarPecasLote.dataset.loading; 4623 updateGerarPecasButtonText(); 4624 }); 4625 } 4626 4627 updateGerarPecasButtonText(); 4628 4629 if (checkboxSelecionarTodos) { 4630 checkboxSelecionarTodos.addEventListener('change', (e) => { 4631 const checkboxes = gestaoQueryAll('.processo-checkbox'); 4632 let skipped = 0; 4633 4634 if (e.target.checked) { 4635 const personaSelect = gestaoGetById('personaSelectGestao'); 4636 if (!personaSelect || !personaSelect.value) { 4637 e.target.checked = false; 4638 warnPersonaRequiredGestao(); 4639 checkboxSelecionarTodos.indeterminate = false; 4640 syncSelecionarTodosGestao(); 4641 return; 4642 } 4643 4644 checkboxes.forEach(cb => { 4645 const row = cb.closest('tr'); 4646 if (rowHasPecaGestao(row)) { 4647 setProcessoCheckboxGestao(cb, true); 4648 } else { 4649 setProcessoCheckboxGestao(cb, false); 4650 skipped++; 4651 } 4652 }); 4653 4654 if (skipped > 0) { 4655 funcMsg('warning', __L.attn, __isEN 4656 ? (skipped === 1 4657 ? '1 case was not selected because no draft was chosen. Select a draft in that row first.' 4658 : `${skipped} cases were not selected because no draft was chosen. Select a draft in each row first.`) 4659 : (skipped === 1 4660 ? '1 processo não foi selecionado porque não tem peça definida. Escolha a peça na linha.' 4661 : `${skipped} processos não foram selecionados porque não têm peça definida. Escolha a peça em cada linha.`), '', '', undefined, gestaoInsightOptions('select_all_skipped_missing_piece', { 4662 errorCode: 'validation:piece_required', 4663 context: { missingFields: ['piece'], skippedCount: skipped, attemptedSelected: checkboxes.length } 4664 })); 4665 } 4666 4667 if (skipped === checkboxes.length) { 4668 e.target.checked = false;
4669 } 4670 } else { 4671 checkboxes.forEach(cb => setProcessoCheckboxGestao(cb, false)); 4672 } 4673 4674 checkboxSelecionarTodos.indeterminate = false; 4675 syncSelecionarTodosGestao(); 4676 }); 4677 } 4678} 4679 4680// Initialize filters 4681function initializeFiltersGestao() { 4682 const busca = gestaoGetById('buscaProcesso'); 4683 4684 if (busca) { 4685 busca.addEventListener('input', debounceGestao(() => applyFiltersAndRenderGestao(true), 200)); 4686 } 4687 4688 gestaoQueryAll('.gestao-processual-table th.sortable').forEach(th => { 4689 th.addEventListener('click', () => { 4690 const field = th.dataset.sortField; 4691 if (field) handleGestaoColumnSort(field); 4692 }); 4693 }); 4694 4695 updateGestaoSortIndicators(); 4696 4697 // Persona change listener 4698 const personaSelect = gestaoGetById('personaSelectGestao'); 4699 if (personaSelect) { 4700 personaSelect.addEventListener('change', function () { 4701 populateRowPecaSelectsGestao(this.value); 4702 }); 4703 } 4704 4705 // Block peça selection without Persona; validate again on change as safety net 4706 document.addEventListener('mousedown', function (e) { 4707 if (!e.target.classList.contains('row-peca-select')) return; 4708 if (personaSelectedGestao()) return; 4709 e.preventDefault(); 4710 e.stopPropagation(); 4711 e.target.blur(); 4712 warnPersonaRequiredGestao(); 4713 }, true); 4714 4715 document.addEventListener('focusin', function (e) { 4716 if (gestaoBlockPecaFocus) return; 4717 if (!e.target.classList || !e.target.classList.contains('row-peca-select')) return; 4718 if (personaSelectedGestao()) return; 4719 e.target.blur(); 4720 focusPersonaSelectGestao(); 4721 }, true); 4722 4723 document.addEventListener('change', function (e) { 4724 if (e.target.classList.contains('row-additional-prompt') || 4725 e.target.classList.contains('row-include-pages') || 4726 e.target.classList.contains('row-anonymization')) { 4727 syncGestaoRowParamsFromDom(e.target.closest('tr')); 4728 return; 4729 } 4730 4731 if (e.target.classList.contains('row-peca-select')) { 4732 if (!personaSelectedGestao()) { 4733 revertPecaSelectGestao(e.target); 4734 warnPersonaRequiredGestao(); 4735 return; 4736 } 4737 4738 const row = e.target.closest('tr'); 4739 const checkbox = row ? row.querySelector('.processo-checkbox') : null; 4740 if (checkbox) { 4741 setProcessoCheckboxGestao(checkbox, !!e.target.value); 4742 } else if (row) { 4743 syncGestaoRowSelectionFromDom(row); 4744 } 4745 syncSelecionarTodosGestao(); 4746 return; 4747 } 4748 4749 if (e.target.classList.contains('processo-checkbox')) { 4750 const row = e.target.closest('tr'); 4751 const pecaSelect = row ? row.querySelector('.row-peca-select') : null; 4752 4753 if (e.target.checked && !rowHasPecaGestao(row)) { 4754 setProcessoCheckboxGestao(e.target, false); 4755 4756 const personaSelect = gestaoGetById('personaSelectGestao'); 4757 if (!personaSelect || !personaSelect.value) { 4758 warnPersonaRequiredGestao(); 4759 } else { 4760 funcMsg('warning', __L.attn, __isEN 4761 ? 'Select a draft for this case before marking it.' 4762 : 'Selecione a peça deste processo antes de marcá-lo.', '', '', undefined, gestaoInsightOptions('mark_process_without_piece', { 4763 errorCode: 'validation:piece_required', 4764 context: { missingFields: ['piece'], processoId: e.target.value || undefined } 4765 })); 4766 highlightPecaSelectGestao(pecaSelect); 4767 } 4768 } else { 4769 setProcessoCheckboxGestao(e.target, e.target.checked); 4770 } 4771 4772 syncSelecionarTodosGestao(); 4773 } 4774 }); 4775 4776 document.addEventListener('input', function (e) { 4777 if (e.target.classList.contains('row-additional-prompt') || 4778 e.target.classList.contains('row-include-pages')) { 4779 syncGestaoRowParamsFromDom(e.target.closest('tr')); 4780 } 4781 }); 4782} 4783 4784function debounceGestao(func, wait) { 4785 let timeout;
4786 return function executedFunction(...args) { 4787 const later = () => { 4788 clearTimeout(timeout); 4789 func.apply(this, args); 4790 }; 4791 clearTimeout(timeout); 4792 timeout = setTimeout(later, wait); 4793 }; 4794} 4795 4796function bootGestaoProcessosIfAuthed() { 4797 if (!localStorage.getItem('CogniJUS_User_Token')) return; 4798 ensureGestaoToastStack(); 4799 rehydrateGestaoWatchedJobsFromSession(); 4800 scheduleGestaoProcessosTableOverflowUpdate(); 4801 loadProcessosGestao(); 4802} 4803 4804// Initialize all Gestão Processual features on DOMContentLoaded 4805document.addEventListener('DOMContentLoaded', function () { 4806 initializeAssessorTabs(); 4807 initializeUploadEventsGestao(); 4808 initializeBatchGenerationGestao(); 4809 initializeFiltersGestao(); 4810 loadGestaoAssessorConfig(); 4811 4812 bootGestaoProcessosIfAuthed(); 4813 4814 document.addEventListener('cognijus-auth-ready', bootGestaoProcessosIfAuthed); 4815 document.addEventListener('cj-erp-assessor-mounted', bootGestaoProcessosIfAuthed); 4816 4817 // cognijus-auth.js chama executePendingQuery após o login no modal 4818 // (sem recarregar a página). Sem este hook a lista só populava no F5. 4819 const previousPendingQuery = typeof window.executePendingQuery === 'function' 4820 ? window.executePendingQuery 4821 : null; 4822 window.executePendingQuery = function () { 4823 if (previousPendingQuery) { 4824 try { previousPendingQuery(); } catch (e) { /* noop */ } 4825 } 4826 bootGestaoProcessosIfAuthed(); 4827 }; 4828 4829 document.addEventListener('visibilitychange', function () { 4830 if (document.visibilityState === 'visible' && localStorage.getItem('CogniJUS_User_Token')) { 4831 loadProcessosGestao(); 4832 } 4833 }); 4834 4835 if (window.AssessorVerify) { 4836 AssessorVerify.init({ 4837 baseUrl: window.location.origin, 4838 getToken: function () { return localStorage.getItem('CogniJUS_User_Token'); }, 4839 isEN: __isEN 4840 }); 4841 } 4842});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.