@cccarv82/freya 2.2.0 → 2.3.0
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 +227 -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
|
@@ -877,6 +877,11 @@ function reportsHtml(defaultDir) {
|
|
|
877
877
|
return buildReportsHtml(safeDefault, APP_VERSION);
|
|
878
878
|
}
|
|
879
879
|
|
|
880
|
+
function projectsHtml(defaultDir) {
|
|
881
|
+
const safeDefault = String(defaultDir || './freya').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
882
|
+
return buildProjectsHtml(safeDefault, APP_VERSION);
|
|
883
|
+
}
|
|
884
|
+
|
|
880
885
|
function companionHtml(defaultDir) {
|
|
881
886
|
const safeDefault = String(defaultDir || './freya').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
882
887
|
return buildCompanionHtml(safeDefault, APP_VERSION);
|
|
@@ -906,6 +911,7 @@ function buildHtml(safeDefault, appVersion) {
|
|
|
906
911
|
<button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
|
|
907
912
|
<button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
|
|
908
913
|
<button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
|
|
914
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
|
|
909
915
|
</div>
|
|
910
916
|
<div class="railBottom">
|
|
911
917
|
<div class="railStatus" id="railStatus" title="status"></div>
|
|
@@ -1157,6 +1163,7 @@ function buildReportsHtml(safeDefault, appVersion) {
|
|
|
1157
1163
|
<button class="railBtn active" id="railReports" type="button" title="Relatórios">R</button>
|
|
1158
1164
|
<button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
|
|
1159
1165
|
<button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
|
|
1166
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
|
|
1160
1167
|
</div>
|
|
1161
1168
|
<div class="railBottom">
|
|
1162
1169
|
<div class="railStatus" id="railStatus" title="status"></div>
|
|
@@ -1235,6 +1242,7 @@ function buildProjectsHtml(safeDefault, appVersion) {
|
|
|
1235
1242
|
<button class="railBtn" id="railReports" type="button" title="Relatorios">R</button>
|
|
1236
1243
|
<button class="railBtn" id="railCompanion" type="button" title="Companion">C</button>
|
|
1237
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>
|
|
1238
1246
|
</div>
|
|
1239
1247
|
<div class="railBottom">
|
|
1240
1248
|
<div class="railStatus" id="railStatus" title="status"></div>
|
|
@@ -1289,6 +1297,85 @@ function buildProjectsHtml(safeDefault, appVersion) {
|
|
|
1289
1297
|
</html>`;
|
|
1290
1298
|
}
|
|
1291
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
|
+
|
|
1292
1379
|
function buildCompanionHtml(safeDefault, appVersion) {
|
|
1293
1380
|
const safeVersion = escapeHtml(appVersion || 'unknown');
|
|
1294
1381
|
return `<!doctype html>
|
|
@@ -1313,6 +1400,7 @@ function buildCompanionHtml(safeDefault, appVersion) {
|
|
|
1313
1400
|
<button class="railBtn" id="railReports" type="button" title="Relatórios">R</button>
|
|
1314
1401
|
<button class="railBtn active" id="railCompanion" type="button" title="Companion">C</button>
|
|
1315
1402
|
<button class="railBtn" id="railProjects" type="button" title="Projects">P</button>
|
|
1403
|
+
<button class="railBtn" id="railTimeline" type="button" title="Timeline">T</button>
|
|
1316
1404
|
</div>
|
|
1317
1405
|
<div class="railBottom">
|
|
1318
1406
|
<div class="railStatus" id="railStatus" title="status"></div>
|
|
@@ -1355,6 +1443,20 @@ function buildCompanionHtml(safeDefault, appVersion) {
|
|
|
1355
1443
|
|
|
1356
1444
|
<section class="reportsGrid" id="healthChecklist"></section>
|
|
1357
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
|
+
|
|
1358
1460
|
<section class="panel" style="margin-top:16px">
|
|
1359
1461
|
<div class="panelHead"><b>Saida</b></div>
|
|
1360
1462
|
<div class="panelBody">
|
|
@@ -1705,6 +1807,14 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
1705
1807
|
return;
|
|
1706
1808
|
}
|
|
1707
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
|
+
|
|
1708
1818
|
if (req.method === 'GET' && req.url === '/app.css') {
|
|
1709
1819
|
const css = fs.readFileSync(path.join(__dirname, 'web-ui.css'), 'utf8');
|
|
1710
1820
|
res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
@@ -1824,6 +1934,79 @@ async function cmdWeb({ port, dir, open, dev }) {
|
|
|
1824
1934
|
return safeJson(res, 200, { ok: true, projects: items });
|
|
1825
1935
|
}
|
|
1826
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
|
+
|
|
1827
2010
|
if (req.url === '/api/reports/list') {
|
|
1828
2011
|
const reports = listReports(workspaceDir);
|
|
1829
2012
|
return safeJson(res, 200, { reports });
|
|
@@ -1887,6 +2070,26 @@ if (req.url === '/api/reports/list') {
|
|
|
1887
2070
|
return safeJson(res, 200, { ok: true, relPath: rel });
|
|
1888
2071
|
}
|
|
1889
2072
|
|
|
2073
|
+
function autoLinkNotes(textInput) {
|
|
2074
|
+
try {
|
|
2075
|
+
const projectsDir = path.join(workspaceDir, 'docs', 'projects');
|
|
2076
|
+
if (!exists(projectsDir)) return '';
|
|
2077
|
+
const files = fs.readdirSync(projectsDir).filter((f) => f.endsWith('.md'));
|
|
2078
|
+
const links = [];
|
|
2079
|
+
for (const f of files) {
|
|
2080
|
+
const name = f.replace('.md', '');
|
|
2081
|
+
const slug = name.toLowerCase();
|
|
2082
|
+
if (textInput.toLowerCase().includes(slug)) links.push(`[[${name}]]`);
|
|
2083
|
+
}
|
|
2084
|
+
if (!links.length) return '';
|
|
2085
|
+
return `
|
|
2086
|
+
|
|
2087
|
+
Links: ${links.join(' ')}`;
|
|
2088
|
+
} catch {
|
|
2089
|
+
return '';
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
|
|
1890
2093
|
if (req.url === '/api/inbox/add') {
|
|
1891
2094
|
const text = String(payload.text || '').trim();
|
|
1892
2095
|
if (!text) return safeJson(res, 400, { error: 'Missing text' });
|
|
@@ -2562,7 +2765,30 @@ if (req.url === '/api/reports/list') {
|
|
|
2562
2765
|
return safeJson(res, 200, { ok: true, items, stats: { pendingTasks, openBlockers, reportsToday, reportsTotal: reports.length } });
|
|
2563
2766
|
}
|
|
2564
2767
|
|
|
2565
|
-
if (req.url === '/api/
|
|
2768
|
+
if (req.url === '/api/incidents') {
|
|
2769
|
+
const p = path.join(workspaceDir, 'docs', 'reports', 'fidelizacao-incident-index.md');
|
|
2770
|
+
if (!exists(p)) return safeJson(res, 200, { ok: true, markdown: '' });
|
|
2771
|
+
const md = fs.readFileSync(p, 'utf8');
|
|
2772
|
+
return safeJson(res, 200, { ok: true, markdown: md });
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
if (req.url === '/api/tasks/heatmap') {
|
|
2776
|
+
const file = path.join(workspaceDir, 'data', 'tasks', 'task-log.json');
|
|
2777
|
+
const doc = readJsonOrNull(file) || { tasks: [] };
|
|
2778
|
+
const tasks = Array.isArray(doc.tasks) ? doc.tasks : [];
|
|
2779
|
+
const map = {};
|
|
2780
|
+
for (const t of tasks) {
|
|
2781
|
+
const slug = t.projectSlug || 'unassigned';
|
|
2782
|
+
if (!map[slug]) map[slug] = { total: 0, pending: 0, completed: 0 };
|
|
2783
|
+
map[slug].total++;
|
|
2784
|
+
if (t.status === 'COMPLETED') map[slug].completed++; else map[slug].pending++;
|
|
2785
|
+
}
|
|
2786
|
+
const items = Object.entries(map).map(([slug, v]) => ({ slug, ...v }));
|
|
2787
|
+
items.sort((a, b) => b.total - a.total);
|
|
2788
|
+
return safeJson(res, 200, { ok: true, items });
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
if (req.url === '/api/blockers/summary') {
|
|
2566
2792
|
const file = path.join(workspaceDir, 'data', 'blockers', 'blocker-log.json');
|
|
2567
2793
|
const doc = readJsonOrNull(file) || { schemaVersion: 1, blockers: [] };
|
|
2568
2794
|
const blockers = Array.isArray(doc.blockers) ? doc.blockers : [];
|
package/package.json
CHANGED