From 95a6973313c36c446270099ccb4e24d127090e6d Mon Sep 17 00:00:00 2001 From: janis steiner Date: Fri, 8 May 2026 01:00:48 +0200 Subject: [PATCH] . --- backend/api/index.php | 19 +++++ frontend/assets/js/app.js | 173 ++++++++++++++++++++++++-------------- frontend/index.html | 19 +++++ 3 files changed, 148 insertions(+), 63 deletions(-) diff --git a/backend/api/index.php b/backend/api/index.php index 033a984..540fa3d 100644 --- a/backend/api/index.php +++ b/backend/api/index.php @@ -337,6 +337,25 @@ function handleEvents($method, $id, $db) { ]); echo json_encode(['id' => $db->lastInsertId()]); break; + case 'PUT': + if ($id) { + $data = json_decode(file_get_contents('php://input'), true); + $fields = []; + $params = []; + foreach (['team_id','title','description','severity','event_type','occurred_at'] as $f) { + if (isset($data[$f])) { + $fields[] = "$f = ?"; + $params[] = $data[$f]; + } + } + if ($fields) { + $params[] = $id; + $stmt = $db->prepare("UPDATE events SET " . implode(', ', $fields) . " WHERE id = ?"); + $stmt->execute($params); + } + echo json_encode(['updated' => true]); + } + break; case 'DELETE': if ($id) { $stmt = $db->prepare("DELETE FROM events WHERE id = ?"); diff --git a/frontend/assets/js/app.js b/frontend/assets/js/app.js index 9db8bf4..c38337e 100644 --- a/frontend/assets/js/app.js +++ b/frontend/assets/js/app.js @@ -3,6 +3,7 @@ let teams = []; let events = []; let documents = []; let editingDocId = null; +let linkingEventId = null; let nodes = []; let links = []; let shapes = []; @@ -131,6 +132,7 @@ function initApp() { document.getElementById('saveNode').addEventListener('click', saveNode); document.getElementById('saveLink').addEventListener('click', saveLink); document.getElementById('saveShape').addEventListener('click', saveShape); + document.getElementById('saveLinkDocs').addEventListener('click', saveLinkDocs); document.getElementById('addUserBtn').addEventListener('click', addUser); document.getElementById('logoutBtn').addEventListener('click', logout); document.getElementById('teamFilter').addEventListener('change', renderTimeline); @@ -269,8 +271,12 @@ function renderTimeline() {
${esc(e.title)}
${e.description ? '

' + renderDocLinks(e.description) + '

' : ''} -
- +
+ +
+ + +
Comments ${e.comments && e.comments.length ? '(' + e.comments.length + ')' : ''}
@@ -297,6 +303,16 @@ function renderDocLinks(text) { }).join(''); } +function renderInlineDocLinks(desc) { + const ids = extractDocIdsFromDesc(desc); + if (!ids.length) return ''; + const labels = ids.map(id => { + const d = documents.find(x => x.id == id); + return d ? `${esc(d.title)}` : ''; + }).join(''); + return labels ? '' + labels : ''; +} + function openDocument(id) { const tab = document.getElementById('documents-tab'); if (tab) tab.click(); @@ -319,17 +335,16 @@ function openDocument(id) { target.style.borderColor = ''; }, 3000); } else { - loadDocuments().then(() => { - setTimeout(() => { - const el = document.getElementById('documentContainer').querySelector(`[data-doc-id="${id}"]`); - if (el) { - el.scrollIntoView({ behavior: 'smooth', block: 'center' }); - el.style.boxShadow = '0 0 20px rgba(59,130,246,.5)'; - el.style.borderColor = 'var(--neptune-accent)'; - setTimeout(() => { el.style.boxShadow = ''; el.style.borderColor = ''; }, 3000); - } - }, 100); - }); + loadDocuments(); + setTimeout(() => { + const el = document.getElementById('documentContainer').querySelector(`[data-doc-id="${id}"]`); + if (el) { + el.scrollIntoView({ behavior: 'smooth', block: 'center' }); + el.style.boxShadow = '0 0 20px rgba(59,130,246,.5)'; + el.style.borderColor = 'var(--neptune-accent)'; + setTimeout(() => { el.style.boxShadow = ''; el.style.borderColor = ''; }, 3000); + } + }, 500); } const filter = document.getElementById('searchDocs'); if (filter) filter.value = ''; @@ -338,6 +353,53 @@ function openDocument(id) { }, 300); } +function linkDocsToEvent(eventId) { + linkingEventId = eventId; + const list = document.getElementById('linkDocsList'); + if (!documents.length) { + list.innerHTML = '
No documents available. Create some first.
'; + } else { + const alreadyLinked = extractDocIdsFromDesc(events.find(e => e.id == eventId)?.description || ''); + list.innerHTML = documents.map(d => { + const checked = alreadyLinked.includes(d.id) ? 'checked' : ''; + return `
+ + +
`; + }).join(''); + } + new bootstrap.Modal(document.getElementById('linkDocsModal')).show(); +} + +function extractDocIdsFromDesc(desc) { + const ids = []; + const re = /\[doc:(\d+)\]/g; + let m; + while ((m = re.exec(desc)) !== null) ids.push(parseInt(m[1])); + return ids; +} + +async function saveLinkDocs() { + const modal = bootstrap.Modal.getInstance(document.getElementById('linkDocsModal')); + if (!linkingEventId) { modal.hide(); return; } + const checks = document.querySelectorAll('#linkDocsList input[type="checkbox"]:checked'); + const selectedIds = Array.from(checks).map(c => parseInt(c.value)); + const event = events.find(e => e.id == linkingEventId); + if (!event) { modal.hide(); return; } + + let desc = event.description || ''; + desc = desc.replace(/\[doc:\d+\].*?\[\/doc\]\s*/g, '').trim(); + const docParts = selectedIds.map(id => { + const d = documents.find(x => x.id == id); + return d ? `[doc:${d.id}]${d.title}[/doc]` : ''; + }).filter(Boolean); + if (docParts.length) desc = (desc ? desc + '\n' : '') + 'Linked: ' + docParts.join(', '); + await apiFetch('events/' + linkingEventId, { method: 'PUT', body: JSON.stringify({ description: desc }) }); + modal.hide(); + linkingEventId = null; + loadEvents(); +} + function renderComments(event) { if (!event.comments || !event.comments.length) return '
No comments yet
'; return event.comments.map(c => ` @@ -1079,7 +1141,11 @@ function renderDocuments() { const meta = DOC_TYPE_ICONS[d.doc_type] || DOC_TYPE_ICONS.deployment; const label = DOC_TYPE_LABELS[d.doc_type] || d.doc_type; const date = new Date(d.occurred_at).toLocaleString(); - const contentPreview = d.content ? d.content.substring(0, 150) + (d.content.length > 150 ? '...' : '') : ''; + let contentPreview = ''; + if (d.content) { + try { const p = JSON.parse(d.content); contentPreview = p.notes || ''; } catch (e) { contentPreview = d.content; } + if (contentPreview.length > 150) contentPreview = contentPreview.substring(0, 150) + '...'; + } return `
@@ -1165,28 +1231,15 @@ function updateDocFormFields() {
`; }).join(''); - // Restore values if editing if (editingDocId && window._editDocData) { const data = window._editDocData; templates.forEach(f => { const el = document.getElementById(f.id); - if (el) el.value = data._fields?.[f.id] || ''; + if (el && data._parsedFields?.[f.id]) el.value = data._parsedFields[f.id]; }); } } -function openNewDocument(type) { - editingDocId = null; - document.getElementById('documentModalLabel').textContent = 'New ' + (DOC_TYPE_LABELS[type] || type) + ' Document'; - document.getElementById('saveDocument').innerHTML = ' Save Document'; - document.getElementById('docType').value = type; - document.getElementById('docTitle').value = ''; - document.getElementById('docContent').value = ''; - window._editDocData = null; - updateDocFormFields(); - new bootstrap.Modal(document.getElementById('documentModal')).show(); -} - function collectDocFields() { const docType = document.getElementById('docType').value; const templates = DOC_FIELD_TEMPLATES[docType] || []; @@ -1202,34 +1255,28 @@ async function saveDocument() { const docType = document.getElementById('docType').value; const teamId = document.getElementById('docTeam').value; const title = document.getElementById('docTitle').value.trim(); - const content = document.getElementById('docContent').value.trim(); + const notes = document.getElementById('docContent').value.trim(); const fields = collectDocFields(); if (!title) return alert('Title is required'); if (!teamId) return alert('Team is required'); - // Build structured content from fields - const label = DOC_TYPE_LABELS[docType] || docType; - let fullContent = content; - const fieldEntries = Object.entries(fields).filter(([, v]) => v); - if (fieldEntries.length) { - const fieldPrefix = fieldEntries.map(([k, v]) => { - const tmpl = (DOC_FIELD_TEMPLATES[docType] || []).find(t => t.id === k); - return (tmpl ? tmpl.label + ': ' : k + ': ') + v; - }).join('\n'); - fullContent = fieldPrefix + (content ? '\n\n' + content : ''); - } + const templates = DOC_FIELD_TEMPLATES[docType] || []; + const fieldsObj = {}; + templates.forEach(f => { + const v = fields[f.id]; + if (v) fieldsObj[f.id] = v; + }); const data = { doc_type: docType, team_id: teamId, title: title, - content: fullContent, + content: JSON.stringify({ fields: fieldsObj, notes: notes }), occurred_at: new Date().toISOString().slice(0, 16) }; if (editingDocId) { - data._fields = fields; await apiFetch('documents/' + editingDocId, { method: 'PUT', body: JSON.stringify(data) }); editingDocId = null; } else { @@ -1243,35 +1290,34 @@ async function saveDocument() { loadEvents(); } +function openNewDocument(type) { + editingDocId = null; + document.getElementById('documentModalLabel').textContent = 'New ' + (DOC_TYPE_LABELS[type] || type) + ' Document'; + document.getElementById('saveDocument').innerHTML = ' Save Document'; + document.getElementById('docType').value = type; + document.getElementById('docTitle').value = ''; + document.getElementById('docContent').value = ''; + window._editDocData = null; + updateDocFormFields(); + new bootstrap.Modal(document.getElementById('documentModal')).show(); +} + async function editDocument(id) { const d = documents.find(x => x.id == id); if (!d) return; editingDocId = id; window._editDocData = d; - // Parse structured content back into fields - const docType = d.doc_type; - const templates = DOC_FIELD_TEMPLATES[docType] || []; + let parsedNotes = ''; const parsedFields = {}; if (d.content) { - const lines = d.content.split('\n'); - let inContent = false; - let contentParts = []; - for (const line of lines) { - if (!inContent && line.includes(': ')) { - const colonIdx = line.indexOf(': '); - const fieldLabel = line.substring(0, colonIdx); - const fieldVal = line.substring(colonIdx + 2); - const tmpl = templates.find(t => t.label === fieldLabel); - if (tmpl) { - parsedFields[tmpl.id] = fieldVal; - continue; - } - } - if (!line.trim() && !inContent) { inContent = true; continue; } - if (inContent) contentParts.push(line); + try { + const parsed = JSON.parse(d.content); + if (parsed.fields) Object.assign(parsedFields, parsed.fields); + parsedNotes = parsed.notes || ''; + } catch (e) { + parsedNotes = d.content; } - d._parsedContent = contentParts.join('\n').trim(); } document.getElementById('documentModalLabel').textContent = 'Edit ' + (DOC_TYPE_LABELS[d.doc_type] || d.doc_type) + ' Document'; @@ -1279,8 +1325,9 @@ async function editDocument(id) { document.getElementById('docType').value = d.doc_type; document.getElementById('docTeam').value = d.team_id; document.getElementById('docTitle').value = d.title; - document.getElementById('docContent').value = d._parsedContent || ''; + document.getElementById('docContent').value = parsedNotes; + d._parsedFields = parsedFields; updateDocFormFields(); new bootstrap.Modal(document.getElementById('documentModal')).show(); } diff --git a/frontend/index.html b/frontend/index.html index 40e5b1e..3521e01 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -441,6 +441,25 @@
+ + +