@cccarv82/freya 2.2.1 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/web-ui.js +99 -0
- package/cli/web.js +245 -1
- package/package.json +1 -1
package/cli/web-ui.js
CHANGED
|
@@ -698,6 +698,87 @@
|
|
|
698
698
|
}
|
|
699
699
|
}
|
|
700
700
|
|
|
701
|
+
function renderTimeline() {
|
|
702
|
+
const el = $('timelineGrid');
|
|
703
|
+
if (!el) return;
|
|
704
|
+
const filter = String(($('timelineFilter') && $('timelineFilter').value) || '').toLowerCase();
|
|
705
|
+
const items = Array.isArray(state.timeline) ? state.timeline : [];
|
|
706
|
+
const filtered = items.filter((i) => {
|
|
707
|
+
const hay = [i.kind, i.title, i.content, (i.tags || []).join(' ')].join(' ').toLowerCase();
|
|
708
|
+
return !filter || hay.includes(filter);
|
|
709
|
+
});
|
|
710
|
+
el.innerHTML = '';
|
|
711
|
+
for (const it of filtered) {
|
|
712
|
+
const card = document.createElement('div');
|
|
713
|
+
card.className = 'reportCard';
|
|
714
|
+
card.innerHTML = '<div class="reportHead">'
|
|
715
|
+
+ '<div><div class="reportTitle">' + escapeHtml(it.title || 'Evento') + '</div>'
|
|
716
|
+
+ '<div class="reportMeta">' + escapeHtml(it.date || '') + ' · ' + escapeHtml(it.kind || '') + '</div></div>'
|
|
717
|
+
+ '</div>'
|
|
718
|
+
+ '<div class="help" style="margin-top:8px">' + escapeHtml(it.content || '') + '</div>';
|
|
719
|
+
el.appendChild(card);
|
|
720
|
+
}
|
|
721
|
+
if (!filtered.length) {
|
|
722
|
+
const empty = document.createElement('div');
|
|
723
|
+
empty.className = 'help';
|
|
724
|
+
empty.textContent = 'Nenhum evento encontrado.';
|
|
725
|
+
el.appendChild(empty);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async function refreshTimeline() {
|
|
730
|
+
try {
|
|
731
|
+
const r = await api('/api/timeline', { dir: dirOrDefault() });
|
|
732
|
+
state.timeline = r.items || [];
|
|
733
|
+
renderTimeline();
|
|
734
|
+
} catch (e) {
|
|
735
|
+
const el = $('timelineGrid');
|
|
736
|
+
if (el) el.textContent = 'Falha ao carregar timeline.';
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
async function refreshIncidents() {
|
|
741
|
+
try {
|
|
742
|
+
const r = await api('/api/incidents', { dir: dirOrDefault() });
|
|
743
|
+
const el = $('incidentsBox');
|
|
744
|
+
if (el) {
|
|
745
|
+
const md = r.markdown || '';
|
|
746
|
+
el.innerHTML = md ? renderMarkdown(md) : '<div class="help">Nenhum incidente registrado.</div>';
|
|
747
|
+
}
|
|
748
|
+
} catch {
|
|
749
|
+
const el = $('incidentsBox');
|
|
750
|
+
if (el) el.textContent = 'Falha ao carregar incidentes.';
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
async function refreshHeatmap() {
|
|
755
|
+
try {
|
|
756
|
+
const r = await api('/api/tasks/heatmap', { dir: dirOrDefault() });
|
|
757
|
+
const el = $('heatmapGrid');
|
|
758
|
+
if (!el) return;
|
|
759
|
+
el.innerHTML = '';
|
|
760
|
+
const items = r.items || [];
|
|
761
|
+
for (const it of items) {
|
|
762
|
+
const row = document.createElement('div');
|
|
763
|
+
row.className = 'rep';
|
|
764
|
+
row.innerHTML = '<div style="display:flex; justify-content:space-between; gap:10px; align-items:center">'
|
|
765
|
+
+ '<div style="min-width:0"><div style="font-weight:800">' + escapeHtml(it.slug || 'unassigned') + '</div>'
|
|
766
|
+
+ '<div class="help" style="margin-top:4px">Total: ' + escapeHtml(String(it.total)) + ' · Pendentes: ' + escapeHtml(String(it.pending)) + ' · Concluidas: ' + escapeHtml(String(it.completed)) + '</div></div>'
|
|
767
|
+
+ '</div>';
|
|
768
|
+
el.appendChild(row);
|
|
769
|
+
}
|
|
770
|
+
if (!items.length) {
|
|
771
|
+
const empty = document.createElement('div');
|
|
772
|
+
empty.className = 'help';
|
|
773
|
+
empty.textContent = 'Sem dados de tasks.';
|
|
774
|
+
el.appendChild(empty);
|
|
775
|
+
}
|
|
776
|
+
} catch {
|
|
777
|
+
const el = $('heatmapGrid');
|
|
778
|
+
if (el) el.textContent = 'Falha ao carregar heatmap.';
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
701
782
|
async function refreshReportsPage() {
|
|
702
783
|
try {
|
|
703
784
|
setPill('run', 'carregando…');
|
|
@@ -723,6 +804,7 @@
|
|
|
723
804
|
const dash = $('railDashboard');
|
|
724
805
|
const rep = $('railReports');
|
|
725
806
|
const proj = $('railProjects');
|
|
807
|
+
const tl = $('railTimeline');
|
|
726
808
|
const health = $('railCompanion');
|
|
727
809
|
if (dash) {
|
|
728
810
|
dash.onclick = () => {
|
|
@@ -747,6 +829,12 @@
|
|
|
747
829
|
if (!isProjects) window.location.href = '/projects';
|
|
748
830
|
};
|
|
749
831
|
}
|
|
832
|
+
if (tl) {
|
|
833
|
+
tl.onclick = () => {
|
|
834
|
+
const isTimeline = document.body && document.body.dataset && document.body.dataset.page === 'timeline';
|
|
835
|
+
if (!isTimeline) window.location.href = '/timeline';
|
|
836
|
+
};
|
|
837
|
+
}
|
|
750
838
|
if (health) {
|
|
751
839
|
health.onclick = () => {
|
|
752
840
|
const isHealth = document.body && document.body.dataset && document.body.dataset.page === 'companion';
|
|
@@ -1364,6 +1452,7 @@
|
|
|
1364
1452
|
|
|
1365
1453
|
const isReportsPage = document.body && document.body.dataset && document.body.dataset.page === 'reports';
|
|
1366
1454
|
const isProjectsPage = document.body && document.body.dataset && document.body.dataset.page === 'projects';
|
|
1455
|
+
const isTimelinePage = document.body && document.body.dataset && document.body.dataset.page === 'timeline';
|
|
1367
1456
|
const isCompanionPage = document.body && document.body.dataset && document.body.dataset.page === 'companion';
|
|
1368
1457
|
|
|
1369
1458
|
// Load persisted settings from the workspace + bootstrap (auto-init + auto-health)
|
|
@@ -1397,8 +1486,15 @@
|
|
|
1397
1486
|
return;
|
|
1398
1487
|
}
|
|
1399
1488
|
|
|
1489
|
+
if (isTimelinePage) {
|
|
1490
|
+
await refreshTimeline();
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1400
1494
|
if (isCompanionPage) {
|
|
1401
1495
|
await refreshHealthChecklist();
|
|
1496
|
+
await refreshIncidents();
|
|
1497
|
+
await refreshHeatmap();
|
|
1402
1498
|
return;
|
|
1403
1499
|
}
|
|
1404
1500
|
|
|
@@ -1441,6 +1537,9 @@
|
|
|
1441
1537
|
window.renderReportsPage = renderReportsPage;
|
|
1442
1538
|
window.refreshReportsPage = refreshReportsPage;
|
|
1443
1539
|
window.refreshProjects = refreshProjects;
|
|
1540
|
+
window.refreshTimeline = refreshTimeline;
|
|
1541
|
+
window.refreshIncidents = refreshIncidents;
|
|
1542
|
+
window.refreshHeatmap = refreshHeatmap;
|
|
1444
1543
|
window.refreshBlockersInsights = refreshBlockersInsights;
|
|
1445
1544
|
window.refreshHealthChecklist = refreshHealthChecklist;
|
|
1446
1545
|
window.copyOut = copyOut;
|
package/cli/web.js
CHANGED
|
@@ -911,6 +911,7 @@ function buildHtml(safeDefault, appVersion) {
|
|
|
911
911
|
<button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
|
|
912
912
|
<button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
|
|
913
913
|
<button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
|
|
914
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
|
|
914
915
|
</div>
|
|
915
916
|
<div class="railBottom">
|
|
916
917
|
<div class="railStatus" id="railStatus" title="status"></div>
|
|
@@ -1162,6 +1163,7 @@ function buildReportsHtml(safeDefault, appVersion) {
|
|
|
1162
1163
|
<button class="railBtn active" id="railReports" type="button" title="Relatórios">R</button>
|
|
1163
1164
|
<button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
|
|
1164
1165
|
<button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
|
|
1166
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
|
|
1165
1167
|
</div>
|
|
1166
1168
|
<div class="railBottom">
|
|
1167
1169
|
<div class="railStatus" id="railStatus" title="status"></div>
|
|
@@ -1240,6 +1242,7 @@ function buildProjectsHtml(safeDefault, appVersion) {
|
|
|
1240
1242
|
<button class="railBtn" id="railReports" type="button" title="Relatorios">R</button>
|
|
1241
1243
|
<button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
|
|
1242
1244
|
<button class="railBtn active" id="railProjects" type="button" title="Projects">P</button>
|
|
1245
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
|
|
1243
1246
|
</div>
|
|
1244
1247
|
<div class="railBottom">
|
|
1245
1248
|
<div class="railStatus" id="railStatus" title="status"></div>
|
|
@@ -1294,6 +1297,85 @@ function buildProjectsHtml(safeDefault, appVersion) {
|
|
|
1294
1297
|
</html>`;
|
|
1295
1298
|
}
|
|
1296
1299
|
|
|
1300
|
+
function buildTimelineHtml(safeDefault, appVersion) {
|
|
1301
|
+
const safeVersion = escapeHtml(appVersion || 'unknown');
|
|
1302
|
+
return `<!doctype html>
|
|
1303
|
+
<html>
|
|
1304
|
+
<head>
|
|
1305
|
+
<meta charset=\"utf-8\" />
|
|
1306
|
+
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />
|
|
1307
|
+
<title>Timeline</title>
|
|
1308
|
+
<link rel=\"stylesheet\" href=\"/app.css\" />
|
|
1309
|
+
</head>
|
|
1310
|
+
<body data-page=\"timeline\">
|
|
1311
|
+
<div class=\"app\">
|
|
1312
|
+
<div class=\"frame\">
|
|
1313
|
+
<div class=\"shell\">
|
|
1314
|
+
|
|
1315
|
+
<aside class=\"rail\">
|
|
1316
|
+
<div class=\"railTop\">
|
|
1317
|
+
<div class=\"railLogo\">F</div>
|
|
1318
|
+
</div>
|
|
1319
|
+
<div class=\"railNav\">
|
|
1320
|
+
<button class=\"railBtn\" id=\"railDashboard\" type=\"button\" title=\"Dashboard\">D</button>
|
|
1321
|
+
<button class=\"railBtn\" id=\"railReports\" type=\"button\" title=\"Relatorios\">R</button>
|
|
1322
|
+
<button class=\"railBtn\" id=\"railCompanion\" type=\"button\" title=\"Companion\">C</button>
|
|
1323
|
+
<button class=\"railBtn\" id=\"railProjects\" type=\"button\" title=\"Projects\">P</button>
|
|
1324
|
+
<button class=\"railBtn active\" id=\"railTimeline\" type=\"button\" title=\"Timeline\">T</button>
|
|
1325
|
+
</div>
|
|
1326
|
+
<div class=\"railBottom\">
|
|
1327
|
+
<div class=\"railStatus\" id=\"railStatus\" title=\"status\"></div>
|
|
1328
|
+
</div>
|
|
1329
|
+
</aside>
|
|
1330
|
+
|
|
1331
|
+
<main class=\"center reportsPage\" id=\"timelinePage\">
|
|
1332
|
+
<div class=\"topbar\">
|
|
1333
|
+
<div class=\"brandLine\">
|
|
1334
|
+
<span class=\"spark\"></span>
|
|
1335
|
+
<div class=\"brandStack\">
|
|
1336
|
+
<div class=\"brand\">FREYA</div>
|
|
1337
|
+
<div class=\"brandSub\">Timeline</div>
|
|
1338
|
+
</div>
|
|
1339
|
+
</div>
|
|
1340
|
+
<div class=\"topActions\">
|
|
1341
|
+
<span class=\"chip\" id=\"chipVersion\">v${safeVersion}</span>
|
|
1342
|
+
<span class=\"chip\" id=\"chipPort\">127.0.0.1:3872</span>
|
|
1343
|
+
</div>
|
|
1344
|
+
</div>
|
|
1345
|
+
|
|
1346
|
+
<div class=\"centerBody\">
|
|
1347
|
+
<input id=\"dir\" type=\"hidden\" />
|
|
1348
|
+
|
|
1349
|
+
<section class=\"reportsHeader\">
|
|
1350
|
+
<div>
|
|
1351
|
+
<div class=\"reportsTitle\">Timeline</div>
|
|
1352
|
+
<div class=\"reportsSubtitle\">Eventos do dia a dia, status e tarefas em ordem cronologica.</div>
|
|
1353
|
+
</div>
|
|
1354
|
+
<div class=\"reportsActions\">
|
|
1355
|
+
<button class=\"btn small\" type=\"button\" onclick=\"refreshTimeline()\">Atualizar</button>
|
|
1356
|
+
</div>
|
|
1357
|
+
</section>
|
|
1358
|
+
|
|
1359
|
+
<section class=\"reportsTools\">
|
|
1360
|
+
<input id=\"timelineFilter\" placeholder=\"filtrar (tag, projeto, tipo)\" oninput=\"renderTimeline()\" />
|
|
1361
|
+
</section>
|
|
1362
|
+
|
|
1363
|
+
<section class=\"reportsGrid\" id=\"timelineGrid\"></section>
|
|
1364
|
+
</div>
|
|
1365
|
+
</main>
|
|
1366
|
+
|
|
1367
|
+
</div>
|
|
1368
|
+
</div>
|
|
1369
|
+
</div>
|
|
1370
|
+
|
|
1371
|
+
<script>
|
|
1372
|
+
window.__FREYA_DEFAULT_DIR = \"${safeDefault}\";
|
|
1373
|
+
</script>
|
|
1374
|
+
<script src=\"/app.js\"></script>
|
|
1375
|
+
</body>
|
|
1376
|
+
</html>`;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1297
1379
|
function buildCompanionHtml(safeDefault, appVersion) {
|
|
1298
1380
|
const safeVersion = escapeHtml(appVersion || 'unknown');
|
|
1299
1381
|
return `<!doctype html>
|
|
@@ -1318,6 +1400,7 @@ function buildCompanionHtml(safeDefault, appVersion) {
|
|
|
1318
1400
|
<button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
|
|
1319
1401
|
<button class="railBtn active" id="railCompanion" type="button" title="Companion">C</button>
|
|
1320
1402
|
<button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
|
|
1403
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
|
|
1321
1404
|
</div>
|
|
1322
1405
|
<div class="railBottom">
|
|
1323
1406
|
<div class="railStatus" id="railStatus" title="status"></div>
|
|
@@ -1360,6 +1443,20 @@ function buildCompanionHtml(safeDefault, appVersion) {
|
|
|
1360
1443
|
|
|
1361
1444
|
<section class="reportsGrid" id="healthChecklist"></section>
|
|
1362
1445
|
|
|
1446
|
+
<section class="panel" style="margin-top:16px">
|
|
1447
|
+
<div class="panelHead"><b>Incident Radar</b></div>
|
|
1448
|
+
<div class="panelBody">
|
|
1449
|
+
<div id="incidentsBox" class="log md" style="font-family: var(--sans);"></div>
|
|
1450
|
+
</div>
|
|
1451
|
+
</section>
|
|
1452
|
+
|
|
1453
|
+
<section class="panel" style="margin-top:16px">
|
|
1454
|
+
<div class="panelHead"><b>Task Heatmap</b></div>
|
|
1455
|
+
<div class="panelBody">
|
|
1456
|
+
<div id="heatmapGrid"></div>
|
|
1457
|
+
</div>
|
|
1458
|
+
</section>
|
|
1459
|
+
|
|
1363
1460
|
<section class="panel" style="margin-top:16px">
|
|
1364
1461
|
<div class="panelHead"><b>Saida</b></div>
|
|
1365
1462
|
<div class="panelBody">
|
|
@@ -1710,6 +1807,14 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
1710
1807
|
return;
|
|
1711
1808
|
}
|
|
1712
1809
|
|
|
1810
|
+
if (req.method === 'GET' && req.url === '/timeline') {
|
|
1811
|
+
try { res.__freyaDebug.workspaceDir = normalizeWorkspaceDir(dir || './freya'); } catch {}
|
|
1812
|
+
const body = timelineHtml(dir || './freya');
|
|
1813
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
1814
|
+
res.end(body);
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1713
1818
|
if (req.method === 'GET' && req.url === '/app.css') {
|
|
1714
1819
|
const css = fs.readFileSync(path.join(__dirname, 'web-ui.css'), 'utf8');
|
|
1715
1820
|
res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
@@ -1829,6 +1934,79 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
1829
1934
|
return safeJson(res, 200, { ok: true, projects: items });
|
|
1830
1935
|
}
|
|
1831
1936
|
|
|
1937
|
+
if (req.url === '/api/timeline') {
|
|
1938
|
+
const items = [];
|
|
1939
|
+
const dailyDir = path.join(workspaceDir, 'logs', 'daily');
|
|
1940
|
+
if (exists(dailyDir)) {
|
|
1941
|
+
const files = fs.readdirSync(dailyDir).filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f));
|
|
1942
|
+
for (const f of files) {
|
|
1943
|
+
const date = f.replace('.md', '');
|
|
1944
|
+
const full = path.join(dailyDir, f);
|
|
1945
|
+
const body = fs.readFileSync(full, 'utf8');
|
|
1946
|
+
items.push({ kind: 'daily', date, title: `Daily ${date}`, content: body.slice(0, 500) });
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
const base = path.join(workspaceDir, 'data', 'Clients');
|
|
1950
|
+
if (exists(base)) {
|
|
1951
|
+
const stack = [base];
|
|
1952
|
+
while (stack.length) {
|
|
1953
|
+
const dirp = stack.pop();
|
|
1954
|
+
const entries = fs.readdirSync(dirp, { withFileTypes: true });
|
|
1955
|
+
for (const ent of entries) {
|
|
1956
|
+
const full = path.join(dirp, ent.name);
|
|
1957
|
+
if (ent.isDirectory()) stack.push(full);
|
|
1958
|
+
else if (ent.isFile() && ent.name === 'status.json') {
|
|
1959
|
+
const doc = readJsonOrNull(full) || {};
|
|
1960
|
+
const slug = path.relative(base, path.dirname(full)).replace(/\\/g, '/');
|
|
1961
|
+
const hist = Array.isArray(doc.history) ? doc.history : [];
|
|
1962
|
+
for (const h of hist) {
|
|
1963
|
+
items.push({
|
|
1964
|
+
kind: 'status',
|
|
1965
|
+
date: h.date || '',
|
|
1966
|
+
title: `${doc.project || slug} (${h.type || 'Status'})`,
|
|
1967
|
+
content: h.content || '',
|
|
1968
|
+
tags: h.tags || [],
|
|
1969
|
+
slug
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
const taskFile = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
|
|
1977
|
+
const taskDoc = readJsonOrNull(taskFile) || { tasks: [] };
|
|
1978
|
+
const tasks = Array.isArray(taskDoc.tasks) ? taskDoc.tasks : [];
|
|
1979
|
+
for (const t of tasks) {
|
|
1980
|
+
if (t.createdAt) items.push({ kind: 'task', date: String(t.createdAt).slice(0, 10), title: `Task criada: ${t.description || t.id}`, content: t.projectSlug || '' });
|
|
1981
|
+
if (t.completedAt) items.push({ kind: 'task', date: String(t.completedAt).slice(0, 10), title: `Task concluida: ${t.description || t.id}`, content: t.projectSlug || '' });
|
|
1982
|
+
}
|
|
1983
|
+
items.sort((a, b) => String(b.date || '').localeCompare(String(a.date || '')));
|
|
1984
|
+
return safeJson(res, 200, { ok: true, items });
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
if (req.url === '/api/incidents') {
|
|
1988
|
+
const p = path.join(workspaceDir, 'docs', 'reports', 'fidelizacao-incident-index.md');
|
|
1989
|
+
if (!exists(p)) return safeJson(res, 200, { ok: true, markdown: '' });
|
|
1990
|
+
const md = fs.readFileSync(p, 'utf8');
|
|
1991
|
+
return safeJson(res, 200, { ok: true, markdown: md });
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
if (req.url === '/api/tasks/heatmap') {
|
|
1995
|
+
const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
|
|
1996
|
+
const doc = readJsonOrNull(file) || { tasks: [] };
|
|
1997
|
+
const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
|
|
1998
|
+
const map = {};
|
|
1999
|
+
for (const t of tasks) {
|
|
2000
|
+
const slug = t.projectSlug || 'unassigned';
|
|
2001
|
+
if (!map[slug]) map[slug] = { total: 0, pending: 0, completed: 0 };
|
|
2002
|
+
map[slug].total++;
|
|
2003
|
+
if (t.status === 'COMPLETED') map[slug].completed++; else map[slug].pending++;
|
|
2004
|
+
}
|
|
2005
|
+
const items = Object.entries(map).map(([slug, v]) => ({ slug, ...v }));
|
|
2006
|
+
items.sort((a, b) => b.total - a.total);
|
|
2007
|
+
return safeJson(res, 200, { ok: true, items });
|
|
2008
|
+
}
|
|
2009
|
+
|
|
1832
2010
|
if (req.url === '/api/reports/list') {
|
|
1833
2011
|
const reports = listReports(workspaceDir);
|
|
1834
2012
|
return safeJson(res, 200, { reports });
|
|
@@ -1892,6 +2070,49 @@ if (req.url === '/api/reports/list') {
|
|
|
1892
2070
|
return safeJson(res, 200, { ok: true, relPath: rel });
|
|
1893
2071
|
}
|
|
1894
2072
|
|
|
2073
|
+
function autoLinkNotes(textInput) {
|
|
2074
|
+
try {
|
|
2075
|
+
const lc = textInput.toLowerCase();
|
|
2076
|
+
const projectsDir = path.join(workspaceDir, 'docs', 'projects');
|
|
2077
|
+
const links = [];
|
|
2078
|
+
|
|
2079
|
+
if (exists(projectsDir)) {
|
|
2080
|
+
const files = fs.readdirSync(projectsDir).filter((f) => f.endsWith('.md'));
|
|
2081
|
+
for (const f of files) {
|
|
2082
|
+
const name = f.replace('.md', '');
|
|
2083
|
+
const full = path.join(projectsDir, f);
|
|
2084
|
+
const txt = fs.readFileSync(full, 'utf8');
|
|
2085
|
+
const m = txt.match(/DataPath:\s*data\/Clients\/(.+?)\//i);
|
|
2086
|
+
const slug = m ? m[1].toLowerCase() : name.toLowerCase();
|
|
2087
|
+
if (lc.includes(slug)) links.push('[[' + name + ']]');
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
const base = path.join(workspaceDir, 'data', 'Clients');
|
|
2092
|
+
if (exists(base)) {
|
|
2093
|
+
const stack = [base];
|
|
2094
|
+
while (stack.length) {
|
|
2095
|
+
const dirp = stack.pop();
|
|
2096
|
+
const entries = fs.readdirSync(dirp, { withFileTypes: true });
|
|
2097
|
+
for (const ent of entries) {
|
|
2098
|
+
const full = path.join(dirp, ent.name);
|
|
2099
|
+
if (ent.isDirectory()) stack.push(full);
|
|
2100
|
+
else if (ent.isFile() && ent.name === 'status.json') {
|
|
2101
|
+
const slug = path.relative(base, path.dirname(full)).replace(/\\/g, '/').toLowerCase();
|
|
2102
|
+
if (lc.includes(slug)) links.push('[[' + slug + ']]');
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
const uniq = Array.from(new Set(links));
|
|
2109
|
+
if (!uniq.length) return '';
|
|
2110
|
+
return '\n\nLinks: ' + uniq.join(' ');
|
|
2111
|
+
} catch {
|
|
2112
|
+
return '';
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
|
|
1895
2116
|
if (req.url === '/api/inbox/add') {
|
|
1896
2117
|
const text = String(payload.text || '').trim();
|
|
1897
2118
|
if (!text) return safeJson(res, 400, { error: 'Missing text' });
|
|
@@ -2567,7 +2788,30 @@ if (req.url === '/api/reports/list') {
|
|
|
2567
2788
|
return safeJson(res, 200, { ok: true, items, stats: { pendingTasks, openBlockers, reportsToday, reportsTotal: reports.length } });
|
|
2568
2789
|
}
|
|
2569
2790
|
|
|
2570
|
-
if (req.url === '/api/
|
|
2791
|
+
if (req.url === '/api/incidents') {
|
|
2792
|
+
const p = path.join(workspaceDir, 'docs', 'reports', 'fidelizacao-incident-index.md');
|
|
2793
|
+
if (!exists(p)) return safeJson(res, 200, { ok: true, markdown: '' });
|
|
2794
|
+
const md = fs.readFileSync(p, 'utf8');
|
|
2795
|
+
return safeJson(res, 200, { ok: true, markdown: md });
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
if (req.url === '/api/tasks/heatmap') {
|
|
2799
|
+
const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
|
|
2800
|
+
const doc = readJsonOrNull(file) || { tasks: [] };
|
|
2801
|
+
const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
|
|
2802
|
+
const map = {};
|
|
2803
|
+
for (const t of tasks) {
|
|
2804
|
+
const slug = t.projectSlug || 'unassigned';
|
|
2805
|
+
if (!map[slug]) map[slug] = { total: 0, pending: 0, completed: 0 };
|
|
2806
|
+
map[slug].total++;
|
|
2807
|
+
if (t.status === 'COMPLETED') map[slug].completed++; else map[slug].pending++;
|
|
2808
|
+
}
|
|
2809
|
+
const items = Object.entries(map).map(([slug, v]) => ({ slug, ...v }));
|
|
2810
|
+
items.sort((a, b) => b.total - a.total);
|
|
2811
|
+
return safeJson(res, 200, { ok: true, items });
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
if (req.url === '/api/blockers/summary') {
|
|
2571
2815
|
const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
|
|
2572
2816
|
const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
|
|
2573
2817
|
const blockers = Array.isArray(doc.blockers) ? doc.blockers : [];
|
package/package.json
CHANGED