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 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 |
|
||||
| 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" |
|
||||
| 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) |
|
||||
|
||||
@@ -706,6 +706,71 @@
|
||||
|
||||
.issue-owner.marius { color: #22c55e; }
|
||||
.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-date {
|
||||
@@ -978,6 +1043,32 @@
|
||||
</div>
|
||||
|
||||
<!-- 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-header" onclick="toggleSection('issuesPanel')">
|
||||
<div class="panel-header-left">
|
||||
@@ -1380,6 +1471,168 @@
|
||||
});
|
||||
|
||||
// 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() {
|
||||
try {
|
||||
const response = await fetch('issues.json?' + Date.now());
|
||||
@@ -1717,7 +1970,7 @@
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
path: 'kanban/issues.json',
|
||||
path: 'dashboard/issues.json',
|
||||
content: JSON.stringify(issuesData, null, 2)
|
||||
})
|
||||
});
|
||||
@@ -1743,6 +1996,7 @@
|
||||
initStatusSections();
|
||||
loadStatus();
|
||||
loadIssues();
|
||||
loadTodos();
|
||||
loadActivity();
|
||||
</script>
|
||||
</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