dev-dev / app.js
stadv's picture
🐳 22/06 - 09:28 - integre com a aasp {  "swagger": "2.0",  "info": {    "version": "v1",    "title": "API - Intimações AASP",    "description": "Api para acesso as intimações dos associados da AASP",
67fa90b verified
Raw
History Blame Contribute Delete
47.9 kB
// ==========================================
// LegalData Manager - Main Application
// ==========================================
// --- Data Store ---
let appData = {
processos: [
{ id: '2023-0456', cliente: 'João Silva', tribunal: 'TJSP', data: '2023-03-15', status: 'Ativo', prazo: '2024-01-15', descricao: 'Ação indenizatória' },
{ id: '2023-0389', cliente: 'Maria Souza', tribunal: 'TRF4', data: '2023-02-20', status: 'Pendente', prazo: '2024-01-18', descricao: 'Mandado de segurança' },
{ id: '2023-0412', cliente: 'Empresa XYZ Ltda', tribunal: 'TJMG', data: '2023-04-10', status: 'Urgente', prazo: '2024-01-20', descricao: 'Execução fiscal' },
{ id: '2023-0501', cliente: 'Ana Oliveira', tribunal: 'TJSP', data: '2023-05-05', status: 'Ativo', prazo: '2024-02-10', descricao: 'Ação de cobrança' },
{ id: '2023-0534', cliente: 'Carlos Mendes', tribunal: 'TRF1', data: '2023-06-12', status: 'Concluído', prazo: null, descricao: 'Habeas corpus' },
{ id: '2023-0601', cliente: 'Tech Solutions SA', tribunal: 'TJSP', data: '2023-07-01', status: 'Ativo', prazo: '2024-03-01', descricao: 'Contrato comercial' },
{ id: '2023-0645', cliente: 'Fernanda Lima', tribunal: 'TJMG', data: '2023-08-15', status: 'Pendente', prazo: '2024-03-20', descricao: 'Inventário' },
{ id: '2023-0702', cliente: 'Roberto Santos', tribunal: 'TRF4', data: '2023-09-03', status: 'Ativo', prazo: '2024-04-05', descricao: 'Recurso de apelação' },
],
clientes: [
{ id: 1, nome: 'João Silva', tipo: 'pf', cpfCnpj: '123.456.789-00', email: 'joao@email.com', telefone: '(11) 98765-4321', endereco: 'Rua A, 100, São Paulo-SP', processos: 2 },
{ id: 2, nome: 'Maria Souza', tipo: 'pf', cpfCnpj: '987.654.321-00', email: 'maria@email.com', telefone: '(21) 99876-5432', endereco: 'Av B, 200, Rio de Janeiro-RJ', processos: 1 },
{ id: 3, nome: 'Empresa XYZ Ltda', tipo: 'pj', cpfCnpj: '12.345.678/0001-90', email: 'contato@xyz.com', telefone: '(11) 3456-7890', endereco: 'Rua C, 300, São Paulo-SP', processos: 3 },
{ id: 4, nome: 'Ana Oliveira', tipo: 'pf', cpfCnpj: '456.789.123-00', email: 'ana@email.com', telefone: '(31) 97654-3210', endereco: 'Rua D, 400, Belo Horizonte-MG', processos: 1 },
{ id: 5, nome: 'Carlos Mendes', tipo: 'pf', cpfCnpj: '789.123.456-00', email: 'carlos@email.com', telefone: '(61) 96543-2109', endereco: 'SQN 5, Bloco E, Brasília-DF', processos: 1 },
{ id: 6, nome: 'Tech Solutions SA', tipo: 'pj', cpfCnpj: '98.765.432/0001-10', email: 'juridico@techsol.com', telefone: '(11) 4567-8901', endereco: 'Av F, 600, São Paulo-SP', processos: 2 },
{ id: 7, nome: 'Fernanda Lima', tipo: 'pf', cpfCnpj: '321.654.987-00', email: 'fernanda@email.com', telefone: '(31) 95432-1098', endereco: 'Rua G, 700, Belo Horizonte-MG', processos: 1 },
{ id: 8, nome: 'Roberto Santos', tipo: 'pf', cpfCnpj: '654.987.321-00', email: 'roberto@email.com', telefone: '(51) 94321-0987', endereco: 'Rua H, 800, Porto Alegre-RS', processos: 1 },
],
evidencias: [],
intimacoes: [],
aaspConfig: { chave: '92E81C018B69454F8053133023AC438B' },
googleAuth: null,
driveConnected: false,
sheetsConnected: false,
importPreview: null,
};
// Load from localStorage
function loadAppData() {
const saved = localStorage.getItem('legaldata_app');
if (saved) {
try {
const parsed = JSON.parse(saved);
appData = { ...appData, ...parsed };
} catch(e) { console.error('Error loading data', e); }
}
}
function saveAppData() {
localStorage.setItem('legaldata_app', JSON.stringify(appData));
}
// --- Tab Navigation ---
function showTab(tabId, navEl) {
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.nav-link').forEach(n => n.classList.remove('active-nav'));
const tab = document.getElementById(tabId);
if (tab) tab.classList.add('active');
if (navEl) {
navEl.classList.add('active-nav');
} else {
document.querySelectorAll('.nav-link').forEach(n => {
if (n.getAttribute('onclick') && n.getAttribute('onclick').includes(tabId)) {
n.classList.add('active-nav');
}
});
}
const titles = {
dashboard: 'Dashboard', processos: 'Gestão de Processos', clientes: 'Gestão de Clientes',
provas: 'Galeria de Provas', intimacoes: 'Intimações AASP', import: 'Importar Dados',
config: 'Configurações', sanitize: 'Sanitização de Dados', integrations: 'Integrações'
};
document.getElementById('current-tab-title').textContent = titles[tabId] || tabId;
if (tabId === 'provas') renderEvidence();
if (tabId === 'processos') renderProcessos();
if (tabId === 'clientes') renderClientes();
}
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('collapsed');
}
// --- Modal Management ---
function showModal(id) {
const modal = document.getElementById('modal-' + id);
if (modal) modal.classList.remove('hidden');
}
function hideModal(id) {
const modal = document.getElementById('modal-' + id);
if (modal) modal.classList.add('hidden');
}
// --- Toast Notifications ---
function showToast(message, type = 'success') {
const container = document.getElementById('toast-container');
const colors = { success: 'bg-green-500', error: 'bg-red-500', info: 'bg-blue-500', warning: 'bg-yellow-500' };
const icons = { success: 'fa-check-circle', error: 'fa-times-circle', info: 'fa-info-circle', warning: 'fa-exclamation-triangle' };
const toast = document.createElement('div');
toast.className = `toast ${colors[type]} text-white px-4 py-3 rounded-lg shadow-lg flex items-center space-x-3 min-w-72`;
toast.innerHTML = `<i class="fas ${icons[type]}"></i><span>${message}</span>`;
container.appendChild(toast);
requestAnimationFrame(() => toast.classList.add('show'));
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 400);
}, 4000);
}
// --- Processos ---
function renderProcessos() {
const tbody = document.getElementById('processos-table-body');
if (!tbody) return;
const statusColors = {
'Ativo': 'bg-green-100 text-green-800',
'Pendente': 'bg-yellow-100 text-yellow-800',
'Urgente': 'bg-red-100 text-red-800',
'Concluído': 'bg-blue-100 text-blue-800',
'Arquivado': 'bg-gray-100 text-gray-800'
};
tbody.innerHTML = appData.processos.map(p => `
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">#${p.id}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${p.cliente}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${p.tribunal}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${formatDate(p.data)}</td>
<td class="px-6 py-4 whitespace-nowrap"><span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${statusColors[p.status] || 'bg-gray-100 text-gray-800'}">${p.status}</span></td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button class="text-indigo-600 hover:text-indigo-900 mr-3" title="Ver"><i class="fas fa-eye"></i></button>
<button class="text-green-600 hover:text-green-900 mr-3" title="Editar"><i class="fas fa-edit"></i></button>
<button class="text-red-600 hover:text-red-900" onclick="deleteProcess('${p.id}')" title="Excluir"><i class="fas fa-trash"></i></button>
</td>
</tr>
`).join('');
}
function addProcess() {
const proc = {
id: document.getElementById('proc-numero').value || generateId(),
cliente: document.getElementById('proc-cliente').value,
tribunal: document.getElementById('proc-tribunal').value,
data: document.getElementById('proc-data').value,
status: document.getElementById('proc-status').value,
prazo: null,
descricao: document.getElementById('proc-descricao').value
};
appData.processos.push(proc);
saveAppData();
renderProcessos();
hideModal('new-process');
showToast('Processo adicionado com sucesso!');
updateStats();
}
function deleteProcess(id) {
if (confirm('Deseja excluir este processo?')) {
appData.processos = appData.processos.filter(p => p.id !== id);
saveAppData();
renderProcessos();
showToast('Processo excluído', 'info');
updateStats();
}
}
// --- Clientes ---
function renderClientes() {
const tbody = document.getElementById('clientes-table-body');
if (!tbody) return;
tbody.innerHTML = appData.clientes.map(c => `
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${c.nome}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${c.cpfCnpj}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${c.email}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${c.telefone}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${c.processos}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button class="text-indigo-600 hover:text-indigo-900 mr-3" title="Ver"><i class="fas fa-eye"></i></button>
<button class="text-green-600 hover:text-green-900 mr-3" title="Editar"><i class="fas fa-edit"></i></button>
<button class="text-red-600 hover:text-red-900" onclick="deleteClient(${c.id})" title="Excluir"><i class="fas fa-trash"></i></button>
</td>
</tr>
`).join('');
}
function addClient() {
const cli = {
id: Date.now(),
nome: document.getElementById('cli-nome').value,
tipo: document.getElementById('cli-tipo').value,
cpfCnpj: document.getElementById('cli-cpfcnpj').value,
email: document.getElementById('cli-email').value,
telefone: document.getElementById('cli-telefone').value,
endereco: document.getElementById('cli-endereco').value,
processos: 0
};
appData.clientes.push(cli);
saveAppData();
renderClientes();
hideModal('new-client');
showToast('Cliente adicionado com sucesso!');
updateStats();
populateSelects();
}
function deleteClient(id) {
if (confirm('Deseja excluir este cliente?')) {
appData.clientes = appData.clientes.filter(c => c.id !== id);
saveAppData();
renderClientes();
showToast('Cliente excluído', 'info');
updateStats();
}
}
// --- Evidence Gallery ---
let selectedEvidenceFiles = [];
let currentEvidenceIndex = 0;
function renderEvidence() {
const grid = document.getElementById('evidence-grid');
const noEv = document.getElementById('no-evidence');
if (!grid) return;
const filterClient = document.getElementById('evidence-filter-client')?.value || '';
const filterType = document.getElementById('evidence-filter-type')?.value || '';
let filtered = appData.evidencias;
if (filterClient) filtered = filtered.filter(e => e.clienteId == filterClient);
if (filterType) filtered = filtered.filter(e => e.categoria.toLowerCase().includes(filterType));
if (filtered.length === 0) {
grid.innerHTML = '';
noEv?.classList.remove('hidden');
return;
}
noEv?.classList.add('hidden');
grid.innerHTML = filtered.map((ev, i) => {
const icon = getFileIcon(ev.tipo);
const thumb = ev.thumb || `http://static.photos/${ev.categoria === 'foto' ? 'people' : 'office'}/320x240/${ev.id}`;
return `
<div class="evidence-card bg-white rounded-xl shadow overflow-hidden cursor-pointer transition" onclick="viewEvidence(${ev.id})">
<div class="h-40 bg-gray-100 flex items-center justify-center overflow-hidden">
${ev.tipo && ev.tipo.startsWith('image/') ?
`<img src="${thumb}" class="w-full h-full object-cover" alt="${ev.descricao}">` :
`<i class="${icon} text-5xl text-gray-300"></i>`}
</div>
<div class="p-3">
<p class="font-medium text-sm truncate">${ev.descricao || 'Sem descrição'}</p>
<p class="text-xs text-gray-500 mt-1">${ev.clienteNome || 'N/A'}${formatDate(ev.data)}</p>
<div class="flex items-center justify-between mt-2">
<span class="text-xs px-2 py-1 rounded-full bg-indigo-100 text-indigo-700">${ev.categoria}</span>
${ev.driveId ? '<i class="fab fa-google-drive text-blue-500 text-xs" title="No Google Drive"></i>' : ''}
</div>
</div>
</div>`;
}).join('');
document.getElementById('stat-provas').textContent = appData.evidencias.length;
}
function getFileIcon(tipo) {
if (!tipo) return 'fas fa-file';
if (tipo.startsWith('image/')) return 'fas fa-file-image';
if (tipo === 'application/pdf') return 'fas fa-file-pdf';
if (tipo.startsWith('video/')) return 'fas fa-file-video';
if (tipo.startsWith('audio/')) return 'fas fa-file-audio';
return 'fas fa-file';
}
function handleEvidenceDrop(event) {
const files = event.dataTransfer.files;
processEvidenceFiles(files);
}
function handleEvidenceSelect(event) {
const files = event.target.files;
processEvidenceFiles(files);
}
function processEvidenceFiles(files) {
selectedEvidenceFiles = Array.from(files);
const preview = document.getElementById('ev-preview');
preview.innerHTML = '';
preview.classList.remove('hidden');
selectedEvidenceFiles.forEach((file, i) => {
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => {
preview.innerHTML += `<div class="relative"><img src="${e.target.result}" class="w-full h-20 object-cover rounded"><span class="absolute top-1 right-1 bg-black bg-opacity-50 text-white text-xs px-1 rounded">${file.name.length > 15 ? file.name.substring(0,12)+'...' : file.name}</span></div>`;
};
reader.readAsDataURL(file);
} else {
preview.innerHTML += `<div class="bg-gray-100 rounded p-2 text-center"><i class="${getFileIcon(file.type)} text-2xl text-gray-400"></i><p class="text-xs text-gray-500 mt-1 truncate">${file.name}</p></div>`;
}
});
}
function uploadEvidence() {
const clienteId = document.getElementById('ev-cliente').value;
const cliente = appData.clientes.find(c => c.id == clienteId);
const processo = document.getElementById('ev-processo').value;
const categoria = document.getElementById('ev-categoria').value;
const descricao = document.getElementById('ev-descricao').value;
const uploadToDrive = document.getElementById('ev-upload-drive').checked;
if (!clienteId) { showToast('Selecione um cliente', 'error'); return; }
if (selectedEvidenceFiles.length === 0) { showToast('Selecione ao menos um arquivo', 'error'); return; }
selectedEvidenceFiles.forEach((file, i) => {
const evId = Date.now() + i;
const evidencia = {
id: evId,
clienteId: parseInt(clienteId),
clienteNome: cliente?.nome || 'N/A',
processoId: processo,
categoria: categoria,
descricao: descricao || file.name,
tipo: file.type,
nomeArquivo: file.name,
data: new Date().toISOString(),
thumb: null,
driveId: null,
localData: null
};
// Create local thumbnail for images
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => {
evidencia.thumb = e.target.result;
evidencia.localData = e.target.result;
appData.evidencias.push(evidencia);
saveAppData();
renderEvidence();
};
reader.readAsDataURL(file);
} else {
// Store file reference
const reader = new FileReader();
reader.onload = (e) => {
evidencia.localData = e.target.result;
appData.evidencias.push(evidencia);
saveAppData();
renderEvidence();
};
reader.readAsDataURL(file);
}
// Upload to Google Drive if connected and checked
if (uploadToDrive && appData.driveConnected) {
uploadToGoogleDrive(file, evId);
}
});
selectedEvidenceFiles = [];
document.getElementById('ev-preview').innerHTML = '';
document.getElementById('ev-preview').classList.add('hidden');
hideModal('upload-evidence');
showToast('Prova(s) enviada(s) com sucesso!');
updateStats();
}
function viewEvidence(id) {
const ev = appData.evidencias.find(e => e.id === id);
if (!ev) return;
currentEvidenceIndex = appData.evidencias.findIndex(e => e.id === id);
const content = document.getElementById('lightbox-content');
const caption = document.getElementById('lightbox-caption');
if (ev.tipo && ev.tipo.startsWith('image/') && ev.localData) {
content.innerHTML = `<img src="${ev.localData}" class="max-w-full max-h-[70vh] object-contain rounded">`;
} else if (ev.tipo === 'application/pdf' && ev.localData) {
content.innerHTML = `<iframe src="${ev.localData}" class="w-full h-[70vh] rounded" frameborder="0"></iframe>`;
} else if (ev.tipo && ev.tipo.startsWith('video/') && ev.localData) {
content.innerHTML = `<video src="${ev.localData}" controls class="max-w-full max-h-[70vh] rounded"></video>`;
} else if (ev.tipo && ev.tipo.startsWith('audio/') && ev.localData) {
content.innerHTML = `<div class="text-center"><i class="fas fa-music text-6xl text-white mb-6"></i><audio src="${ev.localData}" controls class="w-96"></audio></div>`;
} else {
content.innerHTML = `<div class="text-center"><i class="${getFileIcon(ev.tipo)} text-8xl text-white mb-4"></i><p class="text-white text-xl">${ev.nomeArquivo || 'Arquivo'}</p><p class="text-gray-400">Pré-visualização não disponível</p></div>`;
}
caption.textContent = `${ev.descricao}${ev.clienteNome}${formatDate(ev.data)}`;
document.getElementById('lightbox').classList.add('active');
}
function closeLightbox() {
document.getElementById('lightbox').classList.remove('active');
}
function navigateEvidence(direction) {
currentEvidenceIndex += direction;
if (currentEvidenceIndex < 0) currentEvidenceIndex = appData.evidencias.length - 1;
if (currentEvidenceIndex >= appData.evidencias.length) currentEvidenceIndex = 0;
const ev = appData.evidencias[currentEvidenceIndex];
if (ev) viewEvidence(ev.id);
}
function filterEvidence() {
renderEvidence();
}
// --- AASP Integration ---
const AASP_BASE_URL = 'https://api.aasp.org.br';
async function fetchAASPIntimacoes() {
const chave = document.getElementById('aasp-chave').value;
const tipo = document.getElementById('aasp-tipo').value;
const data = document.getElementById('aasp-data').value;
const diferencial = document.getElementById('aasp-diferencial').checked;
const codigo = document.getElementById('aasp-codigo').value;
const btn = document.getElementById('btn-fetch-aasp');
if (!chave) { showToast('Informe a chave de acesso AASP', 'error'); return; }
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Buscando...';
try {
const endpoint = tipo === 'empresa' ? 'Empresa' : 'Associado';
let url = `${AASP_BASE_URL}/api/${endpoint}/intimacao/json?chave=${encodeURIComponent(chave)}`;
if (data) url += `&data=${encodeURIComponent(data)}`;
if (diferencial) url += `&diferencial=true`;
if (tipo === 'empresa' && codigo) url += `&codigoPessoaAssociado=${codigo}`;
const response = await fetch(url);
if (!response.ok) throw new Error(`Erro HTTP: ${response.status}`);
const result = await response.json();
appData.intimacoes = Array.isArray(result) ? result : [result];
renderIntimacoes();
document.getElementById('stat-intimacoes').textContent = appData.intimacoes.length;
updateNotificationBadge();
showToast(`${appData.intimacoes.length} intimação(ões) encontrada(s)!`);
saveAppData();
} catch (error) {
console.error('AASP API Error:', error);
// Show mock data for demo
showAASPMockData();
showToast('Usando dados de demonstração (API pode requerer CORS proxy)', 'warning');
} finally {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-sync-alt mr-2"></i> Buscar Intimações';
}
}
function showAASPMockData() {
appData.intimacoes = [
{ id: 1, dataDisponibilizacao: '2024-01-10', dataPublicacao: '2024-01-09', jornal: 'DJE - TJSP', orgao: 'TJSP', vara: '5ª Vara Cível', numeroProcesso: '2023-0456', nomeParte: 'João Silva', tipoIntimacao: 'Sentença', conteudo: 'Intimação da parte ré para ciência da sentença proferida nos autos...', advogado: 'Dr. Ricardo Mendes', OAB: 'OAB/SP 123.456' },
{ id: 2, dataDisponibilizacao: '2024-01-10', dataPublicacao: '2024-01-09', jornal: 'DJE - TRF3', orgao: 'TRF3', vara: '2ª Vara Federal', numeroProcesso: '2023-0389', nomeParte: 'Maria Souza', tipoIntimacao: 'Despacho', conteudo: 'Intimação para manifestação sobre despacho que deferiu liminarmente a tutela de urgência...', advogado: 'Dra. Ana Paula Torres', OAB: 'OAB/SP 234.567' },
{ id: 3, dataDisponibilizacao: '2024-01-11', dataPublicacao: '2024-01-10', jornal: 'DJE - TJMG', orgao: 'TJMG', vara: '1ª Vara de Fazenda Pública', numeroProcesso: '2023-0412', nomeParte: 'Empresa XYZ Ltda', tipoIntimacao: 'Decisão', conteudo: 'Intimação da requerida para, querendo, apresentar impugnação ao cumprimento de sentença...', advogado: 'Dr. Paulo Santos', OAB: 'OAB/MG 56.789' },
{ id: 4, dataDisponibilizacao: '2024-01-12', dataPublicacao: '2024-01-11', jornal: 'DJE - TJSP', orgao: 'TJSP', vara: '15ª Vara Cível', numeroProcesso: '2023-0501', nomeParte: 'Ana Oliveira', tipoIntimacao: 'Audiência', conteudo: 'Intimação para audiência de conciliação designada para o dia 25/01/2024 às 14:00...', advogado: 'Dra. Carolina Lima', OAB: 'OAB/SP 345.678' },
];
renderIntimacoes();
document.getElementById('stat-intimacoes').textContent = appData.intimacoes.length;
updateNotificationBadge();
saveAppData();
}
function renderIntimacoes() {
const list = document.getElementById('intimacoes-list');
const count = document.getElementById('intimacoes-count');
count.textContent = `${appData.intimacoes.length} resultado(s)`;
if (appData.intimacoes.length === 0) {
list.innerHTML = '<div class="text-center py-8 text-gray-400"><i class="fas fa-gavel text-4xl mb-3"></i><p>Nenhuma intimação encontrada</p></div>';
return;
}
list.innerHTML = appData.intimacoes.map((int, i) => {
const statusColor = int.tipoIntimacao === 'Sentença' ? 'bg-red-100 text-red-800' :
int.tipoIntimacao === 'Audiência' ? 'bg-yellow-100 text-yellow-800' :
'bg-blue-100 text-blue-800';
return `
<div class="border border-gray-200 rounded-lg p-4 mb-3 hover:bg-gray-50 transition">
<div class="flex flex-wrap items-start justify-between gap-3 mb-2">
<div class="flex items-center space-x-3">
<span class="w-8 h-8 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center text-sm font-bold">${i + 1}</span>
<div>
<p class="font-semibold">${int.numeroProcesso || 'N/A'} - ${int.nomeParte || 'N/A'}</p>
<p class="text-sm text-gray-500">${int.orgao || ''}${int.vara || ''}</p>
</div>
</div>
<div class="flex items-center space-x-2">
<span class="px-2 py-1 text-xs font-semibold rounded-full ${statusColor}">${int.tipoIntimacao || 'N/A'}</span>
<span class="text-sm text-gray-500">${formatDate(int.dataDisponibilizacao)}</span>
</div>
</div>
<div class="bg-gray-50 rounded p-3 mb-2">
<p class="text-sm text-gray-700">${(int.conteudo || '').substring(0, 200)}${(int.conteudo || '').length > 200 ? '...' : ''}</p>
</div>
<div class="flex items-center justify-between">
<div class="text-xs text-gray-400">
<span class="mr-3"><i class="fas fa-newspaper mr-1"></i>${int.jornal || 'N/A'}</span>
<span class="mr-3"><i class="fas fa-calendar mr-1"></i>Publicação: ${formatDate(int.dataPublicacao)}</span>
${int.advogado ? `<span><i class="fas fa-user-tie mr-1"></i>${int.advogado}</span>` : ''}
</div>
<div class="flex space-x-2">
<button class="text-sm text-indigo-600 hover:text-indigo-800" onclick="linkIntimacaoToProcess(${int.id})"><i class="fas fa-link mr-1"></i>Vincular</button>
<button class="text-sm text-green-600 hover:text-green-800"><i class="fas fa-check mr-1"></i>Marcar Lida</button>
</div>
</div>
</div>`;
}).join('');
}
async function fetchAASPJornais() {
const chave = document.getElementById('aasp-chave').value;
const tipo = document.getElementById('aasp-tipo').value;
if (!chave) { showToast('Informe a chave AASP', 'error'); return; }
try {
const endpoint = tipo === 'empresa' ? 'Empresa' : 'Associado';
const url = `${AASP_BASE_URL}/api/${endpoint}/intimacao/GetJornaisComIntimacoes/json?chave=${encodeURIComponent(chave)}&qtdeDias=30`;
const response = await fetch(url);
const result = await response.json();
renderJornais(Array.isArray(result) ? result : [result]);
} catch(e) {
// Mock data
renderJornais([
{ jornal: 'DJE - TJSP', quantidade: 12, dataUltima: '2024-01-12' },
{ jornal: 'DJE - TRF3', quantidade: 5, dataUltima: '2024-01-11' },
{ jornal: 'DJE - TJMG', quantidade: 3, dataUltima: '2024-01-10' },
]);
showToast('Usando dados de demonstração para jornais', 'warning');
}
}
function renderJornais(jornais) {
const container = document.getElementById('jornais-results');
const list = document.getElementById('jornais-list');
container.classList.remove('hidden');
list.innerHTML = jornais.map(j => `
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg mb-2">
<div class="flex items-center space-x-3">
<i class="fas fa-newspaper text-indigo-600"></i>
<div>
<p class="font-medium">${j.jornal || j.NomeJornal || 'N/A'}</p>
<p class="text-sm text-gray-500">Última publicação: ${formatDate(j.dataUltima || j.DataUltima)}</p>
</div>
</div>
<span class="bg-indigo-100 text-indigo-700 px-3 py-1 rounded-full text-sm font-medium">${j.quantidade || j.Quantidade || 0} intimação(ões)</span>
</div>
`).join('');
}
function linkIntimacaoToProcess(intId) {
const int = appData.intimacoes.find(i => i.id === intId);
if (!int) return;
showToast(`Intimação ${int.numeroProcesso} vinculada ao processo!`, 'success');
}
function togglePassword(fieldId) {
const field = document.getElementById(fieldId);
if (field.type === 'password') {
field.type = 'text';
field.nextElementSibling.innerHTML = '<i class="fas fa-eye-slash"></i>';
} else {
field.type = 'password';
field.nextElementSibling.innerHTML = '<i class="fas fa-eye"></i>';
}
}
// --- Google Drive / Sheets Integration ---
let gapiLoaded = false;
let gisLoaded = false;
let tokenClient = null;
// Google API Configuration
// NOTE: In production, replace with your own Client ID from Google Cloud Console
const GOOGLE_CLIENT_ID = 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com';
const GOOGLE_API_KEY = 'YOUR_GOOGLE_API_KEY';
const SCOPES = 'https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets.readonly';
const SPREADSHEET_ID = '19EOpljeLASYLgyWhxJWtGBexELlt3GDoYBbcE5ZRExk';
function loadGoogleAPI() {
const script = document.createElement('script');
script.src = 'https://apis.google.com/js/api.js';
script.onload = () => {
gapi.load('client', async () => {
try {
await gapi.client.init({ apiKey: GOOGLE_API_KEY, discoveryDocs: ['https://www.googleapis.com/auth/drive.file', 'https://sheets.googleapis.com/$discovery/rest?version=v4'] });
gapiLoaded = true;
console.log('GAPI loaded');
} catch(e) { console.error('GAPI init error', e); }
});
};
document.body.appendChild(script);
}
function connectGoogleDrive() {
if (!GOOGLE_CLIENT_ID || GOOGLE_CLIENT_ID.includes('YOUR_GOOGLE')) {
showToast('Configure o Client ID do Google no app.js', 'warning');
showGoogleConfigInfo();
return;
}
try {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: GOOGLE_CLIENT_ID,
scope: SCOPES,
callback: (tokenResponse) => {
if (tokenResponse.error) {
showToast('Erro na autenticação Google: ' + tokenResponse.error, 'error');
return;
}
appData.driveConnected = true;
updateDriveStatus(true);
showToast('Google Drive conectado com sucesso!');
saveAppData();
}
});
tokenClient.requestAccessToken({ prompt: '' });
} catch(e) {
console.error('Google Auth Error:', e);
showToast('Erro ao conectar com Google. Verifique a configuração.', 'error');
showGoogleConfigInfo();
}
}
function connectGoogleSheets() {
if (!appData.driveConnected && (!GOOGLE_CLIENT_ID || GOOGLE_CLIENT_ID.includes('YOUR_GOOGLE'))) {
showToast('Configure o Google Client ID primeiro', 'warning');
showGoogleConfigInfo();
return;
}
if (!appData.driveConnected) {
connectGoogleDrive();
return;
}
fetchSpreadsheetData();
}
async function fetchSpreadsheetData() {
try {
const response = await gapi.client.sheets.spreadsheets.values.get({
spreadsheetId: SPREADSHEET_ID,
range: 'A:Z'
});
const rows = response.result.values;
if (rows && rows.length > 0) {
appData.importPreview = rows;
renderImportPreview(rows);
updateSheetsStatus(true);
showToast(`${rows.length - 1} registros importados da planilha!`);
}
} catch(e) {
console.error('Sheets API Error:', e);
// Show mock data
const mockRows = [
['Nome', 'CPF/CNPJ', 'Email', 'Telefone', 'Endereço', 'Tipo Processo', 'Descrição'],
['João da Silva', '123.456.789-00', 'joao@email.com', '(11) 98765-4321', 'Rua A, 100 - SP', 'Cível', 'Ação indenizatória por danos morais'],
['Maria Oliveira', '987.654.321-00', 'maria@email.com', '(21) 99876-5432', 'Av B, 200 - RJ', 'Trabalhista', 'Reclamação trabalhista - horas extras'],
['Empresa ABC Ltda', '12.345.678/0001-90', 'contato@abc.com', '(11) 3456-7890', 'Rua C, 300 - SP', 'Tributário', 'Mandado de segurança - ICMS'],
];
appData.importPreview = mockRows;
renderImportPreview(mockRows);
updateSheetsStatus(true);
showToast('Dados de demonstração carregados', 'warning');
}
}
function renderImportPreview(rows) {
const container = document.getElementById('import-preview');
const table = document.getElementById('import-preview-table');
container.classList.remove('hidden');
if (!rows || rows.length === 0) return;
const headers = rows[0];
let html = '<thead class="bg-gray-50"><tr>';
headers.forEach(h => html += `<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">${h}</th>`);
html += '</tr></thead><tbody class="bg-white divide-y divide-gray-200">';
rows.slice(1).forEach(row => {
html += '<tr>';
row.forEach(cell => html += `<td class="px-4 py-2 text-sm text-gray-500 whitespace-nowrap">${cell || ''}</td>`);
html += '</tr>';
});
html += '</tbody>';
table.innerHTML = html;
}
function confirmImport() {
if (!appData.importPreview) return;
const rows = appData.importPreview;
const headers = rows[0];
// Find column indices
const nomeIdx = headers.findIndex(h => /nome/i.test(h));
const cpfIdx = headers.findIndex(h => /cpf|cnpj/i.test(h));
const emailIdx = headers.findIndex(h => /email|e-mail/i.test(h));
const telIdx = headers.findIndex(h => /telef/i.test(h));
const endIdx = headers.findIndex(h => /endere/i.test(h));
rows.slice(1).forEach(row => {
const exists = appData.clientes.find(c => c.cpfCnpj === (row[cpfIdx] || ''));
if (!exists && row[nomeIdx]) {
appData.clientes.push({
id: Date.now() + Math.random(),
nome: row[nomeIdx] || '',
tipo: (row[cpfIdx] || '').length > 14 ? 'pj' : 'pf',
cpfCnpj: row[cpfIdx] || '',
email: row[emailIdx] || '',
telefone: row[telIdx] || '',
endereco: row[endIdx] || '',
processos: 0
});
}
});
saveAppData();
updateStats();
showToast('Dados importados com sucesso!');
}
async function uploadToGoogleDrive(file, evidenceId) {
if (!appData.driveConnected) return;
try {
const metadata = {
name: `[LegalData] ${file.name}`,
parents: [],
description: `Prova enviada via LegalData Manager - ID: ${evidenceId}`
};
const form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
form.append('file', file);
const response = await fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart', {
method: 'POST',
headers: new Headers({ 'Authorization': 'Bearer ' + gapi.auth.getToken().access_token }),
body: form
});
const result = await response.json();
const ev = appData.evidencias.find(e => e.id === evidenceId);
if (ev) {
ev.driveId = result.id;
saveAppData();
showToast('Arquivo salvo no Google Drive!', 'info');
}
} catch(e) {
console.error('Drive upload error:', e);
showToast('Erro ao salvar no Drive', 'error');
}
}
function updateDriveStatus(connected) {
appData.driveConnected = connected;
const icon = document.getElementById('drive-icon');
const text = document.getElementById('drive-status-text');
const sub = document.getElementById('drive-status-sub');
const btn = document.getElementById('drive-connect-btn');
const dot = document.getElementById('drive-conn-dot');
const label = document.getElementById('drive-conn-label');
const intBtn = document.getElementById('drive-integration-btn');
if (connected) {
if (icon) icon.className = 'w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center text-green-600';
if (icon) icon.innerHTML = '<i class="fab fa-google-drive text-xl"></i>';
if (text) text.textContent = 'Google Drive conectado';
if (sub) sub.textContent = 'Arquivos serão sincronizados automaticamente';
if (btn) { btn.textContent = 'Desconectar'; btn.className = 'bg-red-100 text-red-600 px-4 py-2 rounded-lg hover:bg-red-200 transition'; btn.onclick = disconnectGoogleDrive; }
if (dot) { dot.className = 'w-3 h-3 rounded-full bg-green-500'; }
if (label) { label.textContent = 'Conectado'; label.className = 'text-sm text-green-600 font-medium'; }
if (intBtn) { intBtn.textContent = 'Desconectar'; intBtn.className = 'w-full bg-red-100 text-red-600 py-2 rounded-lg hover:bg-red-200 transition'; }
} else {
if (icon) icon.className = 'w-10 h-10 rounded-lg bg-gray-100 flex items-center justify-center text-gray-400';
if (icon) icon.innerHTML = '<i class="fab fa-google-drive text-xl"></i>';
if (text) text.textContent = 'Google Drive desconectado';
if (sub) sub.textContent = 'Conecte para sincronizar provas na nuvem';
if (btn) { btn.innerHTML = '<i class="fab fa-google mr-2"></i> Conectar Google Drive'; btn.className = 'bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition'; btn.onclick = connectGoogleDrive; }
if (dot) { dot.className = 'w-3 h-3 rounded-full bg-gray-400'; }
if (label) { label.textContent = 'Desconectado'; label.className = 'text-sm text-gray-500 font-medium'; }
}
}
function updateSheetsStatus(connected) {
appData.sheetsConnected = connected;
const dot = document.getElementById('sheets-conn-dot');
const label = document.getElementById('sheets-conn-label');
const btn = document.getElementById('sheets-integration-btn');
if (connected) {
if (dot) dot.className = 'w-3 h-3 rounded-full bg-green-500';
if (label) { label.textContent = 'Conectado'; label.className = 'text-sm text-green-600 font-medium'; }
if (btn) { btn.textContent = 'Sincronizar Dados'; btn.className = 'w-full bg-green-100 text-green-700 py-2 rounded-lg hover:bg-green-200 transition'; }
}
}
function disconnectGoogleDrive() {
if (tokenClient) {
const token = gapi?.auth?.getToken();
if (token) google.accounts.oauth2.revoke(token.access_token);
}
updateDriveStatus(false);
updateSheetsStatus(false);
showToast('Google Drive desconectado', 'info');
}
function showGoogleConfigInfo() {
const info = `
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-4">
<h4 class="font-semibold text-yellow-800 mb-2"><i class="fas fa-info-circle mr-2"></i>Configuração do Google OAuth</h4>
<p class="text-sm text-yellow-700 mb-2">Para usar as integrações Google, você precisa:</p>
<ol class="text-sm text-yellow-700 list-decimal list-inside space-y-1">
<li>Acessar <a href="https://console.cloud.google.com/" target="_blank" class="underline text-yellow-800">Google Cloud Console</a></li>
<li>Criar um projeto e ativar as APIs: Google Drive API e Google Sheets API</li>
<li>Criar credenciais OAuth 2.0</li>
<li>Adicionar <code class="bg-yellow-100 px-1 rounded">http://localhost</code> como origem autorizada</li>
<li>Copiar o Client ID e atualizar no arquivo <code class="bg-yellow-100 px-1 rounded">app.js</code></li>
</ol>
<p class="text-sm text-yellow-700 mt-2">Enquanto isso, o sistema funciona com dados locais e de demonstração.</p>
</div>`;
const list = document.getElementById('intimacoes-list');
if (list) list.innerHTML = info + list.innerHTML;
}
// --- File Import ---
function handleFileImport(event) {
const file = event.target.files[0];
if (!file) return;
if (file.name.endsWith('.csv')) {
Papa.parse(file, {
header: true,
complete: (results) => {
appData.importPreview = [results.meta.fields, ...results.data.map(r => results.meta.fields.map(f => r[f]))];
renderImportPreview(appData.importPreview);
showToast(`${results.data.length} registros importados do CSV`);
}
});
} else if (file.name.endsWith('.xlsx') || file.name.endsWith('.xls')) {
const reader = new FileReader();
reader.onload = (e) => {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
const jsonData = XLSX.utils.sheet_to_json(sheet, { header: 1 });
appData.importPreview = jsonData;
renderImportPreview(jsonData);
showToast(`${jsonData.length - 1} registros importados do Excel`);
};
reader.readAsArrayBuffer(file);
}
}
// --- Sanitization ---
function sanitizeData(type) {
let count = 0;
switch(type) {
case 'cpfcnpj':
appData.clientes.forEach(c => {
c.cpfCnpj = c.cpfCnpj.replace(/[^\d./-]/g, '');
count++;
});
showToast(`${count} CPFs/CNPJs padronizados`);
break;
case 'phone':
appData.clientes.forEach(c => {
c.telefone = c.telefone.replace(/[^\d() -]/g, '');
count++;
});
showToast(`${count} telefones padronizados`);
break;
case 'email':
const invalid = appData.clientes.filter(c => !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(c.email));
showToast(`${invalid.length} e-mails inválidos encontrados`, invalid.length > 0 ? 'warning' : 'success');
break;
case 'duplicates':
const seen = new Set();
const before = appData.clientes.length;
appData.clientes = appData.clientes.filter(c => {
const key = c.cpfCnpj || c.email;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
count = before - appData.clientes.length;
showToast(`${count} duplicatas removidas`, count > 0 ? 'info' : 'success');
break;
}
saveAppData();
renderClientes();
}
// --- Utility Functions ---
function formatDate(dateStr) {
if (!dateStr) return 'N/A';
try {
const d = new Date(dateStr);
return d.toLocaleDateString('pt-BR');
} catch(e) { return dateStr; }
}
function generateId() {
return '2024-' + String(Math.floor(Math.random() * 9000) + 1000);
}
function updateStats() {
document.getElementById('stat-processos').textContent = appData.processos.length;
document.getElementById('stat-clientes').textContent = appData.clientes.length;
document.getElementById('stat-provas').textContent = appData.evidencias.length;
}
function updateNotificationBadge() {
const badge = document.getElementById('notification-badge');
const count = appData.intimacoes.length;
if (count > 0) {
badge.textContent = count;
badge.classList.remove('hidden');
} else {
badge.classList.add('hidden');
}
}
function populateSelects() {
// Populate client selects
const clientSelects = ['proc-cliente', 'ev-cliente'];
clientSelects.forEach(id => {
const el = document.getElementById(id);
if (el) {
el.innerHTML = '<option value="">Selecione...</option>' +
appData.clientes.map(c => `<option value="${c.id}">${c.nome}</option>`).join('');
}
});
// Populate process selects
const procSelects = ['ev-processo'];
procSelects.forEach(id => {
const el = document.getElementById(id);
if (el) {
el.innerHTML = '<option value="">Selecione...</option>' +
appData.processos.map(p => `<option value="${p.id}">#${p.id} - ${p.cliente}</option>`).join('');
}
});
// Evidence filter
const filterClient = document.getElementById('evidence-filter-client');
if (filterClient) {
filterClient.innerHTML = '<option value="">Todos os Clientes</option>' +
appData.clientes.map(c => `<option value="${c.id}">${c.nome}</option>`).join('');
}
}
// --- Deadline Table ---
function renderDeadlineTable() {
const tbody = document.getElementById('deadline-table-body');
if (!tbody) return;
const urgent = appData.processos
.filter(p => p.prazo && p.status !== 'Concluído')
.sort((a, b) => new Date(a.prazo) - new Date(b.prazo))
.slice(0, 5);
const statusColors = {
'Ativo': 'bg-green-100 text-green-800',
'Pendente': 'bg-yellow-100 text-yellow-800',
'Urgente': 'bg-red-100 text-red-800',
};
tbody.innerHTML = urgent.map(p => `
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">#${p.id}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${p.cliente}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${p.tribunal}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${formatDate(p.prazo)}</td>
<td class="px-6 py-4 whitespace-nowrap"><span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${statusColors[p.status] || 'bg-gray-100 text-gray-800'}">${p.status}</span></td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button class="text-indigo-600 hover:text-indigo-900 mr-3"><i class="fas fa-eye"></i></button>
<button class="text-green-600 hover:text-green-900"><i class="fas fa-edit"></i></button>
</td>
</tr>
`).join('');
}
// --- Chart ---
function initChart() {
const ctx = document.getElementById('processChart');
if (!ctx) return;
new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['TJSP', 'TJMG', 'TRF1', 'TRF4', 'Outros'],
datasets: [{
data: [45, 20, 15, 12, 8],
backgroundColor: ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#6B7280'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom' }
}
}
});
}
// --- Auto-refresh AASP ---
let aaspInterval = null;
function startAASPRefresh() {
if (aaspInterval) clearInterval(aaspInterval);
aaspInterval = setInterval(() => {
if (document.getElementById('aasp-auto-refresh')?.checked) {
fetchAASPIntimacoes();
}
}, 30 * 60 * 1000); // 30 minutes
}
// --- Keyboard Shortcuts ---
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
document.querySelectorAll('[id^="modal-"]').forEach(m => m.classList.add('hidden'));
closeLightbox();
}
});
// --- Initialize ---
document.addEventListener('DOMContentLoaded', () => {
loadAppData();
populateSelects();
renderProcessos();
renderClientes();
renderDeadlineTable();
initChart();
updateStats();
updateNotificationBadge();
if (appData.driveConnected) updateDriveStatus(true);
if (appData.sheetsConnected) updateSheetsStatus(true);
startAASPRefresh();
// Set today's date for AASP query
const today = new Date().toISOString().split('T')[0];
const dataField = document.getElementById('aasp-data');
if (dataField) dataField.value = today;
});