Update dashboard, root (+1 ~2)
This commit is contained in:
2
TOOLS.md
2
TOOLS.md
@@ -114,7 +114,7 @@ memory_get path="memory/file.md" from=1 lines=50
|
|||||||
| 15:00 3/feb | 17:00 | grup-sprijin-pregatire | #echo-sprijin | Pregătire fișă grup joi |
|
| 15:00 3/feb | 17:00 | grup-sprijin-pregatire | #echo-sprijin | Pregătire fișă grup joi |
|
||||||
| 15:00 5/feb | 17:00 | grup-sprijin-5feb | #echo-sprijin | Reminder grup sprijin |
|
| 15:00 5/feb | 17:00 | grup-sprijin-5feb | #echo-sprijin | Reminder grup sprijin |
|
||||||
| 18:00 | 20:00 | evening-report | 📧 EMAIL | Raport seară HTML + TOATE propunerile cu context |
|
| 18:00 | 20:00 | evening-report | 📧 EMAIL | Raport seară HTML + TOATE propunerile cu context |
|
||||||
| 19:00 | 21:00 | evening-coaching | #echo-self | Reflecție seară → memory/kb/coaching/ |
|
| 19:00 | 21:00 | evening-coaching | #echo-self + 📧 | Reflecție seară → memory/kb/coaching/ |
|
||||||
| 20:00 | 22:00 | seara-merit-reminder | #echo-self | Reminder lista "10 lucruri pentru care merit respect" |
|
| 20:00 | 22:00 | seara-merit-reminder | #echo-self | Reminder lista "10 lucruri pentru care merit respect" |
|
||||||
| 19:00 dum | 21:00 | weekly-planning | #echo-work | Planning săptămânal |
|
| 19:00 dum | 21:00 | weekly-planning | #echo-work | Planning săptămânal |
|
||||||
| 21:00 | 23:00 | night-execute | #echo-work | Execută task-uri aprobate (run 1) |
|
| 21:00 | 23:00 | night-execute | #echo-work | Execută task-uri aprobate (run 1) |
|
||||||
|
|||||||
@@ -706,6 +706,71 @@
|
|||||||
|
|
||||||
.issue-owner.marius { color: #22c55e; }
|
.issue-owner.marius { color: #22c55e; }
|
||||||
.issue-owner.robert { color: #f59e0b; }
|
.issue-owner.robert { color: #f59e0b; }
|
||||||
|
|
||||||
|
/* Todo's Panel */
|
||||||
|
.todos-panel { border-left: 3px solid #8b5cf6; }
|
||||||
|
.todo-section { margin-bottom: 16px; }
|
||||||
|
.todo-section-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.todo-section-title.overdue { color: #ef4444; }
|
||||||
|
.todo-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: #1f2937;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.todo-item:hover { background: #374151; }
|
||||||
|
.todo-item.done { opacity: 0.6; }
|
||||||
|
.todo-item.done .todo-text { text-decoration: line-through; }
|
||||||
|
.todo-checkbox {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid #6b7280;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.todo-checkbox:hover { border-color: #8b5cf6; }
|
||||||
|
.todo-checkbox.checked {
|
||||||
|
background: #8b5cf6;
|
||||||
|
border-color: #8b5cf6;
|
||||||
|
}
|
||||||
|
.todo-checkbox svg { display: none; width: 14px; height: 14px; color: white; }
|
||||||
|
.todo-checkbox.checked svg { display: block; }
|
||||||
|
.todo-content { flex: 1; min-width: 0; }
|
||||||
|
.todo-text { font-size: 14px; color: #f3f4f6; margin-bottom: 4px; }
|
||||||
|
.todo-meta { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
||||||
|
.todo-domain {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.todo-domain.work { background: #1e40af; color: #93c5fd; }
|
||||||
|
.todo-domain.self { background: #6b21a8; color: #d8b4fe; }
|
||||||
|
.todo-domain.sprijin { background: #166534; color: #86efac; }
|
||||||
|
.todo-domain.scout { background: #c2410c; color: #fdba74; }
|
||||||
|
.todo-due {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
.todo-due.overdue { color: #ef4444; font-weight: 500; }
|
||||||
|
.todo-source { font-size: 11px; color: #6b7280; }
|
||||||
.issue-owner.clawdbot { color: #8b5cf6; }
|
.issue-owner.clawdbot { color: #8b5cf6; }
|
||||||
|
|
||||||
.issue-date {
|
.issue-date {
|
||||||
@@ -978,6 +1043,32 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Issues Panel -->
|
<!-- Issues Panel -->
|
||||||
|
<!-- Todo's Panel -->
|
||||||
|
<div class="panel todos-panel" id="todosPanel">
|
||||||
|
<div class="panel-header" onclick="toggleSection('todosPanel')">
|
||||||
|
<div class="panel-header-left">
|
||||||
|
<i data-lucide="chevron-down" class="panel-toggle"></i>
|
||||||
|
<div class="panel-title">
|
||||||
|
<i data-lucide="list-todo"></i>
|
||||||
|
<span>Todo's</span>
|
||||||
|
</div>
|
||||||
|
<span class="panel-count" id="todosCount">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-actions" onclick="event.stopPropagation()">
|
||||||
|
<button class="btn btn-primary btn-sm" onclick="showAddTodoModal()">
|
||||||
|
<i data-lucide="plus"></i>
|
||||||
|
<span>Nou</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body" id="todosBody">
|
||||||
|
<div class="empty-state">
|
||||||
|
<i data-lucide="loader"></i>
|
||||||
|
<p>Se încarcă...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="panel issues-panel" id="issuesPanel">
|
<div class="panel issues-panel" id="issuesPanel">
|
||||||
<div class="panel-header" onclick="toggleSection('issuesPanel')">
|
<div class="panel-header" onclick="toggleSection('issuesPanel')">
|
||||||
<div class="panel-header-left">
|
<div class="panel-header-left">
|
||||||
@@ -1380,6 +1471,168 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Load data
|
// Load data
|
||||||
|
// ===== TODO'S =====
|
||||||
|
let todosData = { items: [] };
|
||||||
|
|
||||||
|
async function loadTodos() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('todos.json?' + Date.now());
|
||||||
|
if (response.ok) {
|
||||||
|
todosData = await response.json();
|
||||||
|
renderTodos();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading todos:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTodos() {
|
||||||
|
const container = document.getElementById('todosBody');
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
const tomorrow = new Date(Date.now() + 86400000).toISOString().split('T')[0];
|
||||||
|
|
||||||
|
// Group by status
|
||||||
|
const todayItems = todosData.items.filter(t => !t.done && t.dueDate === today);
|
||||||
|
const tomorrowItems = todosData.items.filter(t => !t.done && t.dueDate === tomorrow);
|
||||||
|
const overdueItems = todosData.items.filter(t => !t.done && t.dueDate < today);
|
||||||
|
const futureItems = todosData.items.filter(t => !t.done && t.dueDate > tomorrow);
|
||||||
|
const doneToday = todosData.items.filter(t => t.done && t.doneAt && t.doneAt.startsWith(today));
|
||||||
|
|
||||||
|
// Update count
|
||||||
|
const activeCount = todosData.items.filter(t => !t.done).length;
|
||||||
|
document.getElementById('todosCount').textContent = activeCount;
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
if (overdueItems.length > 0) {
|
||||||
|
html += '<div class="todo-section"><div class="todo-section-title overdue">⚠️ RESTANTE</div>';
|
||||||
|
html += overdueItems.map(t => renderTodoItem(t)).join('');
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (todayItems.length > 0) {
|
||||||
|
html += '<div class="todo-section"><div class="todo-section-title">📅 AZI</div>';
|
||||||
|
html += todayItems.map(t => renderTodoItem(t)).join('');
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tomorrowItems.length > 0) {
|
||||||
|
html += '<div class="todo-section"><div class="todo-section-title">📅 MÂINE</div>';
|
||||||
|
html += tomorrowItems.map(t => renderTodoItem(t)).join('');
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (futureItems.length > 0) {
|
||||||
|
html += '<div class="todo-section"><div class="todo-section-title">📆 VIITOR</div>';
|
||||||
|
html += futureItems.map(t => renderTodoItem(t)).join('');
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doneToday.length > 0) {
|
||||||
|
html += '<div class="todo-section"><div class="todo-section-title">✅ COMPLETATE AZI</div>';
|
||||||
|
html += doneToday.map(t => renderTodoItem(t)).join('');
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (html === '') {
|
||||||
|
html = '<div class="empty-state"><i data-lucide="check-circle"></i><p>Niciun todo activ</p></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = html;
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTodoItem(todo) {
|
||||||
|
const isOverdue = !todo.done && todo.dueDate < new Date().toISOString().split('T')[0];
|
||||||
|
return `
|
||||||
|
<div class="todo-item ${todo.done ? 'done' : ''}" data-id="${todo.id}">
|
||||||
|
<div class="todo-checkbox ${todo.done ? 'checked' : ''}" onclick="toggleTodo('${todo.id}')">
|
||||||
|
<i data-lucide="check"></i>
|
||||||
|
</div>
|
||||||
|
<div class="todo-content">
|
||||||
|
<div class="todo-text">${todo.text}</div>
|
||||||
|
<div class="todo-meta">
|
||||||
|
<span class="todo-domain ${todo.domain}">@${todo.domain}</span>
|
||||||
|
${todo.dueDate ? `<span class="todo-due ${isOverdue ? 'overdue' : ''}">${formatDate(todo.dueDate)}</span>` : ''}
|
||||||
|
${todo.source ? `<span class="todo-source">${todo.source}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(dateStr) {
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
const today = new Date();
|
||||||
|
const tomorrow = new Date(Date.now() + 86400000);
|
||||||
|
|
||||||
|
if (dateStr === today.toISOString().split('T')[0]) return 'Azi';
|
||||||
|
if (dateStr === tomorrow.toISOString().split('T')[0]) return 'Mâine';
|
||||||
|
|
||||||
|
return date.toLocaleDateString('ro-RO', { day: 'numeric', month: 'short' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleTodo(id) {
|
||||||
|
const todo = todosData.items.find(t => t.id === id);
|
||||||
|
if (todo) {
|
||||||
|
todo.done = !todo.done;
|
||||||
|
todo.doneAt = todo.done ? new Date().toISOString() : null;
|
||||||
|
await saveTodos();
|
||||||
|
renderTodos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveTodos() {
|
||||||
|
todosData.lastUpdated = new Date().toISOString();
|
||||||
|
try {
|
||||||
|
const response = await fetch('/echo/api/files', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
path: 'dashboard/todos.json',
|
||||||
|
content: JSON.stringify(todosData, null, 2)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error('Save failed');
|
||||||
|
} catch (error) {
|
||||||
|
showToast('Eroare la salvare', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAddTodoModal() {
|
||||||
|
const text = prompt('Todo (ex: @work Verifică client X)');
|
||||||
|
if (text && text.trim()) {
|
||||||
|
addTodo(text.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addTodo(text) {
|
||||||
|
// Parse domain from text (@work, @self, @sprijin, @scout)
|
||||||
|
let domain = 'work';
|
||||||
|
const domainMatch = text.match(/@(work|self|sprijin|scout)/i);
|
||||||
|
if (domainMatch) {
|
||||||
|
domain = domainMatch[1].toLowerCase();
|
||||||
|
text = text.replace(/@(work|self|sprijin|scout)/i, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const todo = {
|
||||||
|
id: 'todo-' + Date.now(),
|
||||||
|
text: text,
|
||||||
|
domain: domain,
|
||||||
|
dueDate: new Date().toISOString().split('T')[0],
|
||||||
|
done: false,
|
||||||
|
doneAt: null,
|
||||||
|
source: 'manual',
|
||||||
|
createdAt: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
todosData.items.push(todo);
|
||||||
|
await saveTodos();
|
||||||
|
renderTodos();
|
||||||
|
showToast('Todo adăugat');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== ISSUES =====
|
||||||
async function loadIssues() {
|
async function loadIssues() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('issues.json?' + Date.now());
|
const response = await fetch('issues.json?' + Date.now());
|
||||||
@@ -1717,7 +1970,7 @@
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
path: 'kanban/issues.json',
|
path: 'dashboard/issues.json',
|
||||||
content: JSON.stringify(issuesData, null, 2)
|
content: JSON.stringify(issuesData, null, 2)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -1743,6 +1996,7 @@
|
|||||||
initStatusSections();
|
initStatusSections();
|
||||||
loadStatus();
|
loadStatus();
|
||||||
loadIssues();
|
loadIssues();
|
||||||
|
loadTodos();
|
||||||
loadActivity();
|
loadActivity();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
15
dashboard/todos.json
Normal file
15
dashboard/todos.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"lastUpdated": "2026-02-02T10:45:00Z",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "prov-2026-02-02",
|
||||||
|
"text": "Provocare: Observă când apare vocea critică și dă-i un nume ridicol",
|
||||||
|
"domain": "self",
|
||||||
|
"dueDate": "2026-02-02",
|
||||||
|
"done": false,
|
||||||
|
"doneAt": null,
|
||||||
|
"source": "morning-coaching",
|
||||||
|
"createdAt": "2026-02-02T09:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user