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();
});