@vheins/local-memory-mcp 0.4.10 → 0.4.12
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/dist/dashboard/public/app.js +305 -47
- package/dist/dashboard/public/index.html +74 -46
- package/dist/dashboard/server.js +104 -0
- package/dist/dashboard/server.js.map +1 -1
- package/dist/prompts/registry.d.ts.map +1 -1
- package/dist/prompts/registry.js +22 -33
- package/dist/prompts/registry.js.map +1 -1
- package/dist/storage/sqlite.d.ts +4 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +53 -0
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/tests/normalize.test.js +23 -1
- package/dist/tests/normalize.test.js.map +1 -1
- package/dist/tools/memory.synthesize.d.ts.map +1 -1
- package/dist/tools/memory.synthesize.js +4 -3
- package/dist/tools/memory.synthesize.js.map +1 -1
- package/dist/tools/schemas.d.ts +15 -15
- package/dist/tools/schemas.d.ts.map +1 -1
- package/dist/tools/schemas.js +14 -13
- package/dist/tools/schemas.js.map +1 -1
- package/dist/utils/normalize.d.ts +5 -0
- package/dist/utils/normalize.d.ts.map +1 -1
- package/dist/utils/normalize.js +11 -0
- package/dist/utils/normalize.js.map +1 -1
- package/package.json +1 -2
|
@@ -1757,7 +1757,10 @@ let currentTasks = [];
|
|
|
1757
1757
|
|
|
1758
1758
|
async function loadTasks() {
|
|
1759
1759
|
if (!currentRepo) return;
|
|
1760
|
-
|
|
1760
|
+
|
|
1761
|
+
taskTimeStats = null; // Force reload
|
|
1762
|
+
updateTimeStats('daily');
|
|
1763
|
+
|
|
1761
1764
|
// Reset pagination
|
|
1762
1765
|
taskPagination.backlog = { page: 1, pageSize: 20, hasMore: true, loading: false };
|
|
1763
1766
|
taskPagination.todo = { page: 1, pageSize: 20, hasMore: true, loading: false };
|
|
@@ -1903,6 +1906,222 @@ function renderTaskCards(containerId, tasks, clear = false) {
|
|
|
1903
1906
|
}
|
|
1904
1907
|
}
|
|
1905
1908
|
|
|
1909
|
+
let taskTimeStats = null;
|
|
1910
|
+
|
|
1911
|
+
async function updateTimeStats(period) {
|
|
1912
|
+
const btns = ['daily', 'weekly', 'monthly', 'overall'];
|
|
1913
|
+
btns.forEach(p => {
|
|
1914
|
+
const btn = document.getElementById(`${p}StatsBtn`);
|
|
1915
|
+
if (btn) {
|
|
1916
|
+
if (p === period) {
|
|
1917
|
+
btn.classList.add('bg-white', 'dark:bg-slate-800', 'shadow-sm', 'text-sky-600', 'dark:text-sky-400');
|
|
1918
|
+
btn.classList.remove('text-slate-500', 'hover:text-slate-700', 'dark:hover:text-slate-300');
|
|
1919
|
+
} else {
|
|
1920
|
+
btn.classList.remove('bg-white', 'dark:bg-slate-800', 'shadow-sm', 'text-sky-600', 'dark:text-sky-400');
|
|
1921
|
+
btn.classList.add('text-slate-500', 'hover:text-slate-700', 'dark:hover:text-slate-300');
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
const labels = {
|
|
1927
|
+
daily: 'Today',
|
|
1928
|
+
weekly: 'This Week',
|
|
1929
|
+
monthly: 'This Month',
|
|
1930
|
+
overall: 'Overall'
|
|
1931
|
+
};
|
|
1932
|
+
|
|
1933
|
+
for (let i = 1; i <= 4; i++) {
|
|
1934
|
+
const el = document.getElementById(`statsPeriodLabel${i}`);
|
|
1935
|
+
if (el) {
|
|
1936
|
+
const currentText = el.textContent;
|
|
1937
|
+
const type = currentText.split(' ').slice(1).join(' ');
|
|
1938
|
+
el.textContent = `${labels[period]} ${type}`;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
try {
|
|
1943
|
+
if (!taskTimeStats) {
|
|
1944
|
+
const resp = await fetch(`/api/tasks/stats/time?repo=${encodeURIComponent(currentRepo)}`);
|
|
1945
|
+
taskTimeStats = await resp.json();
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
const stats = taskTimeStats[period];
|
|
1949
|
+
document.getElementById('todayCompletedTasksCount').textContent = stats.completed;
|
|
1950
|
+
document.getElementById('todayAddedTasksCount').textContent = stats.added;
|
|
1951
|
+
document.getElementById('todayTokensTasksCount').textContent = stats.tokens.toLocaleString();
|
|
1952
|
+
|
|
1953
|
+
const mins = Math.round(stats.avgDuration / 60);
|
|
1954
|
+
document.getElementById('todayAvgTimeTasksCount').textContent = `${mins}m`;
|
|
1955
|
+
} catch (err) {
|
|
1956
|
+
console.error('Failed to load time stats:', err);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
async function exportAllData() {
|
|
1961
|
+
try {
|
|
1962
|
+
const resp = await fetch(`/api/export?repo=${encodeURIComponent(currentRepo)}`);
|
|
1963
|
+
const data = await resp.json();
|
|
1964
|
+
|
|
1965
|
+
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
1966
|
+
const url = URL.createObjectURL(blob);
|
|
1967
|
+
const a = document.createElement('a');
|
|
1968
|
+
a.href = url;
|
|
1969
|
+
a.download = `mcp-export-${currentRepo.replace(/\//g, '-')}-${new Date().toISOString().split('T')[0]}.json`;
|
|
1970
|
+
document.body.appendChild(a);
|
|
1971
|
+
a.click();
|
|
1972
|
+
document.body.removeChild(a);
|
|
1973
|
+
URL.revokeObjectURL(url);
|
|
1974
|
+
|
|
1975
|
+
showToast('Export successful', 'success');
|
|
1976
|
+
} catch (err) {
|
|
1977
|
+
showToast('Export failed: ' + err.message, 'error');
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
async function updateTaskStatus(id, newStatus, comment = '') {
|
|
1982
|
+
try {
|
|
1983
|
+
const resp = await fetch(`/api/tasks/${id}`, {
|
|
1984
|
+
method: 'PUT',
|
|
1985
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1986
|
+
body: JSON.stringify({ status: newStatus, comment })
|
|
1987
|
+
});
|
|
1988
|
+
|
|
1989
|
+
if (resp.ok) {
|
|
1990
|
+
showToast(`Task moved to ${newStatus}`, 'success');
|
|
1991
|
+
loadTasks();
|
|
1992
|
+
showTaskDetail(id); // Refresh detail view
|
|
1993
|
+
} else {
|
|
1994
|
+
const err = await resp.json();
|
|
1995
|
+
throw new Error(err.error || 'Update failed');
|
|
1996
|
+
}
|
|
1997
|
+
} catch (err) {
|
|
1998
|
+
showToast('Failed to update task: ' + err.message, 'error');
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
let isEditingTask = false;
|
|
2003
|
+
async function toggleTaskEdit(id) {
|
|
2004
|
+
if (isEditingTask) {
|
|
2005
|
+
await saveTaskEdits(id);
|
|
2006
|
+
} else {
|
|
2007
|
+
enterTaskEditMode();
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
function enterTaskEditMode() {
|
|
2012
|
+
isEditingTask = true;
|
|
2013
|
+
const titleEl = document.getElementById('taskDetailTitle');
|
|
2014
|
+
const descEl = document.getElementById('taskDetailDesc');
|
|
2015
|
+
const editBtn = document.getElementById('taskEditBtn');
|
|
2016
|
+
|
|
2017
|
+
if (titleEl) {
|
|
2018
|
+
const titleText = titleEl.textContent;
|
|
2019
|
+
titleEl.innerHTML = `<input type="text" id="editTaskTitle" class="w-full p-2 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg text-lg font-bold" value="${escapeHtml(titleText)}">`;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
if (descEl) {
|
|
2023
|
+
const descText = descEl.dataset.raw || '';
|
|
2024
|
+
descEl.innerHTML = `<textarea id="editTaskDesc" class="w-full p-3 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg text-sm min-h-[150px] font-mono">${escapeHtml(descText)}</textarea>`;
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
if (editBtn) {
|
|
2028
|
+
editBtn.innerHTML = `<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg> Save`;
|
|
2029
|
+
editBtn.classList.add('bg-emerald-500', 'text-white');
|
|
2030
|
+
editBtn.classList.remove('bg-white', 'dark:bg-slate-800');
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
async function saveTaskEdits(id) {
|
|
2035
|
+
const newTitle = document.getElementById('editTaskTitle').value;
|
|
2036
|
+
const newDesc = document.getElementById('editTaskDesc').value;
|
|
2037
|
+
|
|
2038
|
+
try {
|
|
2039
|
+
const resp = await fetch(`/api/tasks/${id}`, {
|
|
2040
|
+
method: 'PUT',
|
|
2041
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2042
|
+
body: JSON.stringify({ title: newTitle, description: newDesc })
|
|
2043
|
+
});
|
|
2044
|
+
|
|
2045
|
+
if (resp.ok) {
|
|
2046
|
+
showToast('Task updated', 'success');
|
|
2047
|
+
isEditingTask = false;
|
|
2048
|
+
loadTasks();
|
|
2049
|
+
showTaskDetail(id);
|
|
2050
|
+
} else {
|
|
2051
|
+
const err = await resp.json();
|
|
2052
|
+
throw new Error(err.error || 'Update failed');
|
|
2053
|
+
}
|
|
2054
|
+
} catch (err) {
|
|
2055
|
+
showToast('Failed to save changes: ' + err.message, 'error');
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
async function addTaskComment(taskId) {
|
|
2060
|
+
const commentInput = document.getElementById('newTaskComment');
|
|
2061
|
+
const comment = commentInput.value.trim();
|
|
2062
|
+
if (!comment) return;
|
|
2063
|
+
|
|
2064
|
+
try {
|
|
2065
|
+
const resp = await fetch(`/api/tasks/${taskId}`, {
|
|
2066
|
+
method: 'PUT',
|
|
2067
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2068
|
+
body: JSON.stringify({ comment })
|
|
2069
|
+
});
|
|
2070
|
+
|
|
2071
|
+
if (resp.ok) {
|
|
2072
|
+
commentInput.value = '';
|
|
2073
|
+
showTaskDetail(taskId);
|
|
2074
|
+
} else {
|
|
2075
|
+
const err = await resp.json();
|
|
2076
|
+
throw new Error(err.error || 'Failed to add comment');
|
|
2077
|
+
}
|
|
2078
|
+
} catch (err) {
|
|
2079
|
+
showToast(err.message, 'error');
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
async function editTaskComment(commentId, taskId) {
|
|
2084
|
+
const commentEl = document.getElementById(`comment-body-${commentId}`);
|
|
2085
|
+
const currentText = commentEl.dataset.raw || '';
|
|
2086
|
+
|
|
2087
|
+
const newText = prompt('Edit comment:', currentText);
|
|
2088
|
+
if (newText === null || newText === currentText) return;
|
|
2089
|
+
|
|
2090
|
+
try {
|
|
2091
|
+
const resp = await fetch(`/api/task-comments/${commentId}`, {
|
|
2092
|
+
method: 'PUT',
|
|
2093
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2094
|
+
body: JSON.stringify({ comment: newText })
|
|
2095
|
+
});
|
|
2096
|
+
|
|
2097
|
+
if (resp.ok) {
|
|
2098
|
+
showTaskDetail(taskId);
|
|
2099
|
+
} else {
|
|
2100
|
+
throw new Error('Failed to update comment');
|
|
2101
|
+
}
|
|
2102
|
+
} catch (err) {
|
|
2103
|
+
showToast(err.message, 'error');
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
async function deleteTaskComment(commentId, taskId) {
|
|
2108
|
+
if (!confirm('Are you sure you want to delete this comment?')) return;
|
|
2109
|
+
|
|
2110
|
+
try {
|
|
2111
|
+
const resp = await fetch(`/api/task-comments/${commentId}`, {
|
|
2112
|
+
method: 'DELETE'
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
if (resp.ok) {
|
|
2116
|
+
showTaskDetail(taskId);
|
|
2117
|
+
} else {
|
|
2118
|
+
throw new Error('Failed to delete comment');
|
|
2119
|
+
}
|
|
2120
|
+
} catch (err) {
|
|
2121
|
+
showToast(err.message, 'error');
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
|
|
1906
2125
|
async function showTaskDetail(id) {
|
|
1907
2126
|
const drawer = document.getElementById('memoryDrawer');
|
|
1908
2127
|
const title = document.getElementById('drawerTitle');
|
|
@@ -1919,11 +2138,10 @@ async function showTaskDetail(id) {
|
|
|
1919
2138
|
<div class="skeleton h-64 w-full"></div>
|
|
1920
2139
|
</div>
|
|
1921
2140
|
`;
|
|
1922
|
-
|
|
2141
|
+
|
|
1923
2142
|
drawer.classList.remove('hidden');
|
|
1924
2143
|
document.body.classList.add('drawer-open');
|
|
1925
|
-
|
|
1926
|
-
// Trigger slide-in animation
|
|
2144
|
+
|
|
1927
2145
|
setTimeout(() => {
|
|
1928
2146
|
const aside = document.getElementById('drawerAside');
|
|
1929
2147
|
if (aside) {
|
|
@@ -1938,12 +2156,37 @@ async function showTaskDetail(id) {
|
|
|
1938
2156
|
if (!response.ok) throw new Error(task.error || 'Failed to load task');
|
|
1939
2157
|
|
|
1940
2158
|
title.textContent = `Task: ${task.task_code}`;
|
|
1941
|
-
|
|
2159
|
+
isEditingTask = false;
|
|
2160
|
+
|
|
2161
|
+
const statusButtons = {
|
|
2162
|
+
backlog: ['pending', 'blocked'],
|
|
2163
|
+
pending: ['in_progress', 'backlog', 'blocked'],
|
|
2164
|
+
in_progress: ['pending', 'backlog', 'blocked'],
|
|
2165
|
+
blocked: ['pending', 'backlog'],
|
|
2166
|
+
completed: ['pending']
|
|
2167
|
+
};
|
|
2168
|
+
|
|
2169
|
+
const currentButtons = statusButtons[task.status] || [];
|
|
2170
|
+
const actionButtonsHtml = currentButtons.map(s => `
|
|
2171
|
+
<button onclick="updateTaskStatus('${task.id}', '${s}')" class="px-3 py-1.5 rounded-lg text-[10px] font-bold uppercase tracking-wider transition-all border border-slate-200 dark:border-slate-700 hover:bg-slate-100 dark:hover:bg-slate-800">
|
|
2172
|
+
Set to ${formatTaskStatusLabel(s)}
|
|
2173
|
+
</button>
|
|
2174
|
+
`).join('');
|
|
2175
|
+
|
|
1942
2176
|
body.innerHTML = `
|
|
1943
2177
|
<div class="space-y-6">
|
|
1944
|
-
<div class="
|
|
1945
|
-
<div class="
|
|
1946
|
-
|
|
2178
|
+
<div class="flex items-center justify-between gap-4">
|
|
2179
|
+
<div class="flex-1 p-5 bg-slate-50 dark:bg-slate-900/50 rounded-2xl border border-slate-100 dark:border-slate-800 shadow-sm">
|
|
2180
|
+
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-2">Title</div>
|
|
2181
|
+
<h3 id="taskDetailTitle" class="text-xl font-bold text-slate-900 dark:text-white leading-tight">${escapeHtml(task.title)}</h3>
|
|
2182
|
+
</div>
|
|
2183
|
+
<button id="taskEditBtn" onclick="toggleTaskEdit('${task.id}')" class="p-3 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-2xl shadow-sm hover:shadow-md transition-all group">
|
|
2184
|
+
<svg class="w-5 h-5 text-slate-400 group-hover:text-sky-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
|
|
2185
|
+
</button>
|
|
2186
|
+
</div>
|
|
2187
|
+
|
|
2188
|
+
<div class="flex flex-wrap gap-2 px-1">
|
|
2189
|
+
${actionButtonsHtml}
|
|
1947
2190
|
</div>
|
|
1948
2191
|
|
|
1949
2192
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
@@ -1978,12 +2221,12 @@ async function showTaskDetail(id) {
|
|
|
1978
2221
|
|
|
1979
2222
|
<div class="p-5 bg-white dark:bg-slate-800/40 rounded-2xl border border-slate-100 dark:border-slate-800">
|
|
1980
2223
|
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-3">Description</div>
|
|
1981
|
-
<div class="text-sm text-slate-600 dark:text-slate-300 leading-relaxed markdown-body">
|
|
2224
|
+
<div id="taskDetailDesc" data-raw="${escapeHtml(task.description || '')}" class="text-sm text-slate-600 dark:text-slate-300 leading-relaxed markdown-body">
|
|
1982
2225
|
${task.description ? renderMarkdown(task.description) : '<span class="italic opacity-50">No description provided</span>'}
|
|
1983
2226
|
</div>
|
|
1984
2227
|
</div>
|
|
1985
2228
|
|
|
1986
|
-
${renderTaskComments(task.comments)}
|
|
2229
|
+
${renderTaskComments(task.comments, task.id)}
|
|
1987
2230
|
|
|
1988
2231
|
${task.doc_path ? `
|
|
1989
2232
|
<div class="p-4 bg-sky-500/5 dark:bg-sky-500/10 rounded-xl border border-sky-500/20">
|
|
@@ -2026,52 +2269,67 @@ function formatTaskStatusLabel(status) {
|
|
|
2026
2269
|
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
2027
2270
|
}
|
|
2028
2271
|
|
|
2029
|
-
function renderTaskComments(comments) {
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2272
|
+
function renderTaskComments(comments, taskId) {
|
|
2273
|
+
const commentsList = (!comments || comments.length === 0) ? `
|
|
2274
|
+
<div class="text-sm italic text-slate-400 py-4">No historical comments yet</div>
|
|
2275
|
+
` : `
|
|
2276
|
+
<div class="space-y-4 mt-4">
|
|
2277
|
+
${comments.map((item) => `
|
|
2278
|
+
<div class="relative pl-5">
|
|
2279
|
+
<div class="absolute left-0 top-1.5 h-full w-px bg-slate-200 dark:bg-slate-700"></div>
|
|
2280
|
+
<div class="absolute left-[-4px] top-1.5 w-2.5 h-2.5 rounded-full bg-sky-500 shadow-[0_0_0_4px_rgba(14,165,233,0.12)]"></div>
|
|
2281
|
+
<div class="rounded-xl border border-slate-100 dark:border-slate-700 bg-slate-50/80 dark:bg-slate-900/40 p-4 group/comment">
|
|
2282
|
+
<div class="flex flex-wrap items-center gap-2 mb-2">
|
|
2283
|
+
<span class="text-xs font-bold text-slate-800 dark:text-slate-100">${escapeHtml(item.agent || 'unknown')}</span>
|
|
2284
|
+
<span class="text-[10px] text-slate-400">•</span>
|
|
2285
|
+
<span class="text-[11px] text-slate-500 dark:text-slate-400">${escapeHtml(item.model || 'unknown')}</span>
|
|
2286
|
+
<span class="ml-auto text-[10px] text-slate-400">${new Date(item.created_at).toLocaleString()}</span>
|
|
2287
|
+
|
|
2288
|
+
<div class="hidden group-hover/comment:flex items-center gap-1 ml-2">
|
|
2289
|
+
<button onclick="editTaskComment('${item.id}', '${taskId}')" class="p-1 text-slate-400 hover:text-sky-500 transition-colors" title="Edit comment">
|
|
2290
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
|
|
2291
|
+
</button>
|
|
2292
|
+
<button onclick="deleteTaskComment('${item.id}', '${taskId}')" class="p-1 text-slate-400 hover:text-rose-500 transition-colors" title="Delete comment">
|
|
2293
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
|
|
2294
|
+
</button>
|
|
2295
|
+
</div>
|
|
2296
|
+
</div>
|
|
2297
|
+
${(item.previous_status || item.next_status) ? `
|
|
2298
|
+
<div class="flex flex-wrap items-center gap-2 mb-3 text-[10px] font-bold uppercase tracking-wide">
|
|
2299
|
+
<span class="px-2 py-1 rounded-lg bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-300">${escapeHtml(formatTaskStatusLabel(item.previous_status || 'note'))}</span>
|
|
2300
|
+
<span class="text-slate-400">→</span>
|
|
2301
|
+
<span class="px-2 py-1 rounded-lg bg-sky-500/10 text-sky-600 dark:text-sky-400 border border-sky-500/20">${escapeHtml(formatTaskStatusLabel(item.next_status || 'note'))}</span>
|
|
2302
|
+
</div>
|
|
2303
|
+
` : ''}
|
|
2304
|
+
<div id="comment-body-${item.id}" data-raw="${escapeHtml(item.comment)}" class="text-sm text-slate-600 dark:text-slate-300 leading-relaxed markdown-body">
|
|
2305
|
+
${renderMarkdown(item.comment)}
|
|
2306
|
+
</div>
|
|
2307
|
+
</div>
|
|
2308
|
+
</div>
|
|
2309
|
+
`).join('')}
|
|
2310
|
+
</div>
|
|
2311
|
+
`;
|
|
2038
2312
|
|
|
2039
2313
|
return `
|
|
2040
2314
|
<div class="p-5 bg-white dark:bg-slate-800/40 rounded-2xl border border-slate-100 dark:border-slate-800">
|
|
2041
2315
|
<div class="flex items-center justify-between gap-3 mb-4">
|
|
2042
|
-
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">History</div>
|
|
2043
|
-
<div class="text-[10px] text-slate-400">${comments.length}
|
|
2316
|
+
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Discussion & History</div>
|
|
2317
|
+
<div class="text-[10px] text-slate-400">${comments ? comments.length : 0} item${comments && comments.length === 1 ? '' : 's'}</div>
|
|
2044
2318
|
</div>
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
<span class="ml-auto text-[10px] text-slate-400">${new Date(item.created_at).toLocaleString()}</span>
|
|
2056
|
-
</div>
|
|
2057
|
-
${(item.previous_status || item.next_status) ? `
|
|
2058
|
-
<div class="flex flex-wrap items-center gap-2 mb-3 text-[10px] font-bold uppercase tracking-wide">
|
|
2059
|
-
<span class="px-2 py-1 rounded-lg bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-300">${escapeHtml(formatTaskStatusLabel(item.previous_status || 'note'))}</span>
|
|
2060
|
-
<span class="text-slate-400">→</span>
|
|
2061
|
-
<span class="px-2 py-1 rounded-lg bg-sky-500/10 text-sky-600 dark:text-sky-400 border border-sky-500/20">${escapeHtml(formatTaskStatusLabel(item.next_status || 'note'))}</span>
|
|
2062
|
-
</div>
|
|
2063
|
-
` : ''}
|
|
2064
|
-
<div class="text-sm text-slate-600 dark:text-slate-300 leading-relaxed markdown-body">
|
|
2065
|
-
${renderMarkdown(item.comment)}
|
|
2066
|
-
</div>
|
|
2067
|
-
</div>
|
|
2068
|
-
</div>
|
|
2069
|
-
`).join('')}
|
|
2319
|
+
|
|
2320
|
+
<div class="mb-6">
|
|
2321
|
+
<textarea id="newTaskComment" placeholder="Add a comment (Markdown supported)..." class="w-full p-3 bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl text-sm focus:ring-2 focus:ring-sky-500/50 outline-none transition-all min-h-[80px]"></textarea>
|
|
2322
|
+
<div class="flex justify-end mt-2">
|
|
2323
|
+
<button onclick="addTaskComment('${taskId}')" class="px-4 py-1.5 bg-sky-500 hover:bg-sky-600 text-white text-xs font-bold rounded-lg transition-all shadow-sm shadow-sky-500/20">Post Comment</button>
|
|
2324
|
+
</div>
|
|
2325
|
+
</div>
|
|
2326
|
+
|
|
2327
|
+
<div class="border-t border-slate-100 dark:border-slate-700/50 pt-2">
|
|
2328
|
+
${commentsList}
|
|
2070
2329
|
</div>
|
|
2071
2330
|
</div>
|
|
2072
2331
|
`;
|
|
2073
2332
|
}
|
|
2074
|
-
|
|
2075
2333
|
let currentTab = localStorage.getItem('activeTab') || 'dashboard';
|
|
2076
2334
|
|
|
2077
2335
|
function syncTabIndicatorTheme(indicator) {
|
|
@@ -443,6 +443,30 @@
|
|
|
443
443
|
|
|
444
444
|
|
|
445
445
|
/* Markdown body styles for Full Content section */
|
|
446
|
+
.custom-scrollbar::-webkit-scrollbar {
|
|
447
|
+
width: 6px;
|
|
448
|
+
height: 6px;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.snap-x {
|
|
452
|
+
scroll-snap-type: x mandatory;
|
|
453
|
+
-webkit-overflow-scrolling: touch;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.snap-center {
|
|
457
|
+
scroll-snap-align: center;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* Responsive Kanban Board */
|
|
461
|
+
@media (max-width: 768px) {
|
|
462
|
+
#taskBoard {
|
|
463
|
+
margin-left: -1rem;
|
|
464
|
+
margin-right: -1rem;
|
|
465
|
+
padding-left: 1rem;
|
|
466
|
+
padding-right: 1rem;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
446
470
|
.markdown-body {
|
|
447
471
|
font-size: 0.875rem;
|
|
448
472
|
line-height: 1.75;
|
|
@@ -1381,6 +1405,10 @@
|
|
|
1381
1405
|
</div>
|
|
1382
1406
|
</div>
|
|
1383
1407
|
<div class="flex items-center gap-2">
|
|
1408
|
+
<button onclick="exportAllData()" class="btn-refresh bg-amber-500! border-amber-600! shadow-amber-500/20!">
|
|
1409
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path></svg>
|
|
1410
|
+
Export All
|
|
1411
|
+
</button>
|
|
1384
1412
|
<button onclick="showAddTaskModal()" class="btn-add-task">
|
|
1385
1413
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
|
|
1386
1414
|
Add Task
|
|
@@ -1397,58 +1425,58 @@
|
|
|
1397
1425
|
</div>
|
|
1398
1426
|
</div>
|
|
1399
1427
|
|
|
1400
|
-
<!-- Task Context
|
|
1401
|
-
<div class="
|
|
1402
|
-
<div class="
|
|
1403
|
-
<
|
|
1404
|
-
|
|
1405
|
-
</
|
|
1406
|
-
<
|
|
1407
|
-
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Today Completed</div>
|
|
1408
|
-
<div id="todayCompletedTasksCount" class="text-xl font-black text-slate-700 dark:text-slate-200">0</div>
|
|
1409
|
-
</div>
|
|
1428
|
+
<!-- Task Context Stats with Tabs -->
|
|
1429
|
+
<div class="mb-8">
|
|
1430
|
+
<div class="flex items-center gap-1 p-1 bg-slate-100 dark:bg-slate-900/50 rounded-xl w-fit mb-4">
|
|
1431
|
+
<button onclick="updateTimeStats('daily')" id="dailyStatsBtn" class="px-4 py-1.5 rounded-lg text-xs font-bold transition-all bg-white dark:bg-slate-800 shadow-sm text-sky-600 dark:text-sky-400">Daily</button>
|
|
1432
|
+
<button onclick="updateTimeStats('weekly')" id="weeklyStatsBtn" class="px-4 py-1.5 rounded-lg text-xs font-bold transition-all text-slate-500 hover:text-slate-700 dark:hover:text-slate-300">Weekly</button>
|
|
1433
|
+
<button onclick="updateTimeStats('monthly')" id="monthlyStatsBtn" class="px-4 py-1.5 rounded-lg text-xs font-bold transition-all text-slate-500 hover:text-slate-700 dark:hover:text-slate-300">Monthly</button>
|
|
1434
|
+
<button onclick="updateTimeStats('overall')" id="overallStatsBtn" class="px-4 py-1.5 rounded-lg text-xs font-bold transition-all text-slate-500 hover:text-slate-700 dark:hover:text-slate-300">Overall</button>
|
|
1410
1435
|
</div>
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
<div
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
<div class="w-10 h-10 rounded-full bg-sky-500/10 flex items-center justify-center text-sky-600">
|
|
1422
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
|
1423
|
-
</div>
|
|
1424
|
-
<div>
|
|
1425
|
-
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Today Processed</div>
|
|
1426
|
-
<div id="todayProcessedTasksCount" class="text-xl font-black text-slate-700 dark:text-slate-200">0</div>
|
|
1427
|
-
</div>
|
|
1428
|
-
</div>
|
|
1429
|
-
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl p-4 flex items-center gap-4">
|
|
1430
|
-
<div class="w-10 h-10 rounded-full bg-amber-500/10 flex items-center justify-center text-amber-600">
|
|
1431
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
|
1436
|
+
|
|
1437
|
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
1438
|
+
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl p-4 flex items-center gap-4 shadow-sm">
|
|
1439
|
+
<div class="w-10 h-10 rounded-full bg-emerald-500/10 flex items-center justify-center text-emerald-600">
|
|
1440
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
1441
|
+
</div>
|
|
1442
|
+
<div>
|
|
1443
|
+
<div id="statsPeriodLabel1" class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Today Completed</div>
|
|
1444
|
+
<div id="todayCompletedTasksCount" class="text-xl font-black text-slate-700 dark:text-slate-200">0</div>
|
|
1445
|
+
</div>
|
|
1432
1446
|
</div>
|
|
1433
|
-
<div>
|
|
1434
|
-
<div class="
|
|
1435
|
-
|
|
1447
|
+
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl p-4 flex items-center gap-4 shadow-sm">
|
|
1448
|
+
<div class="w-10 h-10 rounded-full bg-indigo-500/10 flex items-center justify-center text-indigo-600">
|
|
1449
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
|
|
1450
|
+
</div>
|
|
1451
|
+
<div>
|
|
1452
|
+
<div id="statsPeriodLabel2" class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Today Added</div>
|
|
1453
|
+
<div id="todayAddedTasksCount" class="text-xl font-black text-slate-700 dark:text-slate-200">0</div>
|
|
1454
|
+
</div>
|
|
1436
1455
|
</div>
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1456
|
+
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl p-4 flex items-center gap-4 shadow-sm">
|
|
1457
|
+
<div class="w-10 h-10 rounded-full bg-amber-500/10 flex items-center justify-center text-amber-600">
|
|
1458
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
|
1459
|
+
</div>
|
|
1460
|
+
<div>
|
|
1461
|
+
<div id="statsPeriodLabel3" class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Today Tokens</div>
|
|
1462
|
+
<div id="todayTokensTasksCount" class="text-xl font-black text-slate-700 dark:text-slate-200">0</div>
|
|
1463
|
+
</div>
|
|
1441
1464
|
</div>
|
|
1442
|
-
<div>
|
|
1443
|
-
<div class="
|
|
1444
|
-
|
|
1465
|
+
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl p-4 flex items-center gap-4 shadow-sm">
|
|
1466
|
+
<div class="w-10 h-10 rounded-full bg-sky-500/10 flex items-center justify-center text-sky-600">
|
|
1467
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
1468
|
+
</div>
|
|
1469
|
+
<div>
|
|
1470
|
+
<div id="statsPeriodLabel4" class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Today Avg Time</div>
|
|
1471
|
+
<div id="todayAvgTimeTasksCount" class="text-xl font-black text-slate-700 dark:text-slate-200">0m</div>
|
|
1472
|
+
</div>
|
|
1445
1473
|
</div>
|
|
1446
1474
|
</div>
|
|
1447
1475
|
</div>
|
|
1448
1476
|
|
|
1449
|
-
<div id="taskBoard" class="
|
|
1477
|
+
<div id="taskBoard" class="flex overflow-x-auto pb-6 md:grid md:grid-cols-4 gap-6 snap-x custom-scrollbar">
|
|
1450
1478
|
<!-- Column: Backlog -->
|
|
1451
|
-
<div class="flex flex-col gap-4">
|
|
1479
|
+
<div class="flex flex-col gap-4 flex-shrink-0 w-[85vw] md:w-auto snap-center">
|
|
1452
1480
|
<div class="flex items-center justify-between px-2">
|
|
1453
1481
|
<h3 class="font-bold text-slate-400 uppercase tracking-wider text-xs">Backlog</h3>
|
|
1454
1482
|
<span id="backlogCount" class="bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 px-2 py-0.5 rounded-full text-[10px] font-bold">0</span>
|
|
@@ -1456,7 +1484,7 @@
|
|
|
1456
1484
|
<div id="backlogTasks" class="space-y-4 min-h-[200px] max-h-[70vh] overflow-y-auto pr-2 custom-scrollbar"></div>
|
|
1457
1485
|
</div>
|
|
1458
1486
|
<!-- Column: Todo -->
|
|
1459
|
-
<div class="flex flex-col gap-4">
|
|
1487
|
+
<div class="flex flex-col gap-4 flex-shrink-0 w-[85vw] md:w-auto snap-center">
|
|
1460
1488
|
<div class="flex items-center justify-between px-2">
|
|
1461
1489
|
<h3 class="font-bold text-gray-500 uppercase tracking-wider text-xs">To Do</h3>
|
|
1462
1490
|
<span id="todoCount" class="bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 px-2 py-0.5 rounded-full text-[10px] font-bold">0</span>
|
|
@@ -1464,7 +1492,7 @@
|
|
|
1464
1492
|
<div id="todoTasks" class="space-y-4 min-h-[200px] max-h-[70vh] overflow-y-auto pr-2 custom-scrollbar"></div>
|
|
1465
1493
|
</div>
|
|
1466
1494
|
<!-- Column: In Progress -->
|
|
1467
|
-
<div class="flex flex-col gap-4">
|
|
1495
|
+
<div class="flex flex-col gap-4 flex-shrink-0 w-[85vw] md:w-auto snap-center">
|
|
1468
1496
|
<div class="flex items-center justify-between px-2">
|
|
1469
1497
|
<h3 class="font-bold text-sky-500 uppercase tracking-wider text-xs">In Progress</h3>
|
|
1470
1498
|
<span id="inProgressCount" class="bg-sky-100 dark:bg-sky-900/40 text-sky-600 dark:text-sky-400 px-2 py-0.5 rounded-full text-[10px] font-bold">0</span>
|
|
@@ -1472,7 +1500,7 @@
|
|
|
1472
1500
|
<div id="inProgressTasks" class="space-y-4 min-h-[200px] max-h-[70vh] overflow-y-auto pr-2 custom-scrollbar"></div>
|
|
1473
1501
|
</div>
|
|
1474
1502
|
<!-- Column: Completed -->
|
|
1475
|
-
<div class="flex flex-col gap-4">
|
|
1503
|
+
<div class="flex flex-col gap-4 flex-shrink-0 w-[85vw] md:w-auto snap-center">
|
|
1476
1504
|
<div class="flex items-center justify-between px-2">
|
|
1477
1505
|
<h3 class="font-bold text-emerald-500 uppercase tracking-wider text-xs">Completed</h3>
|
|
1478
1506
|
<span id="completedCount" class="bg-emerald-100 dark:bg-emerald-900/40 text-emerald-600 dark:text-emerald-400 px-2 py-0.5 rounded-full text-[10px] font-bold">0</span>
|