@cccarv82/freya 2.14.1 → 2.15.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.css +303 -2
- package/cli/web-ui.js +306 -7
- package/cli/web.js +376 -10
- package/package.json +2 -2
- package/scripts/lib/DataLayer.js +10 -1
- package/scripts/lib/DataManager.js +1 -0
- package/scripts/lib/Embedder.js +5 -1
- package/templates/base/scripts/lib/DataLayer.js +11 -2
- package/templates/base/scripts/lib/DataManager.js +15 -14
- package/templates/base/scripts/lib/Embedder.js +5 -1
package/cli/web-ui.css
CHANGED
|
@@ -603,12 +603,15 @@ body {
|
|
|
603
603
|
|
|
604
604
|
.promptShell {
|
|
605
605
|
display: flex;
|
|
606
|
-
|
|
606
|
+
flex-direction: column;
|
|
607
|
+
align-items: stretch;
|
|
607
608
|
width: 100%;
|
|
609
|
+
/* Occupy ~90% of viewport height minus topbar */
|
|
610
|
+
min-height: calc(90vh - 60px);
|
|
608
611
|
}
|
|
609
612
|
|
|
610
613
|
.promptBar {
|
|
611
|
-
width:
|
|
614
|
+
width: 100%;
|
|
612
615
|
background: var(--paper);
|
|
613
616
|
backdrop-filter: blur(16px);
|
|
614
617
|
-webkit-backdrop-filter: blur(16px);
|
|
@@ -1612,4 +1615,302 @@ textarea:focus {
|
|
|
1612
1615
|
|
|
1613
1616
|
* {
|
|
1614
1617
|
border-radius: 0 !important;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
/* ── Kanban Board ── */
|
|
1621
|
+
.kanban-board {
|
|
1622
|
+
display: grid;
|
|
1623
|
+
grid-template-columns: repeat(4, 1fr);
|
|
1624
|
+
gap: 12px;
|
|
1625
|
+
min-height: 400px;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
@media (max-width: 1100px) {
|
|
1629
|
+
.kanban-board { grid-template-columns: repeat(2, 1fr); }
|
|
1630
|
+
}
|
|
1631
|
+
@media (max-width: 600px) {
|
|
1632
|
+
.kanban-board { grid-template-columns: 1fr; }
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
.kanban-col {
|
|
1636
|
+
display: flex;
|
|
1637
|
+
flex-direction: column;
|
|
1638
|
+
background: var(--bg);
|
|
1639
|
+
border: 1px solid var(--line2);
|
|
1640
|
+
min-height: 300px;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
.kanban-col-head {
|
|
1644
|
+
display: flex;
|
|
1645
|
+
justify-content: space-between;
|
|
1646
|
+
align-items: center;
|
|
1647
|
+
padding: 10px 14px;
|
|
1648
|
+
font-size: 11px;
|
|
1649
|
+
font-weight: 800;
|
|
1650
|
+
letter-spacing: 1px;
|
|
1651
|
+
text-transform: uppercase;
|
|
1652
|
+
border-bottom: 2px solid var(--line2);
|
|
1653
|
+
flex-shrink: 0;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
.kanban-col-head.do-now { border-bottom-color: #ef4444; color: #ef4444; }
|
|
1657
|
+
.kanban-col-head.schedule { border-bottom-color: #3b82f6; color: #3b82f6; }
|
|
1658
|
+
.kanban-col-head.delegate { border-bottom-color: #f97316; color: #f97316; }
|
|
1659
|
+
.kanban-col-head.done { border-bottom-color: #22c55e; color: #22c55e; }
|
|
1660
|
+
|
|
1661
|
+
.kanban-col-count {
|
|
1662
|
+
font-size: 12px;
|
|
1663
|
+
font-weight: 700;
|
|
1664
|
+
padding: 1px 8px;
|
|
1665
|
+
background: var(--paper2);
|
|
1666
|
+
border: 1px solid var(--line2);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
.kanban-col-body {
|
|
1670
|
+
flex: 1;
|
|
1671
|
+
padding: 8px;
|
|
1672
|
+
display: flex;
|
|
1673
|
+
flex-direction: column;
|
|
1674
|
+
gap: 8px;
|
|
1675
|
+
overflow-y: auto;
|
|
1676
|
+
max-height: 600px;
|
|
1677
|
+
min-height: 80px;
|
|
1678
|
+
transition: background 0.15s;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
.kanban-col-body.drag-over {
|
|
1682
|
+
background: var(--chip);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
.kanban-card {
|
|
1686
|
+
background: var(--paper);
|
|
1687
|
+
border: 1px solid var(--line2);
|
|
1688
|
+
padding: 10px 12px;
|
|
1689
|
+
cursor: grab;
|
|
1690
|
+
transition: box-shadow 0.15s, opacity 0.15s;
|
|
1691
|
+
display: flex;
|
|
1692
|
+
flex-direction: column;
|
|
1693
|
+
gap: 6px;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
.kanban-card:hover {
|
|
1697
|
+
box-shadow: var(--shadow2);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
.kanban-card.dragging {
|
|
1701
|
+
opacity: 0.4;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
.kanban-card-header {
|
|
1705
|
+
display: flex;
|
|
1706
|
+
align-items: flex-start;
|
|
1707
|
+
gap: 8px;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
.kanban-pri-dot {
|
|
1711
|
+
width: 8px;
|
|
1712
|
+
height: 8px;
|
|
1713
|
+
flex-shrink: 0;
|
|
1714
|
+
margin-top: 4px;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
.kanban-card-desc {
|
|
1718
|
+
font-size: 13px;
|
|
1719
|
+
font-weight: 600;
|
|
1720
|
+
color: var(--text);
|
|
1721
|
+
line-height: 1.3;
|
|
1722
|
+
word-break: break-word;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
.kanban-card-meta {
|
|
1726
|
+
display: flex;
|
|
1727
|
+
gap: 6px;
|
|
1728
|
+
flex-wrap: wrap;
|
|
1729
|
+
align-items: center;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
.kanban-tag {
|
|
1733
|
+
font-size: 10px;
|
|
1734
|
+
font-weight: 600;
|
|
1735
|
+
padding: 1px 6px;
|
|
1736
|
+
background: var(--chip);
|
|
1737
|
+
color: var(--primary);
|
|
1738
|
+
border: 1px solid var(--line2);
|
|
1739
|
+
font-family: var(--mono);
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
.kanban-due {
|
|
1743
|
+
font-size: 10px;
|
|
1744
|
+
font-weight: 600;
|
|
1745
|
+
padding: 1px 6px;
|
|
1746
|
+
color: var(--muted);
|
|
1747
|
+
border: 1px solid var(--line2);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
.kanban-due.overdue {
|
|
1751
|
+
background: rgba(239, 68, 68, 0.1);
|
|
1752
|
+
color: #ef4444;
|
|
1753
|
+
border-color: rgba(239, 68, 68, 0.3);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
.kanban-card-actions {
|
|
1757
|
+
display: flex;
|
|
1758
|
+
gap: 4px;
|
|
1759
|
+
justify-content: flex-end;
|
|
1760
|
+
opacity: 0;
|
|
1761
|
+
transition: opacity 0.15s;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
.kanban-card:hover .kanban-card-actions {
|
|
1765
|
+
opacity: 1;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
.kanban-action-btn {
|
|
1769
|
+
background: var(--bg);
|
|
1770
|
+
border: 1px solid var(--line2);
|
|
1771
|
+
color: var(--muted);
|
|
1772
|
+
padding: 2px 8px;
|
|
1773
|
+
font-size: 12px;
|
|
1774
|
+
cursor: pointer;
|
|
1775
|
+
transition: var(--transition);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
.kanban-action-btn:hover {
|
|
1779
|
+
color: var(--text);
|
|
1780
|
+
background: var(--paper2);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
.kanban-action-btn.complete-btn:hover {
|
|
1784
|
+
color: #22c55e;
|
|
1785
|
+
border-color: #22c55e;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
.kanban-owner {
|
|
1789
|
+
font-size: 10px;
|
|
1790
|
+
color: var(--faint);
|
|
1791
|
+
font-style: italic;
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
/* Kanban filter */
|
|
1795
|
+
.kanban-filter {
|
|
1796
|
+
background: var(--bg);
|
|
1797
|
+
border: 1px solid var(--line2);
|
|
1798
|
+
color: var(--text);
|
|
1799
|
+
padding: 5px 10px;
|
|
1800
|
+
font-size: 12px;
|
|
1801
|
+
font-family: var(--mono);
|
|
1802
|
+
min-width: 180px;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
/* Kanban blockers strip */
|
|
1806
|
+
.kanban-blockers-list {
|
|
1807
|
+
display: flex;
|
|
1808
|
+
flex-wrap: wrap;
|
|
1809
|
+
gap: 8px;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
.kanban-blocker-card {
|
|
1813
|
+
display: flex;
|
|
1814
|
+
align-items: center;
|
|
1815
|
+
gap: 8px;
|
|
1816
|
+
padding: 8px 12px;
|
|
1817
|
+
background: rgba(239, 68, 68, 0.04);
|
|
1818
|
+
border: 1px solid rgba(239, 68, 68, 0.15);
|
|
1819
|
+
border-left: 3px solid #ef4444;
|
|
1820
|
+
flex: 1 1 300px;
|
|
1821
|
+
max-width: 500px;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
.kanban-blocker-sev {
|
|
1825
|
+
font-size: 9px;
|
|
1826
|
+
font-weight: 800;
|
|
1827
|
+
letter-spacing: 0.5px;
|
|
1828
|
+
padding: 2px 6px;
|
|
1829
|
+
border: 1px solid;
|
|
1830
|
+
flex-shrink: 0;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
.kanban-blocker-title {
|
|
1834
|
+
font-size: 12px;
|
|
1835
|
+
font-weight: 600;
|
|
1836
|
+
color: var(--text);
|
|
1837
|
+
flex: 1;
|
|
1838
|
+
min-width: 0;
|
|
1839
|
+
overflow: hidden;
|
|
1840
|
+
text-overflow: ellipsis;
|
|
1841
|
+
white-space: nowrap;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
/* ── Quick-Add Modal ── */
|
|
1845
|
+
.qa-overlay {
|
|
1846
|
+
position: fixed;
|
|
1847
|
+
inset: 0;
|
|
1848
|
+
background: rgba(0, 0, 0, 0.5);
|
|
1849
|
+
z-index: 9999;
|
|
1850
|
+
display: flex;
|
|
1851
|
+
align-items: center;
|
|
1852
|
+
justify-content: center;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
.qa-modal {
|
|
1856
|
+
background: var(--bg);
|
|
1857
|
+
border: 1px solid var(--line2);
|
|
1858
|
+
box-shadow: var(--shadow);
|
|
1859
|
+
width: 440px;
|
|
1860
|
+
max-width: 95vw;
|
|
1861
|
+
padding: 16px;
|
|
1862
|
+
display: flex;
|
|
1863
|
+
flex-direction: column;
|
|
1864
|
+
gap: 10px;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
.qa-header {
|
|
1868
|
+
display: flex;
|
|
1869
|
+
justify-content: space-between;
|
|
1870
|
+
align-items: center;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
.qa-input {
|
|
1874
|
+
background: var(--paper);
|
|
1875
|
+
border: 1px solid var(--line2);
|
|
1876
|
+
color: var(--text);
|
|
1877
|
+
padding: 8px 10px;
|
|
1878
|
+
font-size: 13px;
|
|
1879
|
+
font-family: var(--sans);
|
|
1880
|
+
width: 100%;
|
|
1881
|
+
resize: none;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
.qa-input:focus {
|
|
1885
|
+
outline: none;
|
|
1886
|
+
border-color: var(--primary);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
.qa-select {
|
|
1890
|
+
background: var(--paper);
|
|
1891
|
+
border: 1px solid var(--line2);
|
|
1892
|
+
color: var(--text);
|
|
1893
|
+
padding: 6px 10px;
|
|
1894
|
+
font-size: 12px;
|
|
1895
|
+
flex: 1;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
.qa-row {
|
|
1899
|
+
display: flex;
|
|
1900
|
+
gap: 8px;
|
|
1901
|
+
align-items: center;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
/* ── Delta Banner ── */
|
|
1905
|
+
.delta-banner {
|
|
1906
|
+
display: flex;
|
|
1907
|
+
align-items: center;
|
|
1908
|
+
gap: 8px;
|
|
1909
|
+
padding: 8px 14px;
|
|
1910
|
+
margin-bottom: 12px;
|
|
1911
|
+
background: var(--chip);
|
|
1912
|
+
border: 1px solid var(--line2);
|
|
1913
|
+
font-size: 12px;
|
|
1914
|
+
color: var(--text);
|
|
1915
|
+
border-left: 3px solid var(--primary);
|
|
1615
1916
|
}
|
package/cli/web-ui.js
CHANGED
|
@@ -341,6 +341,11 @@
|
|
|
341
341
|
persistChatItem({ ts: Date.now(), role, markdown: !!opts.markdown, text: raw });
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
// show the thread now that it has content
|
|
345
|
+
thread.style.display = 'flex';
|
|
346
|
+
thread.style.padding = '12px';
|
|
347
|
+
thread.style.borderTop = '1px solid var(--border)';
|
|
348
|
+
|
|
344
349
|
// keep newest in view
|
|
345
350
|
try {
|
|
346
351
|
thread.scrollTop = thread.scrollHeight;
|
|
@@ -477,8 +482,8 @@
|
|
|
477
482
|
const thread = $('chatThread');
|
|
478
483
|
if (!thread) return;
|
|
479
484
|
const hasContent = thread.children.length > 0;
|
|
485
|
+
thread.style.display = hasContent ? 'flex' : 'none';
|
|
480
486
|
thread.style.padding = hasContent ? '12px' : '0';
|
|
481
|
-
thread.style.maxHeight = hasContent ? '280px' : '0';
|
|
482
487
|
thread.style.borderTop = hasContent ? '1px solid var(--border)' : 'none';
|
|
483
488
|
}
|
|
484
489
|
|
|
@@ -1431,6 +1436,7 @@
|
|
|
1431
1436
|
const health = $('railCompanion');
|
|
1432
1437
|
const graph = $('railGraph');
|
|
1433
1438
|
const docs = $('railDocs');
|
|
1439
|
+
const kanban = $('railKanban');
|
|
1434
1440
|
|
|
1435
1441
|
const curPage = (document.body && document.body.dataset) ? document.body.dataset.page : null;
|
|
1436
1442
|
const isDashboard = !curPage || curPage === 'dashboard';
|
|
@@ -1460,6 +1466,11 @@
|
|
|
1460
1466
|
if (curPage !== 'companion') window.location.href = '/companion';
|
|
1461
1467
|
};
|
|
1462
1468
|
}
|
|
1469
|
+
if (kanban) {
|
|
1470
|
+
kanban.onclick = () => {
|
|
1471
|
+
if (curPage !== 'kanban') window.location.href = '/kanban';
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1463
1474
|
if (tl) {
|
|
1464
1475
|
tl.onclick = () => {
|
|
1465
1476
|
if (curPage !== 'timeline') window.location.href = '/timeline';
|
|
@@ -2769,6 +2780,7 @@
|
|
|
2769
2780
|
const isTimelinePage = document.body && document.body.dataset && document.body.dataset.page === 'timeline';
|
|
2770
2781
|
const isCompanionPage = document.body && document.body.dataset && document.body.dataset.page === 'companion';
|
|
2771
2782
|
const isGraphPage = document.body && document.body.dataset && document.body.dataset.page === 'graph';
|
|
2783
|
+
const isKanbanPage = document.body && document.body.dataset && document.body.dataset.page === 'kanban';
|
|
2772
2784
|
|
|
2773
2785
|
// Load persisted settings from the workspace + bootstrap (auto-init + auto-health)
|
|
2774
2786
|
(async () => {
|
|
@@ -2819,6 +2831,11 @@
|
|
|
2819
2831
|
return;
|
|
2820
2832
|
}
|
|
2821
2833
|
|
|
2834
|
+
if (isKanbanPage) {
|
|
2835
|
+
await loadKanban();
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2822
2839
|
// If workspace isn't initialized yet, auto-init (reduces clicks)
|
|
2823
2840
|
try {
|
|
2824
2841
|
if (defaults && defaults.workspaceOk === false) {
|
|
@@ -2847,16 +2864,20 @@
|
|
|
2847
2864
|
if (typeof saveAndPlan === 'function') saveAndPlan();
|
|
2848
2865
|
return;
|
|
2849
2866
|
}
|
|
2850
|
-
// Ctrl/Cmd+K:
|
|
2867
|
+
// Ctrl/Cmd+K: Open quick-add modal
|
|
2851
2868
|
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
2852
2869
|
e.preventDefault();
|
|
2853
|
-
|
|
2854
|
-
if (target) target.focus();
|
|
2870
|
+
openQuickAdd();
|
|
2855
2871
|
return;
|
|
2856
2872
|
}
|
|
2857
|
-
// Escape:
|
|
2858
|
-
if (e.key === 'Escape'
|
|
2859
|
-
|
|
2873
|
+
// Escape: Close quick-add modal or blur active element
|
|
2874
|
+
if (e.key === 'Escape') {
|
|
2875
|
+
const overlay = $('quickAddOverlay');
|
|
2876
|
+
if (overlay && overlay.style.display !== 'none') {
|
|
2877
|
+
closeQuickAdd();
|
|
2878
|
+
return;
|
|
2879
|
+
}
|
|
2880
|
+
if (document.activeElement) document.activeElement.blur();
|
|
2860
2881
|
}
|
|
2861
2882
|
});
|
|
2862
2883
|
|
|
@@ -2912,4 +2933,282 @@
|
|
|
2912
2933
|
window.askFreya = askFreya;
|
|
2913
2934
|
window.askFreyaInline = askFreyaInline;
|
|
2914
2935
|
window.askFreyaFromInput = askFreyaFromInput;
|
|
2936
|
+
|
|
2937
|
+
/* ── Quick-Add Modal ── */
|
|
2938
|
+
function openQuickAdd() {
|
|
2939
|
+
const overlay = $('quickAddOverlay');
|
|
2940
|
+
if (!overlay) return;
|
|
2941
|
+
overlay.style.display = 'flex';
|
|
2942
|
+
const desc = $('qaDesc');
|
|
2943
|
+
if (desc) { desc.value = ''; desc.focus(); }
|
|
2944
|
+
var cat = $('qaCat'); if (cat) cat.value = 'DO_NOW';
|
|
2945
|
+
var pri = $('qaPriority'); if (pri) pri.value = '';
|
|
2946
|
+
var slug = $('qaSlug'); if (slug) slug.value = '';
|
|
2947
|
+
var due = $('qaDue'); if (due) due.value = '';
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
function closeQuickAdd() {
|
|
2951
|
+
var overlay = $('quickAddOverlay');
|
|
2952
|
+
if (overlay) overlay.style.display = 'none';
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
async function submitQuickAdd() {
|
|
2956
|
+
var desc = $('qaDesc');
|
|
2957
|
+
var text = desc ? desc.value.trim() : '';
|
|
2958
|
+
if (!text) { showToast('err', 'Descricao obrigatoria'); return; }
|
|
2959
|
+
|
|
2960
|
+
var cat = $('qaCat'); var catVal = cat ? cat.value : 'DO_NOW';
|
|
2961
|
+
var pri = $('qaPriority'); var priVal = pri ? pri.value : '';
|
|
2962
|
+
var slug = $('qaSlug'); var slugVal = slug ? slug.value.trim() : '';
|
|
2963
|
+
var due = $('qaDue'); var dueVal = due ? due.value : '';
|
|
2964
|
+
|
|
2965
|
+
var body = { dir: dirOrDefault(), description: text, category: catVal };
|
|
2966
|
+
if (priVal) body.priority = priVal;
|
|
2967
|
+
if (slugVal) body.projectSlug = slugVal;
|
|
2968
|
+
if (dueVal) body.dueDate = dueVal;
|
|
2969
|
+
|
|
2970
|
+
try {
|
|
2971
|
+
await api('/api/tasks/create', body);
|
|
2972
|
+
closeQuickAdd();
|
|
2973
|
+
showToast('ok', 'Task criada');
|
|
2974
|
+
if (isKanbanPage) await loadKanban();
|
|
2975
|
+
else await refreshToday();
|
|
2976
|
+
} catch (e) {
|
|
2977
|
+
showToast('err', 'Erro ao criar task');
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
window.openQuickAdd = openQuickAdd;
|
|
2982
|
+
window.closeQuickAdd = closeQuickAdd;
|
|
2983
|
+
window.submitQuickAdd = submitQuickAdd;
|
|
2984
|
+
|
|
2985
|
+
/* ── Delta Banner ── */
|
|
2986
|
+
async function loadDelta() {
|
|
2987
|
+
var el = $('kanbanDelta') || $('deltaBanner');
|
|
2988
|
+
if (!el) return;
|
|
2989
|
+
try {
|
|
2990
|
+
var res = await api('/api/summary/delta', { dir: dirOrDefault() });
|
|
2991
|
+
if (!res || !res.delta) { el.style.display = 'none'; return; }
|
|
2992
|
+
var d = res.delta;
|
|
2993
|
+
var parts = [];
|
|
2994
|
+
if (d.completedTasks > 0) parts.push(d.completedTasks + ' concluida(s)');
|
|
2995
|
+
if (d.resolvedBlockers > 0) parts.push(d.resolvedBlockers + ' blocker(s) resolvido(s)');
|
|
2996
|
+
if (d.newTasks > 0) parts.push(d.newTasks + ' nova(s)');
|
|
2997
|
+
if (d.newBlockers > 0) parts.push(d.newBlockers + ' novo(s) blocker(s)');
|
|
2998
|
+
if (d.overdueTasks > 0) parts.push(d.overdueTasks + ' atrasada(s)');
|
|
2999
|
+
if (parts.length === 0) { el.style.display = 'none'; return; }
|
|
3000
|
+
el.style.display = 'flex';
|
|
3001
|
+
el.className = 'delta-banner';
|
|
3002
|
+
el.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></svg>'
|
|
3003
|
+
+ '<span>Ultimas 24h: ' + escapeHtml(parts.join(' \u00b7 ')) + '</span>';
|
|
3004
|
+
} catch { el.style.display = 'none'; }
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
/* ── Kanban Board ── */
|
|
3008
|
+
var _kanbanData = { tasks: [], blockers: [] };
|
|
3009
|
+
|
|
3010
|
+
async function loadKanban() {
|
|
3011
|
+
try {
|
|
3012
|
+
var res = await api('/api/tasks/kanban', { dir: dirOrDefault() });
|
|
3013
|
+
if (!res || !res.ok) return;
|
|
3014
|
+
_kanbanData.tasks = res.tasks || [];
|
|
3015
|
+
_kanbanData.blockers = res.blockers || [];
|
|
3016
|
+
populateKanbanProjects();
|
|
3017
|
+
renderKanban();
|
|
3018
|
+
renderKanbanBlockers();
|
|
3019
|
+
loadDelta();
|
|
3020
|
+
} catch (e) {
|
|
3021
|
+
showToast('err', 'Erro ao carregar kanban');
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
function populateKanbanProjects() {
|
|
3026
|
+
var sel = $('kanbanFilterProject');
|
|
3027
|
+
if (!sel) return;
|
|
3028
|
+
var slugs = new Set();
|
|
3029
|
+
_kanbanData.tasks.forEach(function(t) { if (t.projectSlug) slugs.add(t.projectSlug); });
|
|
3030
|
+
_kanbanData.blockers.forEach(function(b) { if (b.projectSlug) slugs.add(b.projectSlug); });
|
|
3031
|
+
var current = sel.value;
|
|
3032
|
+
sel.innerHTML = '<option value="">Todos os projetos</option>';
|
|
3033
|
+
Array.from(slugs).sort().forEach(function(s) {
|
|
3034
|
+
var opt = document.createElement('option');
|
|
3035
|
+
opt.value = s; opt.textContent = s;
|
|
3036
|
+
sel.appendChild(opt);
|
|
3037
|
+
});
|
|
3038
|
+
sel.value = current;
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
function filterKanban() { renderKanban(); renderKanbanBlockers(); }
|
|
3042
|
+
|
|
3043
|
+
function getFilteredTasks() {
|
|
3044
|
+
var sel = $('kanbanFilterProject');
|
|
3045
|
+
var filter = sel ? sel.value : '';
|
|
3046
|
+
if (!filter) return _kanbanData.tasks;
|
|
3047
|
+
return _kanbanData.tasks.filter(function(t) { return t.projectSlug === filter; });
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
function renderKanban() {
|
|
3051
|
+
var tasks = getFilteredTasks();
|
|
3052
|
+
var today = new Date().toISOString().slice(0, 10);
|
|
3053
|
+
var sevenAgo = new Date(Date.now() - 7 * 86400000).toISOString();
|
|
3054
|
+
|
|
3055
|
+
var cols = {
|
|
3056
|
+
DO_NOW: [], SCHEDULE: [], DELEGATE: [], COMPLETED: []
|
|
3057
|
+
};
|
|
3058
|
+
|
|
3059
|
+
tasks.forEach(function(t) {
|
|
3060
|
+
if (t.status === 'COMPLETED') {
|
|
3061
|
+
if (t.completedAt && t.completedAt >= sevenAgo) cols.COMPLETED.push(t);
|
|
3062
|
+
} else if (t.status === 'PENDING' && cols[t.category]) {
|
|
3063
|
+
cols[t.category].push(t);
|
|
3064
|
+
}
|
|
3065
|
+
});
|
|
3066
|
+
|
|
3067
|
+
var idMap = { DO_NOW: 'colDoNow', SCHEDULE: 'colSchedule', DELEGATE: 'colDelegate', COMPLETED: 'colDone' };
|
|
3068
|
+
var countMap = { DO_NOW: 'countDoNow', SCHEDULE: 'countSchedule', DELEGATE: 'countDelegate', COMPLETED: 'countDone' };
|
|
3069
|
+
|
|
3070
|
+
Object.keys(cols).forEach(function(cat) {
|
|
3071
|
+
var el = $(idMap[cat]);
|
|
3072
|
+
var countEl = $(countMap[cat]);
|
|
3073
|
+
if (countEl) countEl.textContent = cols[cat].length;
|
|
3074
|
+
if (!el) return;
|
|
3075
|
+
el.innerHTML = '';
|
|
3076
|
+
|
|
3077
|
+
cols[cat].forEach(function(t) {
|
|
3078
|
+
var card = document.createElement('div');
|
|
3079
|
+
card.className = 'kanban-card';
|
|
3080
|
+
card.draggable = (cat !== 'COMPLETED');
|
|
3081
|
+
card.dataset.taskId = t.id;
|
|
3082
|
+
card.dataset.category = cat;
|
|
3083
|
+
|
|
3084
|
+
var pc = priColor(t.priority);
|
|
3085
|
+
var isOverdue = t.dueDate && t.dueDate < today && t.status === 'PENDING';
|
|
3086
|
+
|
|
3087
|
+
var html = '<div class="kanban-card-header">'
|
|
3088
|
+
+ '<span class="kanban-pri-dot" style="background:' + pc.dot + ';" title="' + escapeHtml(pc.label) + '"></span>'
|
|
3089
|
+
+ '<span class="kanban-card-desc">' + escapeHtml(t.description || '') + '</span>'
|
|
3090
|
+
+ '</div>';
|
|
3091
|
+
|
|
3092
|
+
var meta = [];
|
|
3093
|
+
if (t.projectSlug) meta.push('<span class="kanban-tag">' + escapeHtml(t.projectSlug) + '</span>');
|
|
3094
|
+
if (t.dueDate) {
|
|
3095
|
+
var dueCls = isOverdue ? 'kanban-due overdue' : 'kanban-due';
|
|
3096
|
+
meta.push('<span class="' + dueCls + '">' + escapeHtml(t.dueDate) + '</span>');
|
|
3097
|
+
}
|
|
3098
|
+
if (meta.length) html += '<div class="kanban-card-meta">' + meta.join('') + '</div>';
|
|
3099
|
+
|
|
3100
|
+
if (cat !== 'COMPLETED') {
|
|
3101
|
+
html += '<div class="kanban-card-actions">'
|
|
3102
|
+
+ '<button class="kanban-action-btn complete-btn" title="Concluir">\u2713</button>'
|
|
3103
|
+
+ '<button class="kanban-action-btn edit-btn" title="Editar">\u270E</button>'
|
|
3104
|
+
+ '</div>';
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
card.innerHTML = html;
|
|
3108
|
+
|
|
3109
|
+
// Drag events
|
|
3110
|
+
if (cat !== 'COMPLETED') {
|
|
3111
|
+
card.addEventListener('dragstart', function(e) {
|
|
3112
|
+
e.dataTransfer.setData('text/plain', t.id);
|
|
3113
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
3114
|
+
card.classList.add('dragging');
|
|
3115
|
+
});
|
|
3116
|
+
card.addEventListener('dragend', function() {
|
|
3117
|
+
card.classList.remove('dragging');
|
|
3118
|
+
});
|
|
3119
|
+
|
|
3120
|
+
// Complete button
|
|
3121
|
+
var completeBtn = card.querySelector('.complete-btn');
|
|
3122
|
+
if (completeBtn) {
|
|
3123
|
+
completeBtn.onclick = async function() {
|
|
3124
|
+
try {
|
|
3125
|
+
await api('/api/tasks/complete', { dir: dirOrDefault(), id: t.id });
|
|
3126
|
+
showToast('ok', 'Concluida');
|
|
3127
|
+
await loadKanban();
|
|
3128
|
+
} catch { showToast('err', 'Falhou'); }
|
|
3129
|
+
};
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
// Edit button - inline edit via prompt
|
|
3133
|
+
var editBtn = card.querySelector('.edit-btn');
|
|
3134
|
+
if (editBtn) {
|
|
3135
|
+
editBtn.onclick = async function() {
|
|
3136
|
+
var newCat = prompt('Categoria (DO_NOW|SCHEDULE|DELEGATE):', cat);
|
|
3137
|
+
if (!newCat) return;
|
|
3138
|
+
var newSlug = prompt('Projeto (slug):', t.projectSlug || '');
|
|
3139
|
+
if (newSlug === null) return;
|
|
3140
|
+
var newDue = prompt('Due date (YYYY-MM-DD):', t.dueDate || '');
|
|
3141
|
+
if (newDue === null) return;
|
|
3142
|
+
try {
|
|
3143
|
+
await api('/api/tasks/update', {
|
|
3144
|
+
dir: dirOrDefault(), id: t.id,
|
|
3145
|
+
patch: { category: newCat, projectSlug: newSlug, dueDate: newDue || null }
|
|
3146
|
+
});
|
|
3147
|
+
showToast('ok', 'Atualizada');
|
|
3148
|
+
await loadKanban();
|
|
3149
|
+
} catch { showToast('err', 'Falhou'); }
|
|
3150
|
+
};
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
el.appendChild(card);
|
|
3155
|
+
});
|
|
3156
|
+
|
|
3157
|
+
// Drop zone
|
|
3158
|
+
if (cat !== 'COMPLETED') {
|
|
3159
|
+
el.addEventListener('dragover', function(e) {
|
|
3160
|
+
e.preventDefault();
|
|
3161
|
+
e.dataTransfer.dropEffect = 'move';
|
|
3162
|
+
el.classList.add('drag-over');
|
|
3163
|
+
});
|
|
3164
|
+
el.addEventListener('dragleave', function() {
|
|
3165
|
+
el.classList.remove('drag-over');
|
|
3166
|
+
});
|
|
3167
|
+
el.addEventListener('drop', async function(e) {
|
|
3168
|
+
e.preventDefault();
|
|
3169
|
+
el.classList.remove('drag-over');
|
|
3170
|
+
var taskId = e.dataTransfer.getData('text/plain');
|
|
3171
|
+
if (!taskId) return;
|
|
3172
|
+
try {
|
|
3173
|
+
await api('/api/tasks/update', {
|
|
3174
|
+
dir: dirOrDefault(), id: taskId,
|
|
3175
|
+
patch: { category: cat }
|
|
3176
|
+
});
|
|
3177
|
+
showToast('ok', 'Movida para ' + cat);
|
|
3178
|
+
await loadKanban();
|
|
3179
|
+
} catch { showToast('err', 'Falhou'); }
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
function renderKanbanBlockers() {
|
|
3186
|
+
var wrap = $('kanbanBlockers');
|
|
3187
|
+
var list = $('kanbanBlockersList');
|
|
3188
|
+
if (!wrap || !list) return;
|
|
3189
|
+
|
|
3190
|
+
var sel = $('kanbanFilterProject');
|
|
3191
|
+
var filter = sel ? sel.value : '';
|
|
3192
|
+
var blockers = _kanbanData.blockers;
|
|
3193
|
+
if (filter) blockers = blockers.filter(function(b) { return b.projectSlug === filter; });
|
|
3194
|
+
|
|
3195
|
+
if (blockers.length === 0) { wrap.style.display = 'none'; return; }
|
|
3196
|
+
wrap.style.display = 'block';
|
|
3197
|
+
list.innerHTML = '';
|
|
3198
|
+
|
|
3199
|
+
blockers.forEach(function(b) {
|
|
3200
|
+
var card = document.createElement('div');
|
|
3201
|
+
card.className = 'kanban-blocker-card';
|
|
3202
|
+
var color = sevColor(b.severity);
|
|
3203
|
+
card.innerHTML = '<span class="kanban-blocker-sev" style="background:' + color + '22; color:' + color + '; border-color:' + color + '44;">' + escapeHtml(b.severity || 'BLOCKER') + '</span>'
|
|
3204
|
+
+ '<span class="kanban-blocker-title">' + escapeHtml(b.title || '') + '</span>'
|
|
3205
|
+
+ (b.projectSlug ? '<span class="kanban-tag">' + escapeHtml(b.projectSlug) + '</span>' : '')
|
|
3206
|
+
+ (b.owner ? '<span class="kanban-owner">' + escapeHtml(b.owner) + '</span>' : '');
|
|
3207
|
+
list.appendChild(card);
|
|
3208
|
+
});
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
window.loadKanban = loadKanban;
|
|
3212
|
+
window.filterKanban = filterKanban;
|
|
3213
|
+
window.loadDelta = loadDelta;
|
|
2915
3214
|
})();
|