alexandremoraisdarosa commited on
Commit
3e547f2
·
verified ·
1 Parent(s): c1a7f7f

Melhore as funcionalidades e o UX conforme as melhores práticas

Browse files
Files changed (3) hide show
  1. index.html +21 -9
  2. script.js +104 -30
  3. style.css +10 -0
index.html CHANGED
@@ -25,18 +25,30 @@
25
 
26
  <!-- Controles -->
27
  <div class="flex flex-wrap justify-between items-center gap-4 mb-6 pb-4 border-b no-print">
28
- <div class="flex gap-2">
29
- <input type="text" id="newEntityInput" placeholder="Nome do Réu, Prova, Testemunha..." class="border border-gray-300 rounded px-4 py-2 w-72 focus:ring-2 focus:ring-primary outline-none transition" />
30
- <button id="addEntityBtn" class="bg-primary hover:bg-primary-dark text-white font-semibold py-2 px-4 rounded transition duration-200">➕ Adicionar</button>
 
 
 
 
31
  </div>
32
- <div class="flex gap-2">
33
- <button onclick="exportMatrix()" class="bg-secondary hover:bg-secondary-dark text-white font-semibold py-2 px-4 rounded transition duration-200">📤 Exportar</button>
34
- <button onclick="importMatrix()" class="bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-2 px-4 rounded transition duration-200">📥 Importar</button>
35
- <button onclick="window.print()" class="bg-green-600 hover:bg-green-700 text-white font-semibold py-2 px-4 rounded transition duration-200">🖨️ Imprimir / Salvar PDF</button>
 
 
 
 
 
 
 
 
 
36
  </div>
37
  </div>
38
-
39
- <!-- Tabela -->
40
  <div class="overflow-x-auto">
41
  <table id="matrix-table" class="min-w-full border-collapse"></table>
42
  </div>
 
25
 
26
  <!-- Controles -->
27
  <div class="flex flex-wrap justify-between items-center gap-4 mb-6 pb-4 border-b no-print">
28
+ <div class="flex gap-2 flex-grow">
29
+ <input type="text" id="newEntityInput" placeholder="Nome do Réu, Prova, Testemunha..."
30
+ class="border border-gray-300 rounded px-4 py-2 w-full max-w-md focus:ring-2 focus:ring-primary outline-none transition"
31
+ autocomplete="off" />
32
+ <button id="addEntityBtn" class="bg-primary hover:bg-primary-dark text-white font-semibold py-2 px-4 rounded transition duration-200 flex items-center gap-1">
33
+ <span class="text-lg">➕</span> Adicionar
34
+ </button>
35
  </div>
36
+ <div class="flex gap-2 flex-wrap">
37
+ <button onclick="exportMatrix()" class="bg-secondary hover:bg-secondary-dark text-white font-semibold py-2 px-4 rounded transition duration-200 flex items-center gap-1">
38
+ <span class="text-lg">📤</span> Exportar
39
+ </button>
40
+ <button onclick="importMatrix()" class="bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-2 px-4 rounded transition duration-200 flex items-center gap-1">
41
+ <span class="text-lg">📥</span> Importar
42
+ </button>
43
+ <button onclick="window.print()" class="bg-green-600 hover:bg-green-700 text-white font-semibold py-2 px-4 rounded transition duration-200 flex items-center gap-1">
44
+ <span class="text-lg">🖨️</span> Imprimir
45
+ </button>
46
+ <button onclick="clearAllData()" class="bg-red-600 hover:bg-red-700 text-white font-semibold py-2 px-4 rounded transition duration-200 flex items-center gap-1">
47
+ <span class="text-lg">🗑️</span> Limpar Tudo
48
+ </button>
49
  </div>
50
  </div>
51
+ <!-- Tabela -->
 
52
  <div class="overflow-x-auto">
53
  <table id="matrix-table" class="min-w-full border-collapse"></table>
54
  </div>
