Dashboard v2: remove old Kanban, add Status bar, collapsible sections
- Added Status bar from old kanban (ANAF, Git, Cron) - All sections now collapsible (Status, Activity, Issues) - Removed Kanban from navigation - Removed kanban.html file - Status bar collapsed by default
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: var(--space-5);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@@ -30,11 +30,131 @@
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Status bar */
|
||||
.status-bar {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
margin-bottom: var(--space-4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.status-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: linear-gradient(135deg, rgba(34, 197, 94, 0.15), rgba(22, 163, 74, 0.1));
|
||||
border-bottom: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.status-header:hover {
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
.status-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-weight: 600;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.status-title svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.status-summary {
|
||||
flex: 1;
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.status-toggle {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--text-muted);
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.status-bar.collapsed .status-toggle {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.status-bar.collapsed .status-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status-content {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
}
|
||||
|
||||
.status-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-4);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.status-label {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.status-value.ok { color: #22c55e; }
|
||||
.status-value.warning { color: #f59e0b; }
|
||||
.status-value.error { color: #ef4444; }
|
||||
|
||||
.status-time {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
margin-left: var(--space-1);
|
||||
}
|
||||
|
||||
.cron-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-sm);
|
||||
padding-top: var(--space-2);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.cron-label {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.cron-list {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.cron-done {
|
||||
color: var(--text-muted);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* Two-column dashboard */
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-5);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
@@ -57,6 +177,18 @@
|
||||
justify-content: space-between;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.panel-header:hover {
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
.panel-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
@@ -73,14 +205,37 @@
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.panel-toggle {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--text-muted);
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.panel.collapsed .panel-toggle {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.panel.collapsed .panel-body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.panel-actions {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.panel-count {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-elevated);
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: var(--space-3);
|
||||
max-height: 600px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -189,11 +344,11 @@
|
||||
|
||||
/* Issues panel */
|
||||
.issues-panel .panel-header {
|
||||
background: linear-gradient(135deg, rgba(34, 197, 94, 0.15), rgba(22, 163, 74, 0.1));
|
||||
background: linear-gradient(135deg, rgba(249, 115, 22, 0.15), rgba(234, 88, 12, 0.1));
|
||||
}
|
||||
|
||||
.issues-panel .panel-title svg {
|
||||
color: #22c55e;
|
||||
color: #f97316;
|
||||
}
|
||||
|
||||
.issues-filters {
|
||||
@@ -222,7 +377,7 @@
|
||||
}
|
||||
|
||||
.priority-group {
|
||||
margin-bottom: var(--space-4);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.priority-header {
|
||||
@@ -234,6 +389,11 @@
|
||||
color: var(--text-muted);
|
||||
margin-bottom: var(--space-2);
|
||||
cursor: pointer;
|
||||
padding: var(--space-1) 0;
|
||||
}
|
||||
|
||||
.priority-header:hover {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.priority-header svg {
|
||||
@@ -491,25 +651,6 @@
|
||||
margin-bottom: var(--space-3);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Old kanban link */
|
||||
.legacy-link {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.legacy-link:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.legacy-link svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -523,10 +664,6 @@
|
||||
<i data-lucide="layout-dashboard"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a href="kanban.html" class="nav-item">
|
||||
<i data-lucide="columns"></i>
|
||||
<span>Kanban</span>
|
||||
</a>
|
||||
<a href="notes.html" class="nav-item">
|
||||
<i data-lucide="file-text"></i>
|
||||
<span>Notes</span>
|
||||
@@ -544,18 +681,55 @@
|
||||
<main class="main">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Dashboard</h1>
|
||||
<p class="page-subtitle" id="lastUpdated">Echo Work · Productivitate și proiecte</p>
|
||||
<p class="page-subtitle">Echo Work · Productivitate și proiecte</p>
|
||||
</div>
|
||||
|
||||
<!-- Status Bar -->
|
||||
<div class="status-bar" id="statusBar">
|
||||
<div class="status-header" onclick="toggleSection('statusBar')">
|
||||
<div class="status-title">
|
||||
<i data-lucide="activity"></i>
|
||||
<span>Status</span>
|
||||
</div>
|
||||
<div class="status-summary" id="statusSummary">Se încarcă...</div>
|
||||
<i data-lucide="chevron-down" class="status-toggle"></i>
|
||||
</div>
|
||||
<div class="status-content">
|
||||
<div class="status-row">
|
||||
<div class="status-item">
|
||||
<span class="status-label">ANAF:</span>
|
||||
<span class="status-value" id="anafStatus">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Git:</span>
|
||||
<span class="status-value" id="gitStatus">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Raport:</span>
|
||||
<span class="status-value" id="lastReport">-</span>
|
||||
<span class="status-time" id="reportTime"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cron-row">
|
||||
<span class="cron-label">Cron azi:</span>
|
||||
<span class="cron-list" id="cronList">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
<!-- Activity Panel -->
|
||||
<div class="panel activity-panel">
|
||||
<div class="panel-header">
|
||||
<div class="panel-title">
|
||||
<i data-lucide="bot"></i>
|
||||
<span>Clawdbot Activity</span>
|
||||
<div class="panel activity-panel" id="activityPanel">
|
||||
<div class="panel-header" onclick="toggleSection('activityPanel')">
|
||||
<div class="panel-header-left">
|
||||
<i data-lucide="chevron-down" class="panel-toggle"></i>
|
||||
<div class="panel-title">
|
||||
<i data-lucide="bot"></i>
|
||||
<span>Activity</span>
|
||||
</div>
|
||||
<span class="panel-count" id="activityCount">0</span>
|
||||
</div>
|
||||
<div class="panel-actions">
|
||||
<div class="panel-actions" onclick="event.stopPropagation()">
|
||||
<button class="btn btn-secondary btn-sm" onclick="refreshActivity()" title="Refresh">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
</button>
|
||||
@@ -570,13 +744,17 @@
|
||||
</div>
|
||||
|
||||
<!-- Issues Panel -->
|
||||
<div class="panel issues-panel">
|
||||
<div class="panel-header">
|
||||
<div class="panel-title">
|
||||
<i data-lucide="check-square"></i>
|
||||
<span>Issues</span>
|
||||
<div class="panel issues-panel" id="issuesPanel">
|
||||
<div class="panel-header" onclick="toggleSection('issuesPanel')">
|
||||
<div class="panel-header-left">
|
||||
<i data-lucide="chevron-down" class="panel-toggle"></i>
|
||||
<div class="panel-title">
|
||||
<i data-lucide="check-square"></i>
|
||||
<span>Issues</span>
|
||||
</div>
|
||||
<span class="panel-count" id="issuesCount">0</span>
|
||||
</div>
|
||||
<div class="panel-actions">
|
||||
<div class="panel-actions" onclick="event.stopPropagation()">
|
||||
<button class="btn btn-primary btn-sm" onclick="showAddModal()">
|
||||
<i data-lucide="plus"></i>
|
||||
<span>Nou</span>
|
||||
@@ -676,6 +854,43 @@
|
||||
initTheme();
|
||||
lucide.createIcons();
|
||||
|
||||
// Collapsible sections
|
||||
function getCollapsedSections() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('collapsedSections') || '["statusBar"]');
|
||||
} catch { return ['statusBar']; }
|
||||
}
|
||||
|
||||
function setCollapsedSections(sections) {
|
||||
localStorage.setItem('collapsedSections', JSON.stringify(sections));
|
||||
}
|
||||
|
||||
function initCollapsedSections() {
|
||||
const collapsed = getCollapsedSections();
|
||||
collapsed.forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.classList.add('collapsed');
|
||||
});
|
||||
}
|
||||
|
||||
function toggleSection(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
|
||||
el.classList.toggle('collapsed');
|
||||
|
||||
const collapsed = getCollapsedSections();
|
||||
const idx = collapsed.indexOf(id);
|
||||
if (el.classList.contains('collapsed')) {
|
||||
if (idx === -1) collapsed.push(id);
|
||||
} else {
|
||||
if (idx > -1) collapsed.splice(idx, 1);
|
||||
}
|
||||
setCollapsedSections(collapsed);
|
||||
}
|
||||
|
||||
initCollapsedSections();
|
||||
|
||||
// Data
|
||||
let issuesData = null;
|
||||
let activityData = [];
|
||||
@@ -692,6 +907,64 @@
|
||||
|
||||
const priorityOrder = ['urgent-important', 'important', 'urgent', 'backlog'];
|
||||
|
||||
// Status
|
||||
async function loadStatus() {
|
||||
try {
|
||||
const response = await fetch('status.json?' + Date.now());
|
||||
if (response.ok) {
|
||||
const status = await response.json();
|
||||
updateStatus(status);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('No status.json');
|
||||
}
|
||||
updateCronList();
|
||||
}
|
||||
|
||||
function updateStatus(status) {
|
||||
const anafEl = document.getElementById('anafStatus');
|
||||
if (status.anaf) {
|
||||
anafEl.textContent = status.anaf.status;
|
||||
anafEl.className = 'status-value ' + (status.anaf.ok ? 'ok' : 'warning');
|
||||
}
|
||||
|
||||
const gitEl = document.getElementById('gitStatus');
|
||||
if (status.git) {
|
||||
gitEl.textContent = status.git.status;
|
||||
gitEl.className = 'status-value ' + (status.git.clean ? 'ok' : 'warning');
|
||||
}
|
||||
|
||||
const reportEl = document.getElementById('lastReport');
|
||||
const timeEl = document.getElementById('reportTime');
|
||||
if (status.lastReport) {
|
||||
reportEl.textContent = status.lastReport.summary || 'OK';
|
||||
timeEl.textContent = status.lastReport.time ? '(' + status.lastReport.time + ')' : '';
|
||||
}
|
||||
|
||||
const summaryEl = document.getElementById('statusSummary');
|
||||
let summary = [];
|
||||
if (status.anaf) summary.push('ANAF: ' + status.anaf.status);
|
||||
if (status.git) summary.push('Git: ' + status.git.status);
|
||||
summaryEl.textContent = summary.join(' · ') || 'OK';
|
||||
}
|
||||
|
||||
function updateCronList() {
|
||||
const now = new Date();
|
||||
const hour = now.getHours();
|
||||
|
||||
const jobs = [
|
||||
{ time: '07:30', name: 'coaching', done: hour >= 8 },
|
||||
{ time: '08:30', name: 'raport', done: hour >= 9 },
|
||||
{ time: '20:00', name: 'raport', done: hour >= 20 },
|
||||
{ time: '21:00', name: 'coaching', done: hour >= 21 }
|
||||
];
|
||||
|
||||
const listEl = document.getElementById('cronList');
|
||||
listEl.innerHTML = jobs.map(job =>
|
||||
`<span class="${job.done ? 'cron-done' : ''}">${job.time} ${job.name}</span>`
|
||||
).join(' · ');
|
||||
}
|
||||
|
||||
// Load data
|
||||
async function loadIssues() {
|
||||
try {
|
||||
@@ -699,6 +972,7 @@
|
||||
issuesData = await response.json();
|
||||
populateProgramSelect();
|
||||
renderIssues();
|
||||
updateIssuesCount();
|
||||
} catch (error) {
|
||||
console.error('Error loading issues:', error);
|
||||
document.getElementById('issuesBody').innerHTML = `
|
||||
@@ -711,14 +985,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
function updateIssuesCount() {
|
||||
if (!issuesData) return;
|
||||
const todoCount = issuesData.issues.filter(i => i.status !== 'done').length;
|
||||
document.getElementById('issuesCount').textContent = todoCount;
|
||||
}
|
||||
|
||||
async function loadActivity() {
|
||||
// For now, show static data. Later we can fetch from API.
|
||||
// For now, show static data. TODO: fetch from API
|
||||
activityData = [
|
||||
{ type: 'done', text: 'Răspuns întrebare D101', agent: 'Echo Work', time: '15:10' },
|
||||
{ type: 'done', text: 'Salut', agent: 'Echo Work', time: '12:55' },
|
||||
{ type: 'done', text: 'Identificare agent', agent: 'Echo Work', time: '12:48' }
|
||||
{ type: 'done', text: 'Propunere dashboard v2', agent: 'Echo Work', time: '15:23' },
|
||||
{ type: 'done', text: 'Fix notes.html loading', agent: 'Echo Work', time: '17:39' }
|
||||
];
|
||||
renderActivity();
|
||||
document.getElementById('activityCount').textContent = activityData.length;
|
||||
}
|
||||
|
||||
function refreshActivity() {
|
||||
@@ -892,12 +1173,12 @@
|
||||
}
|
||||
|
||||
renderIssues();
|
||||
updateIssuesCount();
|
||||
await saveIssues();
|
||||
showToast(issue.status === 'done' ? 'Issue finalizat! ✓' : 'Issue redeschis');
|
||||
}
|
||||
|
||||
function editIssue(id) {
|
||||
// TODO: implement edit modal
|
||||
const issue = issuesData.issues.find(i => i.id === id);
|
||||
if (issue) {
|
||||
alert(`Edit: ${issue.title}\n\n${issue.description || 'Fără descriere'}`);
|
||||
@@ -922,7 +1203,6 @@
|
||||
|
||||
function hideAddModal() {
|
||||
document.getElementById('addModal').classList.remove('active');
|
||||
// Clear form
|
||||
document.getElementById('issueTitle').value = '';
|
||||
document.getElementById('issueDesc').value = '';
|
||||
document.getElementById('issueProgram').value = '';
|
||||
@@ -963,6 +1243,7 @@
|
||||
issuesData.issues.unshift(newIssue);
|
||||
hideAddModal();
|
||||
renderIssues();
|
||||
updateIssuesCount();
|
||||
await saveIssues();
|
||||
showToast('Issue adăugat!');
|
||||
}
|
||||
@@ -997,6 +1278,7 @@
|
||||
});
|
||||
|
||||
// Init
|
||||
loadStatus();
|
||||
loadIssues();
|
||||
loadActivity();
|
||||
</script>
|
||||
|
||||
1003
kanban/kanban.html
1003
kanban/kanban.html
File diff suppressed because it is too large
Load Diff
1
kanban/notes-data
Symbolic link
1
kanban/notes-data
Symbolic link
@@ -0,0 +1 @@
|
||||
../notes
|
||||
@@ -482,7 +482,7 @@
|
||||
|
||||
const notesCache = {};
|
||||
let notesIndex = [];
|
||||
const notesBasePath = "youtube-notes/";
|
||||
const notesBasePath = "notes-data/";
|
||||
const indexPath = notesBasePath + "index.json";
|
||||
|
||||
// Load notes index from JSON
|
||||
|
||||
Reference in New Issue
Block a user