Update dashboard, memory, root (~6)

This commit is contained in:
Echo
2026-02-02 12:47:34 +00:00
parent 9d080a099e
commit 170c65f579
6 changed files with 352 additions and 78 deletions

View File

@@ -192,11 +192,15 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
).stdout.strip()
# Parse uncommitted into structured format
# Format: XY PATH where XY is 2 chars (index + working tree status)
# Examples: "M file" (staged), " M file" (unstaged), "?? file" (untracked)
uncommitted_parsed = []
for line in uncommitted:
if len(line) >= 3:
status = line[:2].strip()
filepath = line[3:].strip()
# Path starts at position 3 for most cases, but we use lstrip
# to handle edge cases where there's no separator space
filepath = line[2:].lstrip().strip()
uncommitted_parsed.append({'status': status, 'path': filepath})
self.send_json({

View File

@@ -970,6 +970,27 @@
margin-bottom: var(--space-3);
opacity: 0.5;
}
/* Danger button */
.btn-danger {
background: #dc2626;
color: white;
border: none;
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-md);
font-size: var(--text-sm);
cursor: pointer;
transition: all var(--transition-fast);
}
.btn-danger:hover {
background: #b91c1c;
}
/* Clickable todo item */
.todo-item {
cursor: pointer;
}
</style>
</head>
<body>
@@ -1181,10 +1202,11 @@
</div>
</div>
<!-- Add Issue Modal -->
<div class="modal-overlay" id="addModal">
<!-- Issue Modal (Add/Edit) -->
<div class="modal-overlay" id="issueModal">
<div class="modal">
<h2 class="modal-title">Issue nou</h2>
<h2 class="modal-title" id="issueModalTitle">Issue nou</h2>
<input type="hidden" id="issueEditId">
<div class="form-group">
<label class="form-label">Titlu *</label>
<input type="text" class="input" id="issueTitle" placeholder="Ce trebuie făcut?">
@@ -1225,8 +1247,59 @@
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideAddModal()">Anulează</button>
<button class="btn btn-primary" onclick="addIssue()">Adaugă</button>
<button class="btn btn-danger" id="issueDeleteBtn" onclick="deleteIssue()" style="margin-right: auto; display: none;">Șterge</button>
<button class="btn btn-secondary" onclick="hideIssueModal()">Anulează</button>
<button class="btn btn-primary" id="issueSaveBtn" onclick="saveIssue()">Adaugă</button>
</div>
</div>
</div>
<!-- Todo Modal (Add/Edit) -->
<div class="modal-overlay" id="todoModal">
<div class="modal">
<h2 class="modal-title" id="todoModalTitle">Todo nou</h2>
<input type="hidden" id="todoEditId">
<div class="form-group">
<label class="form-label">Ce trebuie făcut? *</label>
<input type="text" class="input" id="todoText" placeholder="Ex: Verifică client X">
</div>
<div class="form-group">
<label class="form-label">Context</label>
<textarea class="input" id="todoContext" rows="2" placeholder="De ce? Detalii..."></textarea>
</div>
<div class="form-group">
<label class="form-label">Exemplu</label>
<textarea class="input" id="todoExample" rows="2" placeholder="Un exemplu concret..."></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Domeniu</label>
<select class="input" id="todoDomain">
<option value="work">💼 Work</option>
<option value="self">🧘 Self</option>
<option value="sprijin">💚 Sprijin</option>
<option value="scout">⚜️ Scout</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Data</label>
<input type="date" class="input" id="todoDueDate">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Sursă</label>
<input type="text" class="input" id="todoSource" placeholder="Ex: video, articol...">
</div>
<div class="form-group">
<label class="form-label">Link sursă</label>
<input type="text" class="input" id="todoSourceUrl" placeholder="URL...">
</div>
</div>
<div class="modal-actions">
<button class="btn btn-danger" id="todoDeleteBtn" onclick="deleteTodo()" style="margin-right: auto; display: none;">Șterge</button>
<button class="btn btn-secondary" onclick="hideTodoModal()">Anulează</button>
<button class="btn btn-primary" id="todoSaveBtn" onclick="saveTodoForm()">Adaugă</button>
</div>
</div>
</div>
@@ -1412,7 +1485,8 @@
`;
if (git.uncommittedCount > 0) {
const files = git.uncommitted.slice(0, 3).map(f => f.trim().split(' ').pop()).join(', ');
// Use uncommittedParsed for correctly parsed paths
const files = (git.uncommittedParsed || []).slice(0, 3).map(f => f.path).join(', ');
const more = git.uncommittedCount > 3 ? ` +${git.uncommittedCount - 3}` : '';
html += `<div class="status-detail-item uncommitted">
<i data-lucide="alert-circle"></i>
@@ -1662,8 +1736,8 @@
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}')">
<div class="todo-item ${todo.done ? 'done' : ''}" data-id="${todo.id}" onclick="editTodo('${todo.id}')">
<div class="todo-checkbox ${todo.done ? 'checked' : ''}" onclick="event.stopPropagation(); toggleTodo('${todo.id}')">
<i data-lucide="check"></i>
</div>
<div class="todo-content">
@@ -1746,36 +1820,102 @@
}
function showAddTodoModal() {
const text = prompt('Todo (ex: @work Verifică client X)');
if (text && text.trim()) {
addTodo(text.trim());
}
document.getElementById('todoModalTitle').textContent = 'Todo nou';
document.getElementById('todoEditId').value = '';
document.getElementById('todoText').value = '';
document.getElementById('todoContext').value = '';
document.getElementById('todoExample').value = '';
document.getElementById('todoDomain').value = 'work';
document.getElementById('todoDueDate').value = new Date().toISOString().split('T')[0];
document.getElementById('todoSource').value = '';
document.getElementById('todoSourceUrl').value = '';
document.getElementById('todoDeleteBtn').style.display = 'none';
document.getElementById('todoSaveBtn').textContent = 'Adaugă';
document.getElementById('todoModal').classList.add('active');
document.getElementById('todoText').focus();
}
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();
function editTodo(id) {
const todo = todosData.items.find(t => t.id === id);
if (!todo) return;
document.getElementById('todoModalTitle').textContent = 'Editare todo';
document.getElementById('todoEditId').value = id;
document.getElementById('todoText').value = todo.text || '';
document.getElementById('todoContext').value = todo.context || '';
document.getElementById('todoExample').value = todo.example || '';
document.getElementById('todoDomain').value = todo.domain || 'work';
document.getElementById('todoDueDate').value = todo.dueDate || '';
document.getElementById('todoSource').value = todo.source || '';
document.getElementById('todoSourceUrl').value = todo.sourceUrl || '';
document.getElementById('todoDeleteBtn').style.display = 'block';
document.getElementById('todoSaveBtn').textContent = 'Salvează';
document.getElementById('todoModal').classList.add('active');
document.getElementById('todoText').focus();
}
function hideTodoModal() {
document.getElementById('todoModal').classList.remove('active');
}
async function saveTodoForm() {
const text = document.getElementById('todoText').value.trim();
if (!text) {
showToast('Textul este obligatoriu', 'error');
return;
}
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()
};
const editId = document.getElementById('todoEditId').value;
todosData.items.push(todo);
if (editId) {
// Edit existing
const todo = todosData.items.find(t => t.id === editId);
if (todo) {
todo.text = text;
todo.context = document.getElementById('todoContext').value.trim() || null;
todo.example = document.getElementById('todoExample').value.trim() || null;
todo.domain = document.getElementById('todoDomain').value;
todo.dueDate = document.getElementById('todoDueDate').value || null;
todo.source = document.getElementById('todoSource').value.trim() || null;
todo.sourceUrl = document.getElementById('todoSourceUrl').value.trim() || null;
todo.updatedAt = new Date().toISOString();
}
showToast('Todo actualizat');
} else {
// Add new
const todo = {
id: 'todo-' + Date.now(),
text: text,
context: document.getElementById('todoContext').value.trim() || null,
example: document.getElementById('todoExample').value.trim() || null,
domain: document.getElementById('todoDomain').value,
dueDate: document.getElementById('todoDueDate').value || new Date().toISOString().split('T')[0],
done: false,
doneAt: null,
source: document.getElementById('todoSource').value.trim() || 'manual',
sourceUrl: document.getElementById('todoSourceUrl').value.trim() || null,
createdAt: new Date().toISOString()
};
todosData.items.push(todo);
showToast('Todo adăugat');
}
hideTodoModal();
await saveTodos();
renderTodos();
showToast('Todo adăugat');
}
async function deleteTodo() {
const editId = document.getElementById('todoEditId').value;
if (!editId) return;
if (!confirm('Sigur ștergi acest todo?')) return;
todosData.items = todosData.items.filter(t => t.id !== editId);
hideTodoModal();
await saveTodos();
renderTodos();
showToast('Todo șters');
}
// ===== ISSUES =====
@@ -2028,13 +2168,6 @@
showToast(issue.status === 'done' ? 'Issue finalizat! ✓' : 'Issue redeschis');
}
function editIssue(id) {
const issue = issuesData.issues.find(i => i.id === id);
if (issue) {
alert(`Edit: ${issue.title}\n\n${issue.description || 'Fără descriere'}`);
}
}
// Filters
document.getElementById('issuesFilters').addEventListener('click', (e) => {
if (e.target.classList.contains('filter-btn')) {
@@ -2045,22 +2178,47 @@
}
});
// Modal
// Issue Modal
function showAddModal() {
document.getElementById('addModal').classList.add('active');
document.getElementById('issueTitle').focus();
}
function hideAddModal() {
document.getElementById('addModal').classList.remove('active');
document.getElementById('issueModalTitle').textContent = 'Issue nou';
document.getElementById('issueEditId').value = '';
document.getElementById('issueTitle').value = '';
document.getElementById('issueDesc').value = '';
document.getElementById('issueProgram').value = '';
document.getElementById('issueOwner').value = 'marius';
document.getElementById('issuePriority').value = 'urgent-important';
document.getElementById('issueDeadline').value = '';
document.getElementById('issueDeleteBtn').style.display = 'none';
document.getElementById('issueSaveBtn').textContent = 'Adaugă';
document.getElementById('issueModal').classList.add('active');
document.getElementById('issueTitle').focus();
}
function editIssue(id) {
const issue = issuesData.issues.find(i => i.id === id);
if (!issue) return;
document.getElementById('issueModalTitle').textContent = 'Editare issue';
document.getElementById('issueEditId').value = id;
document.getElementById('issueTitle').value = issue.title || '';
document.getElementById('issueDesc').value = issue.description || '';
document.getElementById('issueProgram').value = issue.program || '';
document.getElementById('issueOwner').value = issue.owner || 'marius';
document.getElementById('issuePriority').value = issue.priority || 'backlog';
document.getElementById('issueDeadline').value = issue.deadline || '';
document.getElementById('issueDeleteBtn').style.display = 'block';
document.getElementById('issueSaveBtn').textContent = 'Salvează';
document.getElementById('issueModal').classList.add('active');
document.getElementById('issueTitle').focus();
}
function hideIssueModal() {
document.getElementById('issueModal').classList.remove('active');
}
// Legacy alias
function hideAddModal() { hideIssueModal(); }
function populateProgramSelect() {
const select = document.getElementById('issueProgram');
select.innerHTML = '<option value="">— Selectează —</option>';
@@ -2071,32 +2229,67 @@
}
}
async function addIssue() {
async function saveIssue() {
const title = document.getElementById('issueTitle').value.trim();
if (!title) {
showToast('Titlul este obligatoriu', 'error');
return;
}
const newIssue = {
id: 'ROA-' + String(issuesData.issues.length + 1).padStart(3, '0'),
title: title,
description: document.getElementById('issueDesc').value.trim(),
program: document.getElementById('issueProgram').value,
owner: document.getElementById('issueOwner').value,
priority: document.getElementById('issuePriority').value,
status: 'todo',
created: new Date().toISOString(),
deadline: document.getElementById('issueDeadline').value || null
};
const editId = document.getElementById('issueEditId').value;
if (editId) {
// Edit existing
const issue = issuesData.issues.find(i => i.id === editId);
if (issue) {
issue.title = title;
issue.description = document.getElementById('issueDesc').value.trim();
issue.program = document.getElementById('issueProgram').value;
issue.owner = document.getElementById('issueOwner').value;
issue.priority = document.getElementById('issuePriority').value;
issue.deadline = document.getElementById('issueDeadline').value || null;
issue.updated = new Date().toISOString();
}
showToast('Issue actualizat!');
} else {
// Add new
const newIssue = {
id: 'ROA-' + String(issuesData.issues.length + 1).padStart(3, '0'),
title: title,
description: document.getElementById('issueDesc').value.trim(),
program: document.getElementById('issueProgram').value,
owner: document.getElementById('issueOwner').value,
priority: document.getElementById('issuePriority').value,
status: 'todo',
created: new Date().toISOString(),
deadline: document.getElementById('issueDeadline').value || null
};
issuesData.issues.unshift(newIssue);
showToast('Issue adăugat!');
}
issuesData.issues.unshift(newIssue);
hideAddModal();
hideIssueModal();
renderIssues();
updateIssuesCount();
await saveIssues();
showToast('Issue adăugat!');
}
async function deleteIssue() {
const editId = document.getElementById('issueEditId').value;
if (!editId) return;
if (!confirm('Sigur ștergi acest issue?')) return;
issuesData.issues = issuesData.issues.filter(i => i.id !== editId);
hideIssueModal();
renderIssues();
updateIssuesCount();
await saveIssues();
showToast('Issue șters');
}
// Legacy alias
async function addIssue() { await saveIssue(); }
async function saveIssues() {
issuesData.lastUpdated = new Date().toISOString();
@@ -2122,9 +2315,12 @@
setTimeout(() => toast.classList.remove('show'), 3000);
}
// Close modal on outside click
document.getElementById('addModal').addEventListener('click', (e) => {
if (e.target.id === 'addModal') hideAddModal();
// Close modals on outside click
document.getElementById('issueModal').addEventListener('click', (e) => {
if (e.target.id === 'issueModal') hideIssueModal();
});
document.getElementById('todoModal').addEventListener('click', (e) => {
if (e.target.id === 'todoModal') hideTodoModal();
});
// Init

View File

@@ -1,5 +1,5 @@
{
"lastUpdated": "2026-02-02T11:29:00Z",
"lastUpdated": "2026-02-02T12:17:34.010Z",
"items": [
{
"id": "prov-2026-02-02",
@@ -8,11 +8,11 @@
"example": "'Vreau să sun un client vechi' → Eforturi: 5 min pregătire, 10 min apel, posibil să zică nu, disconfort inițial. Îmi asum? Dacă da, sun acum. Dacă nu, aleg altceva mai mic.",
"domain": "self",
"dueDate": "2026-02-02",
"done": false,
"doneAt": null,
"done": true,
"doneAt": "2026-02-02T12:17:34.010Z",
"source": "Zoltan Vereș - Motivația Intrinsecă",
"sourceUrl": "https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/2026-02-02_zoltan-veres-motivatie-intrinseca-complet.md",
"createdAt": "2026-02-02T09:00:00Z"
}
]
}
}