Update dashboard, memory, root (~6)
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user