Update dashboard, root (+1 ~2)

This commit is contained in:
Echo
2026-02-02 10:45:03 +00:00
parent 02604f6d16
commit 110fa39da5
3 changed files with 271 additions and 2 deletions

View File

@@ -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
View 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"
}
]
}