767 lines
27 KiB
HTML
767 lines
27 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ro">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Echo · Tasks</title>
|
|
<link rel="stylesheet" href="common.css">
|
|
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
|
<script src="swipe-nav.js"></script>
|
|
<style>
|
|
/* Page-specific styles */
|
|
.main {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: var(--space-5);
|
|
}
|
|
|
|
.page-header {
|
|
margin-bottom: var(--space-6);
|
|
}
|
|
|
|
.page-title {
|
|
font-size: var(--text-xl);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin-bottom: var(--space-1);
|
|
}
|
|
|
|
.page-subtitle {
|
|
font-size: var(--text-sm);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.search-bar {
|
|
max-width: 400px;
|
|
margin-bottom: var(--space-6);
|
|
}
|
|
|
|
/* Kanban board */
|
|
.board {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
gap: var(--space-5);
|
|
}
|
|
|
|
.column {
|
|
background: var(--bg-surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
min-height: 200px;
|
|
}
|
|
|
|
.column-header {
|
|
padding: var(--space-4);
|
|
border-bottom: 1px solid var(--border);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
.column-header:hover {
|
|
filter: brightness(1.1);
|
|
}
|
|
|
|
/* Column header colors */
|
|
.column.backlog .column-header {
|
|
background: linear-gradient(135deg, rgba(100, 100, 120, 0.3), rgba(80, 80, 100, 0.2));
|
|
border-left: 3px solid #6b7280;
|
|
}
|
|
|
|
.column.in-progress .column-header {
|
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.25), rgba(37, 99, 235, 0.15));
|
|
border-left: 3px solid #3b82f6;
|
|
}
|
|
|
|
.column.done .column-header {
|
|
background: linear-gradient(135deg, rgba(34, 197, 94, 0.25), rgba(22, 163, 74, 0.15));
|
|
border-left: 3px solid #22c55e;
|
|
}
|
|
|
|
.column-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
font-size: var(--text-sm);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.column-title svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
color: var(--text-muted);
|
|
transition: transform var(--transition-fast);
|
|
}
|
|
|
|
.column.collapsed .column-title svg {
|
|
transform: rotate(-90deg);
|
|
}
|
|
|
|
.column-body {
|
|
padding: var(--space-3);
|
|
}
|
|
|
|
.column.collapsed .column-body {
|
|
display: none;
|
|
}
|
|
|
|
.column.collapsed {
|
|
min-height: auto;
|
|
}
|
|
|
|
/* Tasks */
|
|
.task {
|
|
background: var(--bg-elevated);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-md);
|
|
padding: var(--space-3);
|
|
margin-bottom: var(--space-2);
|
|
cursor: grab;
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.task:hover {
|
|
border-color: var(--border-focus);
|
|
transform: translateY(-1px);
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.task.dragging {
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.task-title {
|
|
font-size: var(--text-sm);
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
margin-bottom: var(--space-1);
|
|
}
|
|
|
|
.task-desc {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-muted);
|
|
display: none;
|
|
margin-bottom: var(--space-2);
|
|
}
|
|
|
|
.task.expanded .task-desc {
|
|
display: block;
|
|
}
|
|
|
|
.task-footer {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
min-height: 24px;
|
|
position: relative;
|
|
}
|
|
|
|
.task-date {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-muted);
|
|
flex: 1;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
padding-right: var(--space-2);
|
|
}
|
|
|
|
.task-priority {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.priority-high { background: var(--error); }
|
|
.priority-medium { background: var(--warning); }
|
|
.priority-low { background: var(--success); }
|
|
|
|
.task-actions {
|
|
display: flex;
|
|
gap: var(--space-1);
|
|
opacity: 0;
|
|
transition: opacity var(--transition-fast);
|
|
}
|
|
|
|
.task:hover .task-actions {
|
|
opacity: 1;
|
|
}
|
|
|
|
.task-action {
|
|
padding: var(--space-1);
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.task-action:hover {
|
|
background: var(--bg-surface);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.task-action svg {
|
|
width: 14px;
|
|
height: 14px;
|
|
}
|
|
|
|
/* Add task */
|
|
.add-task-btn {
|
|
width: 100%;
|
|
padding: var(--space-3);
|
|
background: transparent;
|
|
border: 1px dashed var(--border);
|
|
border-radius: var(--radius-md);
|
|
color: var(--text-muted);
|
|
font-size: var(--text-sm);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--space-2);
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.add-task-btn:hover {
|
|
border-color: var(--accent);
|
|
color: var(--accent);
|
|
background: var(--accent-subtle);
|
|
}
|
|
|
|
.add-task-btn svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
|
|
.add-form {
|
|
display: none;
|
|
flex-direction: column;
|
|
gap: var(--space-2);
|
|
padding: var(--space-3);
|
|
background: var(--bg-elevated);
|
|
border-radius: var(--radius-md);
|
|
margin-top: var(--space-2);
|
|
}
|
|
|
|
.add-form.active {
|
|
display: flex;
|
|
}
|
|
|
|
.add-form-actions {
|
|
display: flex;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
/* Stats */
|
|
.stats {
|
|
display: flex;
|
|
gap: var(--space-6);
|
|
padding: var(--space-5);
|
|
background: var(--bg-surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
margin-top: var(--space-6);
|
|
justify-content: center;
|
|
}
|
|
|
|
.stat {
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: var(--text-xl);
|
|
font-weight: 700;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Drag over */
|
|
.column.drag-over {
|
|
background: var(--accent-subtle);
|
|
}
|
|
|
|
/* Toast */
|
|
.toast {
|
|
position: fixed;
|
|
bottom: var(--space-5);
|
|
left: 50%;
|
|
transform: translateX(-50%) translateY(100px);
|
|
background: var(--bg-elevated);
|
|
border: 1px solid var(--border);
|
|
padding: var(--space-3) var(--space-5);
|
|
border-radius: var(--radius-md);
|
|
font-size: var(--text-sm);
|
|
opacity: 0;
|
|
transition: all var(--transition-base);
|
|
z-index: 1000;
|
|
}
|
|
|
|
.toast.show {
|
|
transform: translateX(-50%) translateY(0);
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Hidden on search */
|
|
.task.hidden { display: none; }
|
|
|
|
@media (max-width: 768px) {
|
|
.board {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
.stats {
|
|
flex-wrap: wrap;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header class="header">
|
|
<a href="index.html" class="logo">
|
|
<i data-lucide="circle-dot"></i>
|
|
Echo
|
|
</a>
|
|
<nav class="nav">
|
|
<a href="index.html" class="nav-item active">
|
|
<i data-lucide="layout-list"></i>
|
|
<span>Tasks</span>
|
|
</a>
|
|
<a href="notes.html" class="nav-item">
|
|
<i data-lucide="file-text"></i>
|
|
<span>Notes</span>
|
|
</a>
|
|
<a href="files.html" class="nav-item">
|
|
<i data-lucide="folder"></i>
|
|
<span>Files</span>
|
|
</a>
|
|
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
|
|
<i data-lucide="sun" id="themeIcon"></i>
|
|
</button>
|
|
</nav>
|
|
</header>
|
|
|
|
<main class="main">
|
|
<div class="page-header">
|
|
<h1 class="page-title">Task Board</h1>
|
|
<p class="page-subtitle" id="lastUpdated">Se încarcă...</p>
|
|
</div>
|
|
|
|
<div class="search-bar">
|
|
<input type="text" class="input" id="searchInput" placeholder="Caută task-uri..." oninput="searchTasks()">
|
|
</div>
|
|
|
|
<div class="board" id="board"></div>
|
|
|
|
<div class="stats" id="stats"></div>
|
|
</main>
|
|
|
|
<div class="toast" id="toast"></div>
|
|
|
|
<script>
|
|
// Theme
|
|
function initTheme() {
|
|
const saved = localStorage.getItem('theme') || 'dark';
|
|
document.documentElement.setAttribute('data-theme', saved);
|
|
updateThemeIcon(saved);
|
|
}
|
|
|
|
function toggleTheme() {
|
|
const current = document.documentElement.getAttribute('data-theme') || 'dark';
|
|
const next = current === 'dark' ? 'light' : 'dark';
|
|
document.documentElement.setAttribute('data-theme', next);
|
|
localStorage.setItem('theme', next);
|
|
updateThemeIcon(next);
|
|
}
|
|
|
|
function updateThemeIcon(theme) {
|
|
const icon = document.getElementById('themeIcon');
|
|
if (icon) {
|
|
icon.setAttribute('data-lucide', theme === 'dark' ? 'sun' : 'moon');
|
|
lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
initTheme();
|
|
|
|
// Init Lucide icons
|
|
lucide.createIcons();
|
|
|
|
let tasksData = null;
|
|
let draggedTask = null;
|
|
let saveTimeout = null;
|
|
const DONE_LIMIT = 5;
|
|
let showAllDone = false;
|
|
|
|
// Collapsed columns state - default: only in-progress expanded
|
|
function getCollapsedColumns() {
|
|
try {
|
|
const saved = localStorage.getItem('collapsedColumns');
|
|
if (saved) return JSON.parse(saved);
|
|
return ['backlog', 'done'];
|
|
} catch { return ['backlog', 'done']; }
|
|
}
|
|
|
|
function setCollapsedColumns(cols) {
|
|
localStorage.setItem('collapsedColumns', JSON.stringify(cols));
|
|
}
|
|
|
|
function toggleColumnCollapse(columnId) {
|
|
const cols = getCollapsedColumns();
|
|
const idx = cols.indexOf(columnId);
|
|
if (idx > -1) cols.splice(idx, 1);
|
|
else cols.push(columnId);
|
|
setCollapsedColumns(cols);
|
|
renderBoard();
|
|
}
|
|
|
|
function toggleTaskExpand(e, taskEl) {
|
|
if (e.target.hasAttribute('contenteditable') || e.target.tagName === 'BUTTON') return;
|
|
taskEl.classList.toggle('expanded');
|
|
}
|
|
|
|
async function loadTasks() {
|
|
try {
|
|
const response = await fetch('tasks.json?' + Date.now());
|
|
tasksData = await response.json();
|
|
renderBoard();
|
|
} catch (error) {
|
|
showToast('Eroare la încărcare', 'error');
|
|
}
|
|
}
|
|
|
|
function searchTasks() {
|
|
const query = document.getElementById('searchInput').value.toLowerCase().trim();
|
|
document.querySelectorAll('.task').forEach(taskEl => {
|
|
const title = taskEl.querySelector('.task-title')?.textContent.toLowerCase() || '';
|
|
const desc = taskEl.querySelector('.task-desc')?.textContent.toLowerCase() || '';
|
|
taskEl.classList.toggle('hidden', query && !title.includes(query) && !desc.includes(query));
|
|
});
|
|
}
|
|
|
|
function renderBoard() {
|
|
const board = document.getElementById('board');
|
|
const stats = document.getElementById('stats');
|
|
const lastUpdated = document.getElementById('lastUpdated');
|
|
|
|
const date = new Date(tasksData.lastUpdated);
|
|
lastUpdated.textContent = `Actualizat: ${date.toLocaleString('ro-RO')}`;
|
|
|
|
board.innerHTML = '';
|
|
|
|
let totalTasks = 0, doneTasks = 0, inProgress = 0;
|
|
|
|
const icons = {
|
|
'backlog': 'inbox',
|
|
'in-progress': 'loader',
|
|
'done': 'check-circle'
|
|
};
|
|
|
|
tasksData.columns.forEach(column => {
|
|
column.tasks.sort((a, b) => {
|
|
const dateA = a.completed || a.created || '';
|
|
const dateB = b.completed || b.created || '';
|
|
return dateB.localeCompare(dateA);
|
|
});
|
|
|
|
totalTasks += column.tasks.length;
|
|
if (column.id === 'done') doneTasks = column.tasks.length;
|
|
if (column.id === 'in-progress') inProgress = column.tasks.length;
|
|
|
|
const isCollapsed = getCollapsedColumns().includes(column.id);
|
|
|
|
let visibleTasks = column.tasks;
|
|
let hasMore = false;
|
|
if (column.id === 'done' && !showAllDone && column.tasks.length > DONE_LIMIT) {
|
|
visibleTasks = column.tasks.slice(0, DONE_LIMIT);
|
|
hasMore = true;
|
|
}
|
|
|
|
const columnEl = document.createElement('div');
|
|
columnEl.className = `column ${column.id}${isCollapsed ? ' collapsed' : ''}`;
|
|
columnEl.dataset.columnId = column.id;
|
|
|
|
columnEl.innerHTML = `
|
|
<div class="column-header" onclick="toggleColumnCollapse('${column.id}')">
|
|
<div class="column-title">
|
|
<i data-lucide="chevron-down"></i>
|
|
${column.name}
|
|
</div>
|
|
<span class="badge">${column.tasks.length}</span>
|
|
</div>
|
|
<div class="column-body">
|
|
<div class="tasks-container" data-column-id="${column.id}">
|
|
${visibleTasks.map(task => renderTask(task)).join('')}
|
|
</div>
|
|
${hasMore ? `
|
|
<button class="add-task-btn" onclick="event.stopPropagation(); showAllDone=true; renderBoard();">
|
|
<i data-lucide="chevron-down"></i>
|
|
${column.tasks.length - DONE_LIMIT} mai multe
|
|
</button>
|
|
` : ''}
|
|
${column.id === 'done' && showAllDone && column.tasks.length > DONE_LIMIT ? `
|
|
<button class="add-task-btn" onclick="event.stopPropagation(); showAllDone=false; renderBoard();">
|
|
<i data-lucide="chevron-up"></i>
|
|
Arată mai puține
|
|
</button>
|
|
` : ''}
|
|
<button class="add-task-btn" onclick="event.stopPropagation(); showAddForm('${column.id}')">
|
|
<i data-lucide="plus"></i>
|
|
Adaugă task
|
|
</button>
|
|
<div class="add-form" id="form-${column.id}">
|
|
<input type="text" class="input" placeholder="Titlu..." id="title-${column.id}">
|
|
<textarea class="input" placeholder="Descriere..." id="desc-${column.id}" rows="2"></textarea>
|
|
<select class="input" id="priority-${column.id}">
|
|
<option value="medium">Medium</option>
|
|
<option value="high">High</option>
|
|
<option value="low">Low</option>
|
|
</select>
|
|
<div class="add-form-actions">
|
|
<button class="btn btn-secondary" onclick="hideAddForm('${column.id}')">Anulează</button>
|
|
<button class="btn btn-primary" onclick="addTask('${column.id}')">Adaugă</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
board.appendChild(columnEl);
|
|
|
|
const container = columnEl.querySelector('.tasks-container');
|
|
container.addEventListener('dragover', handleDragOver);
|
|
container.addEventListener('drop', handleDrop);
|
|
container.addEventListener('dragleave', handleDragLeave);
|
|
});
|
|
|
|
document.querySelectorAll('.task').forEach(task => {
|
|
task.addEventListener('dragstart', handleDragStart);
|
|
task.addEventListener('dragend', handleDragEnd);
|
|
});
|
|
|
|
stats.innerHTML = `
|
|
<div class="stat"><div class="stat-value">${totalTasks}</div><div class="stat-label">Total</div></div>
|
|
<div class="stat"><div class="stat-value">${inProgress}</div><div class="stat-label">În progres</div></div>
|
|
<div class="stat"><div class="stat-value">${doneTasks}</div><div class="stat-label">Finalizate</div></div>
|
|
`;
|
|
|
|
lucide.createIcons();
|
|
searchTasks();
|
|
}
|
|
|
|
function formatDateTime(isoStr) {
|
|
if (!isoStr) return '';
|
|
const d = new Date(isoStr);
|
|
if (isNaN(d.getTime())) return isoStr; // fallback for old format
|
|
const day = d.getDate().toString().padStart(2, '0');
|
|
const month = (d.getMonth() + 1).toString().padStart(2, '0');
|
|
const year = d.getFullYear();
|
|
const hours = d.getHours().toString().padStart(2, '0');
|
|
const mins = d.getMinutes().toString().padStart(2, '0');
|
|
const secs = d.getSeconds().toString().padStart(2, '0');
|
|
return `${day}/${month}/${year} ${hours}:${mins}:${secs}`;
|
|
}
|
|
|
|
function calcDuration(created, completed) {
|
|
if (!created || !completed) return '';
|
|
const start = new Date(created);
|
|
const end = new Date(completed);
|
|
if (isNaN(start.getTime()) || isNaN(end.getTime())) return '';
|
|
const diffMs = end - start;
|
|
if (diffMs < 0) return '';
|
|
const mins = Math.floor(diffMs / 60000);
|
|
if (mins < 60) return `${mins}m`;
|
|
const hours = Math.floor(mins / 60);
|
|
if (hours < 24) return `${hours}h ${mins % 60}m`;
|
|
const days = Math.floor(hours / 24);
|
|
return `${days}d ${hours % 24}h`;
|
|
}
|
|
|
|
function renderTask(task) {
|
|
const hasDesc = task.description && task.description.trim();
|
|
const startTime = formatDateTime(task.created);
|
|
const duration = calcDuration(task.created, task.completed);
|
|
const timeInfo = duration ? `${startTime} (${duration})` : startTime;
|
|
|
|
return `
|
|
<div class="task" draggable="true" data-task-id="${task.id}" onclick="toggleTaskExpand(event, this)">
|
|
<div class="task-title" contenteditable="true" onblur="updateTask('${task.id}', 'title', this.textContent)">${task.title}</div>
|
|
${hasDesc ? `<div class="task-desc" contenteditable="true" onblur="updateTask('${task.id}', 'description', this.textContent)">${task.description}</div>` : ''}
|
|
<div class="task-footer">
|
|
<span class="task-date">${timeInfo || task.completed || task.created}</span>
|
|
<div class="task-actions">
|
|
<button class="task-action" onclick="event.stopPropagation(); cyclePriority('${task.id}')" title="Prioritate">
|
|
<i data-lucide="flag"></i>
|
|
</button>
|
|
<button class="task-action" onclick="event.stopPropagation(); deleteTask('${task.id}')" title="Șterge">
|
|
<i data-lucide="trash-2"></i>
|
|
</button>
|
|
</div>
|
|
<span class="task-priority priority-${task.priority || 'medium'}" title="${task.priority || 'medium'}"></span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Drag & Drop
|
|
function handleDragStart(e) {
|
|
draggedTask = e.target;
|
|
e.target.classList.add('dragging');
|
|
e.dataTransfer.effectAllowed = 'move';
|
|
}
|
|
|
|
function handleDragEnd(e) {
|
|
e.target.classList.remove('dragging');
|
|
document.querySelectorAll('.column').forEach(col => col.classList.remove('drag-over'));
|
|
}
|
|
|
|
function handleDragOver(e) {
|
|
e.preventDefault();
|
|
e.dataTransfer.dropEffect = 'move';
|
|
e.currentTarget.closest('.column').classList.add('drag-over');
|
|
}
|
|
|
|
function handleDragLeave(e) {
|
|
e.currentTarget.closest('.column').classList.remove('drag-over');
|
|
}
|
|
|
|
function handleDrop(e) {
|
|
e.preventDefault();
|
|
const targetColumn = e.currentTarget.dataset.columnId;
|
|
const taskId = draggedTask.dataset.taskId;
|
|
|
|
for (const col of tasksData.columns) {
|
|
const taskIdx = col.tasks.findIndex(t => t.id === taskId);
|
|
if (taskIdx > -1) {
|
|
const [task] = col.tasks.splice(taskIdx, 1);
|
|
if (targetColumn === 'done' && !task.completed) {
|
|
task.completed = new Date().toISOString().split('T')[0];
|
|
} else if (targetColumn !== 'done') {
|
|
delete task.completed;
|
|
}
|
|
const targetCol = tasksData.columns.find(c => c.id === targetColumn);
|
|
targetCol.tasks.unshift(task);
|
|
break;
|
|
}
|
|
}
|
|
|
|
renderBoard();
|
|
scheduleSave();
|
|
}
|
|
|
|
function showAddForm(columnId) {
|
|
document.querySelectorAll('.add-form').forEach(f => f.classList.remove('active'));
|
|
document.getElementById(`form-${columnId}`).classList.add('active');
|
|
document.getElementById(`title-${columnId}`).focus();
|
|
}
|
|
|
|
function hideAddForm(columnId) {
|
|
document.getElementById(`form-${columnId}`).classList.remove('active');
|
|
}
|
|
|
|
function addTask(columnId) {
|
|
const title = document.getElementById(`title-${columnId}`).value.trim();
|
|
if (!title) return;
|
|
|
|
const desc = document.getElementById(`desc-${columnId}`).value.trim();
|
|
const priority = document.getElementById(`priority-${columnId}`).value;
|
|
|
|
const newTask = {
|
|
id: 'task-' + Date.now(),
|
|
title,
|
|
description: desc,
|
|
priority,
|
|
created: new Date().toISOString().split('T')[0]
|
|
};
|
|
|
|
const col = tasksData.columns.find(c => c.id === columnId);
|
|
col.tasks.unshift(newTask);
|
|
|
|
hideAddForm(columnId);
|
|
document.getElementById(`title-${columnId}`).value = '';
|
|
document.getElementById(`desc-${columnId}`).value = '';
|
|
|
|
renderBoard();
|
|
scheduleSave();
|
|
showToast('Task adăugat');
|
|
}
|
|
|
|
function updateTask(taskId, field, value) {
|
|
for (const col of tasksData.columns) {
|
|
const task = col.tasks.find(t => t.id === taskId);
|
|
if (task) {
|
|
task[field] = value.trim();
|
|
break;
|
|
}
|
|
}
|
|
scheduleSave();
|
|
}
|
|
|
|
function deleteTask(taskId) {
|
|
if (!confirm('Ștergi acest task?')) return;
|
|
for (const col of tasksData.columns) {
|
|
const idx = col.tasks.findIndex(t => t.id === taskId);
|
|
if (idx > -1) {
|
|
col.tasks.splice(idx, 1);
|
|
break;
|
|
}
|
|
}
|
|
renderBoard();
|
|
scheduleSave();
|
|
showToast('Task șters');
|
|
}
|
|
|
|
function cyclePriority(taskId) {
|
|
const priorities = ['low', 'medium', 'high'];
|
|
for (const col of tasksData.columns) {
|
|
const task = col.tasks.find(t => t.id === taskId);
|
|
if (task) {
|
|
const current = task.priority || 'medium';
|
|
const idx = priorities.indexOf(current);
|
|
task.priority = priorities[(idx + 1) % 3];
|
|
break;
|
|
}
|
|
}
|
|
renderBoard();
|
|
scheduleSave();
|
|
}
|
|
|
|
function scheduleSave() {
|
|
if (saveTimeout) clearTimeout(saveTimeout);
|
|
saveTimeout = setTimeout(saveTasks, 1000);
|
|
}
|
|
|
|
async function saveTasks() {
|
|
tasksData.lastUpdated = new Date().toISOString();
|
|
try {
|
|
const response = await fetch('/echo/api/files', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
path: 'kanban/tasks.json',
|
|
content: JSON.stringify(tasksData, null, 2)
|
|
})
|
|
});
|
|
if (!response.ok) throw new Error('Save failed');
|
|
} catch (error) {
|
|
showToast('Eroare la salvare', 'error');
|
|
}
|
|
}
|
|
|
|
function showToast(message, type = 'success') {
|
|
const toast = document.getElementById('toast');
|
|
toast.textContent = message;
|
|
toast.className = `toast show status-${type}`;
|
|
setTimeout(() => toast.classList.remove('show'), 3000);
|
|
}
|
|
|
|
loadTasks();
|
|
</script>
|
|
</body>
|
|
</html>
|