@tenonhq/dovetail-dashboard 0.0.44 → 0.0.45
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/README.md +68 -37
- package/package.json +1 -1
- package/public/app.js +270 -66
- package/public/styles.css +22 -0
- package/server.js +567 -215
package/public/app.js
CHANGED
|
@@ -79,11 +79,16 @@ function renderScopes() {
|
|
|
79
79
|
|
|
80
80
|
scopesData.forEach(function (scope) {
|
|
81
81
|
var card = document.createElement("div");
|
|
82
|
-
card.className =
|
|
82
|
+
card.className =
|
|
83
|
+
"scope-card" + (scope.selected_update_set ? " has-selection" : "");
|
|
83
84
|
card.id = "card-" + scope.scope;
|
|
84
85
|
|
|
85
|
-
var selectedName = scope.selected_update_set
|
|
86
|
-
|
|
86
|
+
var selectedName = scope.selected_update_set
|
|
87
|
+
? scope.selected_update_set.name
|
|
88
|
+
: "None";
|
|
89
|
+
var selectedId = scope.selected_update_set
|
|
90
|
+
? scope.selected_update_set.sys_id
|
|
91
|
+
: "";
|
|
87
92
|
|
|
88
93
|
// Build activate button HTML if there's an active task
|
|
89
94
|
var activateHtml = "";
|
|
@@ -95,8 +100,12 @@ function renderScopes() {
|
|
|
95
100
|
} else {
|
|
96
101
|
activateHtml =
|
|
97
102
|
'<button class="btn-activate" onclick="activateScope(\'' +
|
|
98
|
-
scope.scope +
|
|
99
|
-
"'
|
|
103
|
+
scope.scope +
|
|
104
|
+
"', '" +
|
|
105
|
+
scope.sys_id +
|
|
106
|
+
"')\">Activate " +
|
|
107
|
+
(activeTask.customId || activeTask.taskId) +
|
|
108
|
+
"</button>";
|
|
100
109
|
}
|
|
101
110
|
}
|
|
102
111
|
|
|
@@ -104,30 +113,72 @@ function renderScopes() {
|
|
|
104
113
|
var taskBadgeHtml = "";
|
|
105
114
|
if (activeTask && activeTask.scopes && activeTask.scopes[scope.scope]) {
|
|
106
115
|
taskBadgeHtml =
|
|
107
|
-
'<div class="scope-task-badge">
|
|
116
|
+
'<div class="scope-task-badge">' +
|
|
117
|
+
(activeTask.customId || activeTask.taskId) +
|
|
118
|
+
"</div>";
|
|
108
119
|
}
|
|
109
120
|
|
|
110
121
|
card.innerHTML =
|
|
111
122
|
'<div class="scope-header">' +
|
|
112
|
-
|
|
113
|
-
|
|
123
|
+
'<span class="scope-name">' +
|
|
124
|
+
scope.scope +
|
|
125
|
+
"</span>" +
|
|
126
|
+
'<span class="scope-id">' +
|
|
127
|
+
(scope.sys_id ? scope.sys_id.substring(0, 8) + "..." : "not found") +
|
|
128
|
+
"</span>" +
|
|
129
|
+
"</div>" +
|
|
130
|
+
'<div class="display-name">' +
|
|
131
|
+
scope.display_name +
|
|
114
132
|
"</div>" +
|
|
115
|
-
'<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
133
|
+
'<select class="update-set-select" id="select-' +
|
|
134
|
+
scope.scope +
|
|
135
|
+
'" onchange="onSelectChange(\'' +
|
|
136
|
+
scope.scope +
|
|
137
|
+
"')\">" +
|
|
138
|
+
'<option value="">-- No Update Set --</option>' +
|
|
139
|
+
'<option value="__loading" disabled>Loading...</option>' +
|
|
119
140
|
"</select>" +
|
|
120
141
|
'<div class="card-actions">' +
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
142
|
+
'<button class="btn btn-primary" onclick="openCreateModal(\'' +
|
|
143
|
+
scope.scope +
|
|
144
|
+
"', '" +
|
|
145
|
+
scope.sys_id +
|
|
146
|
+
"')\" " +
|
|
147
|
+
(!scope.sys_id ? "disabled" : "") +
|
|
148
|
+
">New</button>" +
|
|
149
|
+
'<button class="btn btn-danger" id="close-btn-' +
|
|
150
|
+
scope.scope +
|
|
151
|
+
'" onclick="closeUpdateSet(\'' +
|
|
152
|
+
scope.scope +
|
|
153
|
+
"')\" disabled>Close</button>" +
|
|
154
|
+
'<button class="btn btn-clear" id="clear-btn-' +
|
|
155
|
+
scope.scope +
|
|
156
|
+
'" onclick="clearSelection(\'' +
|
|
157
|
+
scope.scope +
|
|
158
|
+
"')\" " +
|
|
159
|
+
(!scope.selected_update_set ? "disabled" : "") +
|
|
160
|
+
">Clear</button>" +
|
|
161
|
+
activateHtml +
|
|
125
162
|
"</div>" +
|
|
126
163
|
'<div class="quick-create">' +
|
|
127
|
-
|
|
128
|
-
|
|
164
|
+
'<input type="text" id="quick-name-' +
|
|
165
|
+
scope.scope +
|
|
166
|
+
'" placeholder="Update set name..." class="quick-create-input" onkeydown="if(event.key===\'Enter\')quickCreateUpdateSet(\'' +
|
|
167
|
+
scope.scope +
|
|
168
|
+
"', '" +
|
|
169
|
+
scope.sys_id +
|
|
170
|
+
"')\" />" +
|
|
171
|
+
'<button class="btn btn-primary btn-small" onclick="quickCreateUpdateSet(\'' +
|
|
172
|
+
scope.scope +
|
|
173
|
+
"', '" +
|
|
174
|
+
scope.sys_id +
|
|
175
|
+
"')\" " +
|
|
176
|
+
(!scope.sys_id ? "disabled" : "") +
|
|
177
|
+
">Create</button>" +
|
|
129
178
|
"</div>" +
|
|
130
|
-
(scope.selected_update_set
|
|
179
|
+
(scope.selected_update_set
|
|
180
|
+
? '<div class="selected-badge">Active for push</div>'
|
|
181
|
+
: "") +
|
|
131
182
|
taskBadgeHtml;
|
|
132
183
|
|
|
133
184
|
grid.appendChild(card);
|
|
@@ -166,7 +217,9 @@ async function loadUpdateSets(scope, selectedId) {
|
|
|
166
217
|
async function onSelectChange(scope) {
|
|
167
218
|
var select = document.getElementById("select-" + scope);
|
|
168
219
|
var sysId = select.value;
|
|
169
|
-
var name = select.options[select.selectedIndex]
|
|
220
|
+
var name = select.options[select.selectedIndex]
|
|
221
|
+
? select.options[select.selectedIndex].textContent
|
|
222
|
+
: "";
|
|
170
223
|
|
|
171
224
|
try {
|
|
172
225
|
await api("POST", "/api/select-update-set", {
|
|
@@ -175,9 +228,13 @@ async function onSelectChange(scope) {
|
|
|
175
228
|
update_set_name: sysId ? name : null,
|
|
176
229
|
});
|
|
177
230
|
|
|
178
|
-
var scopeData = scopesData.find(function (s) {
|
|
231
|
+
var scopeData = scopesData.find(function (s) {
|
|
232
|
+
return s.scope === scope;
|
|
233
|
+
});
|
|
179
234
|
if (scopeData) {
|
|
180
|
-
scopeData.selected_update_set = sysId
|
|
235
|
+
scopeData.selected_update_set = sysId
|
|
236
|
+
? { sys_id: sysId, name: name }
|
|
237
|
+
: null;
|
|
181
238
|
}
|
|
182
239
|
|
|
183
240
|
var card = document.getElementById("card-" + scope);
|
|
@@ -221,8 +278,10 @@ async function closeUpdateSet(scope) {
|
|
|
221
278
|
var sysId = select ? select.value : "";
|
|
222
279
|
if (!sysId) return;
|
|
223
280
|
|
|
224
|
-
var name = select.options[select.selectedIndex]
|
|
225
|
-
|
|
281
|
+
var name = select.options[select.selectedIndex]
|
|
282
|
+
? select.options[select.selectedIndex].textContent
|
|
283
|
+
: "";
|
|
284
|
+
if (!confirm('Close update set "' + name + '"?')) return;
|
|
226
285
|
|
|
227
286
|
try {
|
|
228
287
|
await api("PATCH", "/api/update-set/" + sysId + "/close");
|
|
@@ -233,7 +292,9 @@ async function closeUpdateSet(scope) {
|
|
|
233
292
|
update_set_name: null,
|
|
234
293
|
});
|
|
235
294
|
|
|
236
|
-
var scopeData = scopesData.find(function (s) {
|
|
295
|
+
var scopeData = scopesData.find(function (s) {
|
|
296
|
+
return s.scope === scope;
|
|
297
|
+
});
|
|
237
298
|
if (scopeData) scopeData.selected_update_set = null;
|
|
238
299
|
|
|
239
300
|
toast("Closed: " + name);
|
|
@@ -260,8 +321,12 @@ function openCreateModal(scope, scopeSysId) {
|
|
|
260
321
|
modalScopeKey = scope;
|
|
261
322
|
modalScopeSysId = scopeSysId;
|
|
262
323
|
document.getElementById("modal-scope").value = scope;
|
|
263
|
-
document.getElementById("modal-name").value = activeTask
|
|
264
|
-
|
|
324
|
+
document.getElementById("modal-name").value = activeTask
|
|
325
|
+
? activeTask.updateSetName
|
|
326
|
+
: "";
|
|
327
|
+
document.getElementById("modal-description").value = activeTask
|
|
328
|
+
? activeTask.description
|
|
329
|
+
: "";
|
|
265
330
|
document.getElementById("create-modal").classList.add("active");
|
|
266
331
|
document.getElementById("modal-name").focus();
|
|
267
332
|
}
|
|
@@ -299,7 +364,9 @@ async function createUpdateSet() {
|
|
|
299
364
|
update_set_name: name,
|
|
300
365
|
});
|
|
301
366
|
|
|
302
|
-
var scopeData = scopesData.find(function (s) {
|
|
367
|
+
var scopeData = scopesData.find(function (s) {
|
|
368
|
+
return s.scope === modalScopeKey;
|
|
369
|
+
});
|
|
303
370
|
if (scopeData) {
|
|
304
371
|
scopeData.selected_update_set = { sys_id: newSysId, name: name };
|
|
305
372
|
}
|
|
@@ -366,7 +433,9 @@ async function quickCreateUpdateSet(scope, scopeSysId) {
|
|
|
366
433
|
update_set_name: name,
|
|
367
434
|
});
|
|
368
435
|
|
|
369
|
-
var scopeData = scopesData.find(function (s) {
|
|
436
|
+
var scopeData = scopesData.find(function (s) {
|
|
437
|
+
return s.scope === scope;
|
|
438
|
+
});
|
|
370
439
|
if (scopeData) {
|
|
371
440
|
scopeData.selected_update_set = { sys_id: newSysId, name: name };
|
|
372
441
|
}
|
|
@@ -468,7 +537,10 @@ async function loadClickUpTasks() {
|
|
|
468
537
|
|
|
469
538
|
try {
|
|
470
539
|
var statusParam = activeStatuses.join(",");
|
|
471
|
-
var data = await api(
|
|
540
|
+
var data = await api(
|
|
541
|
+
"GET",
|
|
542
|
+
"/api/clickup/tasks?statuses=" + encodeURIComponent(statusParam)
|
|
543
|
+
);
|
|
472
544
|
clickupTasks = data.byStatus || {};
|
|
473
545
|
|
|
474
546
|
// Merge discovered statuses with what we know
|
|
@@ -505,7 +577,8 @@ function renderStatusFilters() {
|
|
|
505
577
|
container.innerHTML = "";
|
|
506
578
|
availableStatuses.forEach(function (status) {
|
|
507
579
|
var chip = document.createElement("button");
|
|
508
|
-
chip.className =
|
|
580
|
+
chip.className =
|
|
581
|
+
"filter-chip" + (activeStatuses.indexOf(status) !== -1 ? " active" : "");
|
|
509
582
|
chip.textContent = status;
|
|
510
583
|
chip.onclick = function () {
|
|
511
584
|
var idx = activeStatuses.indexOf(status);
|
|
@@ -535,9 +608,12 @@ function filterTasks(tasks) {
|
|
|
535
608
|
}
|
|
536
609
|
if (myTasksOnly && clickupUserId) {
|
|
537
610
|
filtered = filtered.filter(function (task) {
|
|
538
|
-
return
|
|
539
|
-
|
|
540
|
-
|
|
611
|
+
return (
|
|
612
|
+
task.assignees &&
|
|
613
|
+
task.assignees.some(function (a) {
|
|
614
|
+
return String(a.id) === String(clickupUserId);
|
|
615
|
+
})
|
|
616
|
+
);
|
|
541
617
|
});
|
|
542
618
|
}
|
|
543
619
|
return filtered;
|
|
@@ -571,7 +647,9 @@ function renderTaskList() {
|
|
|
571
647
|
|
|
572
648
|
tasks.forEach(function (task) {
|
|
573
649
|
var card = document.createElement("div");
|
|
574
|
-
card.className =
|
|
650
|
+
card.className =
|
|
651
|
+
"task-card" +
|
|
652
|
+
(activeTask && activeTask.taskId === task.id ? " selected" : "");
|
|
575
653
|
card.onclick = function () {
|
|
576
654
|
selectTask(task);
|
|
577
655
|
};
|
|
@@ -579,25 +657,44 @@ function renderTaskList() {
|
|
|
579
657
|
var priorityHtml = "";
|
|
580
658
|
if (task.priority) {
|
|
581
659
|
// Tenon tertiary + status palette (see tokens.css)
|
|
582
|
-
var colors = {
|
|
660
|
+
var colors = {
|
|
661
|
+
urgent: "#d92b2b",
|
|
662
|
+
high: "#f16b19",
|
|
663
|
+
normal: "#f4dc00",
|
|
664
|
+
low: "#76d6ff",
|
|
665
|
+
};
|
|
583
666
|
var color = colors[task.priority] || "#9caaa1";
|
|
584
|
-
priorityHtml =
|
|
667
|
+
priorityHtml =
|
|
668
|
+
'<span class="task-priority-dot" style="background:' +
|
|
669
|
+
color +
|
|
670
|
+
'"></span>';
|
|
585
671
|
}
|
|
586
672
|
|
|
587
673
|
var assigneeHtml = "";
|
|
588
674
|
if (task.assignees && task.assignees.length > 0) {
|
|
589
|
-
var initials = task.assignees
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
675
|
+
var initials = task.assignees
|
|
676
|
+
.map(function (a) {
|
|
677
|
+
return (
|
|
678
|
+
a.initials || (a.username || "?").substring(0, 2).toUpperCase()
|
|
679
|
+
);
|
|
680
|
+
})
|
|
681
|
+
.join(", ");
|
|
682
|
+
assigneeHtml =
|
|
683
|
+
'<span class="task-card-assignee">' +
|
|
684
|
+
escapeHtml(initials) +
|
|
685
|
+
"</span>";
|
|
593
686
|
}
|
|
594
687
|
|
|
595
688
|
card.innerHTML =
|
|
596
|
-
'<div class="task-card-name">' +
|
|
689
|
+
'<div class="task-card-name">' +
|
|
690
|
+
escapeHtml(task.name) +
|
|
691
|
+
"</div>" +
|
|
597
692
|
'<div class="task-card-meta">' +
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
693
|
+
'<span class="task-card-id">' +
|
|
694
|
+
(task.customId || task.id) +
|
|
695
|
+
"</span>" +
|
|
696
|
+
priorityHtml +
|
|
697
|
+
assigneeHtml +
|
|
601
698
|
"</div>";
|
|
602
699
|
|
|
603
700
|
group.appendChild(card);
|
|
@@ -607,7 +704,8 @@ function renderTaskList() {
|
|
|
607
704
|
});
|
|
608
705
|
|
|
609
706
|
if (totalVisible === 0) {
|
|
610
|
-
container.innerHTML =
|
|
707
|
+
container.innerHTML =
|
|
708
|
+
'<div class="sidebar-loading">No matching tasks</div>';
|
|
611
709
|
}
|
|
612
710
|
}
|
|
613
711
|
|
|
@@ -623,6 +721,7 @@ async function selectTask(task) {
|
|
|
623
721
|
try {
|
|
624
722
|
var data = await api("POST", "/api/clickup/select-task", {
|
|
625
723
|
taskId: task.id,
|
|
724
|
+
customId: task.customId || "",
|
|
626
725
|
taskName: task.name,
|
|
627
726
|
taskDescription: task.description || "",
|
|
628
727
|
taskUrl: task.url || "",
|
|
@@ -676,14 +775,22 @@ function renderActiveTaskBanner() {
|
|
|
676
775
|
banner.style.display = "";
|
|
677
776
|
|
|
678
777
|
var scopeKeys = activeTask.scopes ? Object.keys(activeTask.scopes) : [];
|
|
679
|
-
var scopeText =
|
|
680
|
-
|
|
681
|
-
|
|
778
|
+
var scopeText =
|
|
779
|
+
scopeKeys.length > 0
|
|
780
|
+
? "<span>" + scopeKeys.join(", ") + "</span>"
|
|
781
|
+
: "None activated yet";
|
|
682
782
|
|
|
683
783
|
banner.innerHTML =
|
|
684
|
-
'<div class="active-task-name">' +
|
|
685
|
-
|
|
686
|
-
|
|
784
|
+
'<div class="active-task-name">' +
|
|
785
|
+
escapeHtml(activeTask.taskName) +
|
|
786
|
+
"</div>" +
|
|
787
|
+
'<div class="active-task-us-name">' +
|
|
788
|
+
escapeHtml(activeTask.updateSetName) +
|
|
789
|
+
"</div>" +
|
|
790
|
+
'<div class="active-task-scopes">Scopes: ' +
|
|
791
|
+
scopeText +
|
|
792
|
+
"</div>" +
|
|
793
|
+
'<button class="btn-start-task" onclick="startTask()">Start Task</button>' +
|
|
687
794
|
'<button class="btn-deselect" onclick="deselectTask()">Deselect</button>';
|
|
688
795
|
}
|
|
689
796
|
|
|
@@ -698,7 +805,7 @@ function renderActiveTaskChip() {
|
|
|
698
805
|
}
|
|
699
806
|
|
|
700
807
|
chip.style.display = "";
|
|
701
|
-
chip.textContent =
|
|
808
|
+
chip.textContent = activeTask.customId || activeTask.taskId;
|
|
702
809
|
chip.title = activeTask.updateSetName;
|
|
703
810
|
chip.onclick = function () {
|
|
704
811
|
if (!sidebarOpen) toggleSidebar();
|
|
@@ -736,7 +843,9 @@ async function activateScope(scope, scopeSysId) {
|
|
|
736
843
|
activeTask.scopes[scope] = { sys_id: us.sys_id, name: us.name };
|
|
737
844
|
|
|
738
845
|
// Update scopesData so the dropdown reflects the new selection
|
|
739
|
-
var scopeData = scopesData.find(function (s) {
|
|
846
|
+
var scopeData = scopesData.find(function (s) {
|
|
847
|
+
return s.scope === scope;
|
|
848
|
+
});
|
|
740
849
|
if (scopeData) {
|
|
741
850
|
scopeData.selected_update_set = { sys_id: us.sys_id, name: us.name };
|
|
742
851
|
}
|
|
@@ -748,7 +857,80 @@ async function activateScope(scope, scopeSysId) {
|
|
|
748
857
|
toast("Failed to activate scope: " + e.message, "error");
|
|
749
858
|
if (activateBtn) {
|
|
750
859
|
activateBtn.disabled = false;
|
|
751
|
-
activateBtn.textContent =
|
|
860
|
+
activateBtn.textContent =
|
|
861
|
+
"Activate " + (activeTask.customId || activeTask.taskId);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// --- Start task: ClickUp status -> in progress, per-scope update sets, branch ---
|
|
867
|
+
|
|
868
|
+
async function startTask() {
|
|
869
|
+
if (!activeTask) return;
|
|
870
|
+
|
|
871
|
+
var btn = document.querySelector(".btn-start-task");
|
|
872
|
+
if (btn) {
|
|
873
|
+
btn.disabled = true;
|
|
874
|
+
btn.textContent = "Starting...";
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
try {
|
|
878
|
+
var data = await api("POST", "/api/clickup/start-task");
|
|
879
|
+
var scopeResults = data.scopes || [];
|
|
880
|
+
|
|
881
|
+
if (data.activeTask) {
|
|
882
|
+
activeTask = data.activeTask;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
var created = 0;
|
|
886
|
+
var found = 0;
|
|
887
|
+
var errors = 0;
|
|
888
|
+
scopeResults.forEach(function (r) {
|
|
889
|
+
if (r.error) {
|
|
890
|
+
errors++;
|
|
891
|
+
} else if (r.created) {
|
|
892
|
+
created++;
|
|
893
|
+
} else {
|
|
894
|
+
found++;
|
|
895
|
+
}
|
|
896
|
+
var scopeData = scopesData.find(function (s) {
|
|
897
|
+
return s.scope === r.scope;
|
|
898
|
+
});
|
|
899
|
+
if (scopeData && r.update_set) {
|
|
900
|
+
scopeData.selected_update_set = {
|
|
901
|
+
sys_id: r.update_set.sys_id,
|
|
902
|
+
name: r.update_set.name,
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
var parts = ["status: in progress"];
|
|
908
|
+
var setParts = [];
|
|
909
|
+
if (created > 0) setParts.push(created + " created");
|
|
910
|
+
if (found > 0) setParts.push(found + " found");
|
|
911
|
+
if (errors > 0) setParts.push(errors + " failed");
|
|
912
|
+
if (setParts.length) parts.push("update sets: " + setParts.join(", "));
|
|
913
|
+
|
|
914
|
+
if (data.branch && data.branch.branch) {
|
|
915
|
+
parts.push(
|
|
916
|
+
(data.branch.created ? "branch created: " : "switched to branch: ") +
|
|
917
|
+
data.branch.branch
|
|
918
|
+
);
|
|
919
|
+
} else if (data.branch && data.branch.error) {
|
|
920
|
+
parts.push("branch failed: " + data.branch.error);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
toast(parts.join(" — "));
|
|
924
|
+
|
|
925
|
+
renderActiveTaskBanner();
|
|
926
|
+
renderScopes();
|
|
927
|
+
} catch (e) {
|
|
928
|
+
toast("Failed to start task: " + e.message, "error");
|
|
929
|
+
} finally {
|
|
930
|
+
var refreshedBtn = document.querySelector(".btn-start-task");
|
|
931
|
+
if (refreshedBtn) {
|
|
932
|
+
refreshedBtn.disabled = false;
|
|
933
|
+
refreshedBtn.textContent = "Start Task";
|
|
752
934
|
}
|
|
753
935
|
}
|
|
754
936
|
}
|
|
@@ -793,9 +975,14 @@ async function autoActivateAllScopes() {
|
|
|
793
975
|
}
|
|
794
976
|
|
|
795
977
|
// Update scopesData
|
|
796
|
-
var scopeData = scopesData.find(function (s) {
|
|
978
|
+
var scopeData = scopesData.find(function (s) {
|
|
979
|
+
return s.scope === r.scope;
|
|
980
|
+
});
|
|
797
981
|
if (scopeData) {
|
|
798
|
-
scopeData.selected_update_set = {
|
|
982
|
+
scopeData.selected_update_set = {
|
|
983
|
+
sys_id: r.update_set.sys_id,
|
|
984
|
+
name: r.update_set.name,
|
|
985
|
+
};
|
|
799
986
|
}
|
|
800
987
|
});
|
|
801
988
|
|
|
@@ -879,7 +1066,10 @@ async function loadRecentEdits() {
|
|
|
879
1066
|
);
|
|
880
1067
|
});
|
|
881
1068
|
if (recentSave) {
|
|
882
|
-
toast(
|
|
1069
|
+
toast(
|
|
1070
|
+
edit.name + " was already saved less than 30 minutes ago.",
|
|
1071
|
+
"warn"
|
|
1072
|
+
);
|
|
883
1073
|
}
|
|
884
1074
|
}
|
|
885
1075
|
});
|
|
@@ -893,7 +1083,10 @@ async function loadRecentEdits() {
|
|
|
893
1083
|
|
|
894
1084
|
async function dismissRecentEdit(sys_id, tableName) {
|
|
895
1085
|
try {
|
|
896
|
-
await api("POST", "/api/recent-edits/dismiss", {
|
|
1086
|
+
await api("POST", "/api/recent-edits/dismiss", {
|
|
1087
|
+
sys_id: sys_id,
|
|
1088
|
+
tableName: tableName,
|
|
1089
|
+
});
|
|
897
1090
|
recentEdits = recentEdits.filter(function (e) {
|
|
898
1091
|
return !(e.sys_id === sys_id && e.tableName === tableName);
|
|
899
1092
|
});
|
|
@@ -951,8 +1144,10 @@ function renderRecentEdits() {
|
|
|
951
1144
|
var updateSetEl = document.createElement("span");
|
|
952
1145
|
var isDefault = edit.updateSet.toLowerCase().indexOf("default") !== -1;
|
|
953
1146
|
var expected = expectedUpdateSets[edit.scope];
|
|
954
|
-
var isMismatch =
|
|
955
|
-
|
|
1147
|
+
var isMismatch =
|
|
1148
|
+
expected && edit.updateSet !== expected && edit.updateSet !== "unknown";
|
|
1149
|
+
updateSetEl.className =
|
|
1150
|
+
"recent-edit-update-set" + (isDefault || isMismatch ? " warning" : "");
|
|
956
1151
|
updateSetEl.textContent = edit.updateSet;
|
|
957
1152
|
if (isMismatch && expected) {
|
|
958
1153
|
updateSetEl.title = "Expected: " + expected;
|
|
@@ -1031,9 +1226,15 @@ loadClickUpStatus();
|
|
|
1031
1226
|
recentEditsInterval = setInterval(loadRecentEdits, 60000);
|
|
1032
1227
|
|
|
1033
1228
|
// Wire up sidebar toggle and close buttons
|
|
1034
|
-
document
|
|
1035
|
-
|
|
1036
|
-
|
|
1229
|
+
document
|
|
1230
|
+
.getElementById("sidebar-toggle")
|
|
1231
|
+
.addEventListener("click", toggleSidebar);
|
|
1232
|
+
document
|
|
1233
|
+
.getElementById("sidebar-close")
|
|
1234
|
+
.addEventListener("click", toggleSidebar);
|
|
1235
|
+
document
|
|
1236
|
+
.getElementById("refresh-btn")
|
|
1237
|
+
.addEventListener("click", refreshDashboard);
|
|
1037
1238
|
|
|
1038
1239
|
// Render initial status filter chips
|
|
1039
1240
|
renderStatusFilters();
|
|
@@ -1050,7 +1251,9 @@ renderTaskToggles();
|
|
|
1050
1251
|
// --- Theme toggle ---
|
|
1051
1252
|
|
|
1052
1253
|
function currentTheme() {
|
|
1053
|
-
return document.documentElement.getAttribute("data-theme") === "dark"
|
|
1254
|
+
return document.documentElement.getAttribute("data-theme") === "dark"
|
|
1255
|
+
? "dark"
|
|
1256
|
+
: "light";
|
|
1054
1257
|
}
|
|
1055
1258
|
|
|
1056
1259
|
function applyTheme(theme) {
|
|
@@ -1061,7 +1264,8 @@ function applyTheme(theme) {
|
|
|
1061
1264
|
var btn = document.getElementById("cp-theme-toggle");
|
|
1062
1265
|
if (btn) {
|
|
1063
1266
|
btn.textContent = theme === "dark" ? "☀︎" : "☾";
|
|
1064
|
-
btn.title =
|
|
1267
|
+
btn.title =
|
|
1268
|
+
theme === "dark" ? "Switch to light mode" : "Switch to dark mode";
|
|
1065
1269
|
}
|
|
1066
1270
|
}
|
|
1067
1271
|
|
package/public/styles.css
CHANGED
|
@@ -312,6 +312,28 @@ header h1 span {
|
|
|
312
312
|
font-weight: var(--weight-semibold);
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
.btn-start-task {
|
|
316
|
+
font-family: inherit;
|
|
317
|
+
font-size: 11px;
|
|
318
|
+
font-weight: var(--weight-semibold);
|
|
319
|
+
padding: 4px 10px;
|
|
320
|
+
margin-right: 6px;
|
|
321
|
+
background: var(--success);
|
|
322
|
+
color: var(--fg-on-dark);
|
|
323
|
+
border: 1px solid var(--success);
|
|
324
|
+
border-radius: var(--radius-sm);
|
|
325
|
+
cursor: pointer;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.btn-start-task:hover {
|
|
329
|
+
filter: brightness(1.1);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.btn-start-task:disabled {
|
|
333
|
+
opacity: 0.6;
|
|
334
|
+
cursor: default;
|
|
335
|
+
}
|
|
336
|
+
|
|
315
337
|
.btn-deselect {
|
|
316
338
|
font-family: inherit;
|
|
317
339
|
font-size: 11px;
|