document.addEventListener('DOMContentLoaded', () => { const matrixTable = document.getElementById('matrix-table'); const addEntityBtn = document.getElementById('addEntityBtn'); const newEntityInput = document.getElementById('newEntityInput'); const importInput = document.getElementById('importInput'); const noteModal = document.getElementById('note-modal'); const noteTextarea = document.getElementById('note-textarea'); const noteContext = document.getElementById('note-context'); let currentNoteCell = null; const RELATION_STATES = [ { class: 'bg-green-100 text-green-800 hover:bg-green-200', symbol: '✔', tooltip: 'Vínculo Confirmado' }, { class: 'bg-yellow-100 text-yellow-800 hover:bg-yellow-200', symbol: '∼', tooltip: 'Vínculo Possível' }, { class: 'bg-red-100 text-red-800 hover:bg-red-200', symbol: '✖', tooltip: 'Vínculo Excluído' }, { class: 'bg-gray-100 text-gray-600 hover:bg-gray-200', symbol: '•', tooltip: 'Não Analisado' } ]; let data = { entities: [], relations: {} }; function saveData() { localStorage.setItem('triangularMatrixData', JSON.stringify(data)); } function loadData() { const savedData = localStorage.getItem('triangularMatrixData'); if (savedData) { try { data = JSON.parse(savedData); } catch (e) { console.error("Erro ao carregar dados."); } } else { data = { entities: ["Réu João", "Testemunha Maria", "Laudo A", "Réu Pedro"], relations: { '1-0': { state: 2 }, '2-0': { state: 0, note: 'Laudo confirma digital do Réu João.' }, '3-1': { state: 1 } } }; } } function renderMatrix() { matrixTable.innerHTML = ''; const { entities } = data; if (entities.length < 2) { matrixTable.innerHTML = 'Adicione pelo menos duas entidades para iniciar a matriz.'; return; } // 1. Criar as linhas do corpo da matriz (cabeçalhos de linha + células) for (let rowIndex = 1; rowIndex < entities.length; rowIndex++) { const tr = document.createElement('tr'); // Cabeçalho da Linha (Eixo Y, à esquerda) const rowTh = document.createElement('th'); rowTh.className = 'min-w-[200px] bg-gray-50 border-b border-gray-300 p-2 text-right pr-4 whitespace-nowrap relative'; rowTh.innerHTML = `${entities[rowIndex]}🗑️`; tr.appendChild(rowTh); // Células de Relação for (let colIndex = 0; colIndex < rowIndex; colIndex++) { const td = document.createElement('td'); td.dataset.row = rowIndex; td.dataset.col = colIndex; const key = `${rowIndex}-${colIndex}`; const relation = data.relations[key] || { state: 3, note: '' }; const state = RELATION_STATES[relation.state]; td.className = `relation-cell border-b border-gray-300 text-center cursor-pointer relative text-3xl w-16 h-16 ${state.class}`; td.setAttribute('title', state.tooltip); td.innerHTML = `${state.symbol}`; if (relation.note) { td.classList.add('has-note'); } tr.appendChild(td); } matrixTable.appendChild(tr); } // 2. Criar a última linha com os cabeçalhos das colunas (Eixo X, no fundo) const footerRow = document.createElement('tr'); // Canto inferior esquerdo (vazio) footerRow.appendChild(document.createElement('th')); // Cabeçalhos da Coluna for (let i = 0; i < entities.length - 1; i++) { const th = document.createElement('th'); th.className = 'bg-gray-50 border-gray-300 p-2 text-center align-top relative'; th.innerHTML = `${entities[i]}🗑️`; footerRow.appendChild(th); } matrixTable.appendChild(footerRow); saveData(); } function addEntity() { const name = newEntityInput.value.trim(); if (!name) { showToast('Por favor, digite o nome da entidade.', 'error'); newEntityInput.focus(); return; } if (data.entities.includes(name)) { showToast('Essa entidade já está na lista.', 'warning'); return; } data.entities.push(name); newEntityInput.value = ''; renderMatrix(); showToast('Entidade adicionada com sucesso!', 'success'); } function showToast(message, type = 'info') { const toast = document.createElement('div'); toast.className = `fixed bottom-4 right-4 px-4 py-2 rounded-md shadow-lg text-white ${ type === 'error' ? 'bg-red-500' : type === 'success' ? 'bg-green-500' : type === 'warning' ? 'bg-yellow-500' : 'bg-blue-500' }`; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { toast.classList.add('opacity-0', 'transition-opacity', 'duration-300'); setTimeout(() => toast.remove(), 300); }, 3000); } function deleteEntity(index) { const entityName = data.entities[index]; const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50'; modal.innerHTML = `

Confirmar Exclusão

Tem certeza que deseja remover "${entityName}"? Todos os seus vínculos serão perdidos.

`; document.body.appendChild(modal); modal.querySelector('.cancel-btn').addEventListener('click', () => { modal.remove(); }); modal.querySelector('.confirm-btn').addEventListener('click', () => { data.entities.splice(index, 1); const newRelations = {}; for (const key in data.relations) { const [r, c] = key.split('-').map(Number); if (r === index || c === index) continue; let newR = r > index ? r - 1 : r; let newC = c > index ? c - 1 : c; newRelations[`${newR}-${newC}`] = data.relations[key]; } data.relations = newRelations; renderMatrix(); modal.remove(); showToast('Entidade removida com sucesso!', 'success'); }); } function getRelationKey(cell) { return `${cell.dataset.row}-${cell.dataset.col}`; } window.openNoteModal = (cell) => { currentNoteCell = cell; const key = getRelationKey(cell); const [row, col] = key.split('-').map(Number); noteContext.innerHTML = `Anotação entre: "${data.entities[row]}" e "${data.entities[col]}"`; noteTextarea.value = data.relations[key]?.note || ''; noteModal.classList.remove('hidden'); noteTextarea.focus(); }; window.closeNoteModal = () => { noteModal.classList.add('hidden'); currentNoteCell = null; }; window.saveNote = () => { if (!currentNoteCell) return; const key = getRelationKey(currentNoteCell); const note = noteTextarea.value.trim(); if (!data.relations[key]) data.relations[key] = { state: 3, note: '' }; data.relations[key].note = note; if (note) currentNoteCell.classList.add('has-note'); else currentNoteCell.classList.remove('has-note'); saveData(); closeNoteModal(); }; window.exportMatrix = () => { const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `matriz-vinculos-${new Date().toISOString().slice(0,10)}.json`; a.click(); URL.revokeObjectURL(a.href); }; window.clearAllData = () => { const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50'; modal.innerHTML = `

Confirmar Limpeza

Tem certeza que deseja limpar TODOS os dados? Esta ação não pode ser desfeita.

`; document.body.appendChild(modal); modal.querySelector('.cancel-btn').addEventListener('click', () => { modal.remove(); }); modal.querySelector('.confirm-btn').addEventListener('click', () => { data = { entities: [], relations: {} }; localStorage.removeItem('triangularMatrixData'); renderMatrix(); modal.remove(); showToast('Todos os dados foram limpos!', 'success'); }); }; window.importMatrix = () => { importInput.click(); }; importInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = () => { try { const importedData = JSON.parse(reader.result); if (importedData.entities && importedData.relations) { data = importedData; renderMatrix(); alert('✅ Matriz importada com sucesso!'); } else { alert('❌ Erro ao importar: O arquivo JSON não tem a estrutura esperada.'); } } catch (err) { alert('❌ Erro ao importar: formato de arquivo inválido.'); } }; reader.readAsText(file); e.target.value = ''; }); addEntityBtn.addEventListener('click', addEntity); newEntityInput.addEventListener('keypress', e => { if (e.key === 'Enter') addEntity(); }); matrixTable.addEventListener('click', e => { const cell = e.target.closest('.relation-cell'); if (cell) { cell.classList.add('scale-90'); setTimeout(() => cell.classList.remove('scale-90'), 200); const key = getRelationKey(cell); if (!data.relations[key]) data.relations[key] = { state: 3, note: '' }; data.relations[key].state = (data.relations[key].state + 1) % RELATION_STATES.length; // Feedback visual imediato antes do re-render const newState = RELATION_STATES[data.relations[key].state]; cell.className = `relation-cell border-b border-gray-300 text-center cursor-pointer relative text-3xl w-16 h-16 ${newState.class} scale-90`; cell.innerHTML = `${newState.symbol}`; setTimeout(() => renderMatrix(), 200); // Re-render após animação return; } const deleteBtn = e.target.closest('.btn-delete'); if (deleteBtn) { deleteEntity(parseInt(deleteBtn.dataset.index)); } }); matrixTable.addEventListener('contextmenu', e => { const cell = e.target.closest('.relation-cell'); if (cell) { e.preventDefault(); openNoteModal(cell); } }); loadData(); renderMatrix(); });