script.js CHANGED
@@ -7,15 +7,13 @@ document.addEventListener('DOMContentLoaded', () => {
7
  const noteTextarea = document.getElementById('note-textarea');
8
  const noteContext = document.getElementById('note-context');
9
  let currentNoteCell = null;
10
-
11
  const RELATION_STATES = [
12
- { class: 'bg-green-100 text-green-800', symbol: '✔', tooltip: 'Vínculo Confirmado' },
13
- { class: 'bg-yellow-100 text-yellow-800', symbol: '∼', tooltip: 'Vínculo Possível' },
14
- { class: 'bg-red-100 text-red-800', symbol: '✖', tooltip: 'Vínculo Excluído' },
15
- { class: 'bg-gray-100 text-gray-600', symbol: '•', tooltip: 'Não Analisado' }
16
  ];
17
-
18
- let data = { entities: [], relations: {} };
19
 
20
  function saveData() {
21
  localStorage.setItem('triangularMatrixData', JSON.stringify(data));
@@ -92,33 +90,75 @@ document.addEventListener('DOMContentLoaded', () => {
92
 
93
  saveData();
94
  }
95
-
96
  function addEntity() {
97
  const name = newEntityInput.value.trim();
98
- if (!name) { alert('Por favor, digite o nome da entidade.'); return; }
99
- if (data.entities.includes(name)) { alert('Essa entidade está na lista.'); return; }
 
 
 
 
 
 
 
100
  data.entities.push(name);
101
  newEntityInput.value = '';
102
  renderMatrix();
 
103
  }
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  function deleteEntity(index) {
106
  const entityName = data.entities[index];
107
- if (!confirm(`Tem certeza que deseja remover "${entityName}"? Todos os seus vínculos serão perdidos.`)) return;
108
- data.entities.splice(index, 1);
109
- const newRelations = {};
110
- for (const key in data.relations) {
111
- const [r, c] = key.split('-').map(Number);
112
- if (r === index || c === index) continue;
113
- let newR = r > index ? r - 1 : r;
114
- let newC = c > index ? c - 1 : c;
115
- newRelations[`${newR}-${newC}`] = data.relations[key];
116
- }
117
- data.relations = newRelations;
118
- renderMatrix();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  }
120
-
121
- function getRelationKey(cell) { return `${cell.dataset.row}-${cell.dataset.col}`; }
122
 
123
  window.openNoteModal = (cell) => {
124
  currentNoteCell = cell;
@@ -155,10 +195,37 @@ document.addEventListener('DOMContentLoaded', () => {
155
  a.click();
156
  URL.revokeObjectURL(a.href);
157
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
  window.importMatrix = () => { importInput.click(); };
160
-
161
- importInput.addEventListener('change', (e) => {
162
  const file = e.target.files[0];
163
  if (!file) return;
164
  const reader = new FileReader();
@@ -180,14 +247,22 @@ document.addEventListener('DOMContentLoaded', () => {
180
 
181
  addEntityBtn.addEventListener('click', addEntity);
182
  newEntityInput.addEventListener('keypress', e => { if (e.key === 'Enter') addEntity(); });
183
-
184
  matrixTable.addEventListener('click', e => {
185
  const cell = e.target.closest('.relation-cell');
186
  if (cell) {
 
 
 
187
  const key = getRelationKey(cell);
188
  if (!data.relations[key]) data.relations[key] = { state: 3, note: '' };
189
  data.relations[key].state = (data.relations[key].state + 1) % RELATION_STATES.length;
190
- renderMatrix();
 
 
 
 
 
 
191
  return;
192
  }
193
  const deleteBtn = e.target.closest('.btn-delete');
@@ -195,8 +270,7 @@ document.addEventListener('DOMContentLoaded', () => {
195
  deleteEntity(parseInt(deleteBtn.dataset.index));
196
  }
197
  });
198
-
199
- matrixTable.addEventListener('contextmenu', e => {
200
  const cell = e.target.closest('.relation-cell');
201
  if (cell) {
202
  e.preventDefault();
 
7
  const noteTextarea = document.getElementById('note-textarea');
8
  const noteContext = document.getElementById('note-context');
9
  let currentNoteCell = null;
 
10
  const RELATION_STATES = [
11
+ { class: 'bg-green-100 text-green-800 hover:bg-green-200', symbol: '✔', tooltip: 'Vínculo Confirmado' },
12
+ { class: 'bg-yellow-100 text-yellow-800 hover:bg-yellow-200', symbol: '∼', tooltip: 'Vínculo Possível' },
13
+ { class: 'bg-red-100 text-red-800 hover:bg-red-200', symbol: '✖', tooltip: 'Vínculo Excluído' },
14
+ { class: 'bg-gray-100 text-gray-600 hover:bg-gray-200', symbol: '•', tooltip: 'Não Analisado' }
15
  ];
16
+ let data = { entities: [], relations: {} };
 
17
 
18
  function saveData() {
19
  localStorage.setItem('triangularMatrixData', JSON.stringify(data));
 
90
 
91
  saveData();
92
  }
 
93
  function addEntity() {
94
  const name = newEntityInput.value.trim();
95
+ if (!name) {
96
+ showToast('Por favor, digite o nome da entidade.', 'error');
97
+ newEntityInput.focus();
98
+ return;
99
+ }
100
+ if (data.entities.includes(name)) {
101
+ showToast('Essa entidade já está na lista.', 'warning');
102
+ return;
103
+ }
104
  data.entities.push(name);
105
  newEntityInput.value = '';
106
  renderMatrix();
107
+ showToast('Entidade adicionada com sucesso!', 'success');
108
  }
109
 
110
+ function showToast(message, type = 'info') {
111
+ const toast = document.createElement('div');
112
+ toast.className = `fixed bottom-4 right-4 px-4 py-2 rounded-md shadow-lg text-white ${
113
+ type === 'error' ? 'bg-red-500' :
114
+ type === 'success' ? 'bg-green-500' :
115
+ type === 'warning' ? 'bg-yellow-500' : 'bg-blue-500'
116
+ }`;
117
+ toast.textContent = message;
118
+ document.body.appendChild(toast);
119
+ setTimeout(() => {
120
+ toast.classList.add('opacity-0', 'transition-opacity', 'duration-300');
121
+ setTimeout(() => toast.remove(), 300);
122
+ }, 3000);
123
+ }
124
  function deleteEntity(index) {
125
  const entityName = data.entities[index];
126
+ const modal = document.createElement('div');
127
+ modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
128
+ modal.innerHTML = `
129
+ <div class="bg-white rounded-lg shadow-lg max-w-md w-full p-6">
130
+ <h2 class="text-xl font-bold mb-4">Confirmar Exclusão</h2>
131
+ <p class="mb-6">Tem certeza que deseja remover <strong>"${entityName}"</strong>? Todos os seus vínculos serão perdidos.</p>
132
+ <div class="flex justify-end gap-2">
133
+ <button class="cancel-btn px-4 py-2 bg-gray-300 hover:bg-gray-400 rounded-md transition">Cancelar</button>
134
+ <button class="confirm-btn px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-md transition">Excluir</button>
135
+ </div>
136
+ </div>
137
+ `;
138
+
139
+ document.body.appendChild(modal);
140
+
141
+ modal.querySelector('.cancel-btn').addEventListener('click', () => {
142
+ modal.remove();
143
+ });
144
+
145
+ modal.querySelector('.confirm-btn').addEventListener('click', () => {
146
+ data.entities.splice(index, 1);
147
+ const newRelations = {};
148
+ for (const key in data.relations) {
149
+ const [r, c] = key.split('-').map(Number);
150
+ if (r === index || c === index) continue;
151
+ let newR = r > index ? r - 1 : r;
152
+ let newC = c > index ? c - 1 : c;
153
+ newRelations[`${newR}-${newC}`] = data.relations[key];
154
+ }
155
+ data.relations = newRelations;
156
+ renderMatrix();
157
+ modal.remove();
158
+ showToast('Entidade removida com sucesso!', 'success');
159
+ });
160
  }
161
+ function getRelationKey(cell) { return `${cell.dataset.row}-${cell.dataset.col}`; }
 
162
 
163
  window.openNoteModal = (cell) => {
164
  currentNoteCell = cell;
 
195
  a.click();
196
  URL.revokeObjectURL(a.href);
197
  };
198
+ window.clearAllData = () => {
199
+ const modal = document.createElement('div');
200
+ modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
201
+ modal.innerHTML = `
202
+ <div class="bg-white rounded-lg shadow-lg max-w-md w-full p-6">
203
+ <h2 class="text-xl font-bold mb-4">Confirmar Limpeza</h2>
204
+ <p class="mb-6">Tem certeza que deseja limpar TODOS os dados? Esta ação não pode ser desfeita.</p>
205
+ <div class="flex justify-end gap-2">
206
+ <button class="cancel-btn px-4 py-2 bg-gray-300 hover:bg-gray-400 rounded-md transition">Cancelar</button>
207
+ <button class="confirm-btn px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-md transition">Limpar Tudo</button>
208
+ </div>
209
+ </div>
210
+ `;
211
+
212
+ document.body.appendChild(modal);
213
+
214
+ modal.querySelector('.cancel-btn').addEventListener('click', () => {
215
+ modal.remove();
216
+ });
217
+
218
+ modal.querySelector('.confirm-btn').addEventListener('click', () => {
219
+ data = { entities: [], relations: {} };
220
+ localStorage.removeItem('triangularMatrixData');
221
+ renderMatrix();
222
+ modal.remove();
223
+ showToast('Todos os dados foram limpos!', 'success');
224
+ });
225
+ };
226
 
227
  window.importMatrix = () => { importInput.click(); };
228
+ importInput.addEventListener('change', (e) => {
 
229
  const file = e.target.files[0];
230
  if (!file) return;
231
  const reader = new FileReader();
 
247
 
248
  addEntityBtn.addEventListener('click', addEntity);
249
  newEntityInput.addEventListener('keypress', e => { if (e.key === 'Enter') addEntity(); });
 
250
  matrixTable.addEventListener('click', e => {
251
  const cell = e.target.closest('.relation-cell');
252
  if (cell) {
253
+ cell.classList.add('scale-90');
254
+ setTimeout(() => cell.classList.remove('scale-90'), 200);
255
+
256
  const key = getRelationKey(cell);
257
  if (!data.relations[key]) data.relations[key] = { state: 3, note: '' };
258
  data.relations[key].state = (data.relations[key].state + 1) % RELATION_STATES.length;
259
+
260
+ // Feedback visual imediato antes do re-render
261
+ const newState = RELATION_STATES[data.relations[key].state];
262
+ cell.className = `relation-cell border-b border-gray-300 text-center cursor-pointer relative text-3xl w-16 h-16 ${newState.class} scale-90`;
263
+ cell.innerHTML = `${newState.symbol}<span class="note-indicator"></span>`;
264
+
265
+ setTimeout(() => renderMatrix(), 200); // Re-render após animação
266
  return;
267
  }
268
  const deleteBtn = e.target.closest('.btn-delete');
 
270
  deleteEntity(parseInt(deleteBtn.dataset.index));
271
  }
272
  });
273
+ matrixTable.addEventListener('contextmenu', e => {
 
274
  const cell = e.target.closest('.relation-cell');
275
  if (cell) {
276
  e.preventDefault();
style.css CHANGED
@@ -8,9 +8,19 @@
8
  background-color: #3b82f6;
9
  border-radius: 50%;
10
  display: none;
 
11
  }
12
  .has-note .note-indicator {
13
  display: block;
 
 
 
 
 
 
 
 
 
14
  }
15
  .btn-delete {
16
  visibility: hidden;
 
8
  background-color: #3b82f6;
9
  border-radius: 50%;
10
  display: none;
11
+ transition: all 0.2s ease;
12
  }
13
  .has-note .note-indicator {
14
  display: block;
15
+ animation: pulse 1.5s infinite;
16
+ }
17
+ @keyframes pulse {
18
+ 0% { transform: scale(1); }
19
+ 50% { transform: scale(1.3); }
20
+ 100% { transform: scale(1); }
21
+ }
22
+ .relation-cell {
23
+ transition: all 0.2s ease;
24
  }
25
  .btn-delete {
26
  visibility: hidden;