TODO-listaus tauluriveinä korttien sijaan, deadline-lajittelu
Tehtävät ja kehitysehdotukset näytetään nyt taulukkoriveinä (kuten tukitiketit) kortti-gridin sijaan. Tehtävät lajitellaan deadlinen mukaan (lähimmät ensin), valmiit loppuun. Myöhästyneet rivit punaisella ja pian erääntyvät keltaisella taustalla. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
32
index.html
32
index.html
@@ -462,7 +462,22 @@
|
|||||||
<button class="btn-primary" id="btn-add-task" style="display:none;">+ Uusi tehtävä</button>
|
<button class="btn-primary" id="btn-add-task" style="display:none;">+ Uusi tehtävä</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="tasks-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem;"></div>
|
<div class="table-card" style="overflow-x:auto;">
|
||||||
|
<table class="data-table" id="tasks-table" style="display:none;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Prioriteetti</th>
|
||||||
|
<th>Tehtävä</th>
|
||||||
|
<th>Vastuuhenkilö</th>
|
||||||
|
<th>Deadline</th>
|
||||||
|
<th>Tunnit</th>
|
||||||
|
<th>💬</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="tasks-tbody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
<p id="no-tasks" style="display:none;text-align:center;color:#aaa;padding:3rem 0;">Ei tehtäviä.</p>
|
<p id="no-tasks" style="display:none;text-align:center;color:#aaa;padding:3rem 0;">Ei tehtäviä.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -585,7 +600,20 @@
|
|||||||
<button class="btn-primary" id="btn-add-feature">+ Uusi ehdotus</button>
|
<button class="btn-primary" id="btn-add-feature">+ Uusi ehdotus</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="features-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem;"></div>
|
<div class="table-card" style="overflow-x:auto;">
|
||||||
|
<table class="data-table" id="features-table" style="display:none;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Ehdotus</th>
|
||||||
|
<th>Ehdottaja</th>
|
||||||
|
<th>Päivämäärä</th>
|
||||||
|
<th>💬</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="features-tbody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
<p id="no-features" style="display:none;text-align:center;color:#aaa;padding:3rem 0;">Ei kehitysehdotuksia.</p>
|
<p id="no-features" style="display:none;text-align:center;color:#aaa;padding:3rem 0;">Ei kehitysehdotuksia.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
92
script.js
92
script.js
@@ -3943,28 +3943,45 @@ function renderTasksList() {
|
|||||||
if (query) tasks = tasks.filter(t => (t.title||'').toLowerCase().includes(query) || (t.description||'').toLowerCase().includes(query) || (t.assigned_to||'').toLowerCase().includes(query));
|
if (query) tasks = tasks.filter(t => (t.title||'').toLowerCase().includes(query) || (t.description||'').toLowerCase().includes(query) || (t.assigned_to||'').toLowerCase().includes(query));
|
||||||
if (statusF) tasks = tasks.filter(t => t.status === statusF);
|
if (statusF) tasks = tasks.filter(t => t.status === statusF);
|
||||||
if (assignF) tasks = tasks.filter(t => t.assigned_to === assignF);
|
if (assignF) tasks = tasks.filter(t => t.assigned_to === assignF);
|
||||||
const grid = document.getElementById('tasks-grid');
|
|
||||||
const noEl = document.getElementById('no-tasks');
|
// Lajittelu: deadline lähimmät ensin (null-deadlinet loppuun), sitten prioriteetti
|
||||||
if (!grid) return;
|
|
||||||
if (!tasks.length) { grid.innerHTML = ''; if (noEl) noEl.style.display = ''; return; }
|
|
||||||
if (noEl) noEl.style.display = 'none';
|
|
||||||
const today = new Date().toISOString().slice(0,10);
|
const today = new Date().toISOString().slice(0,10);
|
||||||
grid.innerHTML = tasks.map(t => {
|
const prioOrder = { kiireellinen: 0, tarkea: 1, normaali: 2 };
|
||||||
|
const statusOrder = { avoin: 0, kaynnissa: 1, odottaa: 2, valmis: 3 };
|
||||||
|
tasks.sort((a, b) => {
|
||||||
|
// Valmiit aina loppuun
|
||||||
|
if ((a.status === 'valmis') !== (b.status === 'valmis')) return a.status === 'valmis' ? 1 : -1;
|
||||||
|
// Deadline: lähimmät ensin, null loppuun
|
||||||
|
const da = a.deadline || '9999-99-99';
|
||||||
|
const db = b.deadline || '9999-99-99';
|
||||||
|
if (da !== db) return da.localeCompare(db);
|
||||||
|
// Prioriteetti
|
||||||
|
const pa = prioOrder[a.priority] ?? 2;
|
||||||
|
const pb = prioOrder[b.priority] ?? 2;
|
||||||
|
if (pa !== pb) return pa - pb;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const tbody = document.getElementById('tasks-tbody');
|
||||||
|
const table = document.getElementById('tasks-table');
|
||||||
|
const noEl = document.getElementById('no-tasks');
|
||||||
|
if (!tbody) return;
|
||||||
|
if (!tasks.length) { tbody.innerHTML = ''; table.style.display = 'none'; if (noEl) noEl.style.display = ''; return; }
|
||||||
|
if (noEl) noEl.style.display = 'none';
|
||||||
|
table.style.display = 'table';
|
||||||
|
tbody.innerHTML = tasks.map(t => {
|
||||||
const overdue = t.deadline && t.status !== 'valmis' && t.deadline < today;
|
const overdue = t.deadline && t.status !== 'valmis' && t.deadline < today;
|
||||||
const soon = t.deadline && t.status !== 'valmis' && !overdue && t.deadline <= new Date(Date.now()+3*86400000).toISOString().slice(0,10);
|
const soon = t.deadline && t.status !== 'valmis' && !overdue && t.deadline <= new Date(Date.now()+3*86400000).toISOString().slice(0,10);
|
||||||
return `<div class="todo-card${overdue ? ' overdue' : ''}${soon ? ' deadline-soon' : ''}" onclick="openTaskRead('${t.id}')" style="cursor:pointer;">
|
const rowClass = overdue ? 'todo-row-overdue' : (soon ? 'todo-row-soon' : (t.status === 'valmis' ? 'todo-row-done' : ''));
|
||||||
<div style="display:flex;gap:0.4rem;margin-bottom:0.5rem;flex-wrap:wrap;">
|
return `<tr class="${rowClass}" onclick="openTaskRead('${t.id}')" style="cursor:pointer;">
|
||||||
<span class="priority-badge priority-${t.priority}">${todoPriorityLabels[t.priority]||t.priority}</span>
|
<td><span class="todo-status status-${t.status}">${todoStatusLabels[t.status]||t.status}</span></td>
|
||||||
<span class="todo-status status-${t.status}">${todoStatusLabels[t.status]||t.status}</span>
|
<td><span class="priority-badge priority-${t.priority}">${todoPriorityLabels[t.priority]||t.priority}</span></td>
|
||||||
</div>
|
<td><strong>${esc(t.title)}</strong></td>
|
||||||
<div style="font-weight:600;font-size:0.95rem;margin-bottom:0.4rem;">${esc(t.title)}</div>
|
<td>${t.assigned_to ? esc(t.assigned_to) : '<span style="color:#ccc;">—</span>'}</td>
|
||||||
<div style="font-size:0.82rem;color:#888;display:flex;gap:0.75rem;flex-wrap:wrap;margin-top:auto;">
|
<td class="nowrap">${t.deadline ? `<span${overdue ? ' style="color:#e74c3c;font-weight:600;"' : (soon ? ' style="color:#f39c12;font-weight:600;"' : '')}>${t.deadline}</span>` : '<span style="color:#ccc;">—</span>'}</td>
|
||||||
${t.assigned_to ? `<span>👤 ${esc(t.assigned_to)}</span>` : ''}
|
<td style="text-align:center;">${t.total_hours > 0 ? t.total_hours + 'h' : '<span style="color:#ccc;">—</span>'}</td>
|
||||||
${t.deadline ? `<span${overdue ? ' style="color:#e74c3c;font-weight:600;"' : ''}>📅 ${t.deadline}</span>` : ''}
|
<td style="text-align:center;">${t.comment_count > 0 ? t.comment_count : ''}</td>
|
||||||
${t.total_hours > 0 ? `<span>⏱ ${t.total_hours}h</span>` : ''}
|
</tr>`;
|
||||||
${t.comment_count > 0 ? `<span>💬 ${t.comment_count}</span>` : ''}
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4093,20 +4110,33 @@ function renderFeaturesList() {
|
|||||||
let features = todosData.filter(t => t.type === 'feature_request');
|
let features = todosData.filter(t => t.type === 'feature_request');
|
||||||
if (query) features = features.filter(t => (t.title||'').toLowerCase().includes(query) || (t.description||'').toLowerCase().includes(query));
|
if (query) features = features.filter(t => (t.title||'').toLowerCase().includes(query) || (t.description||'').toLowerCase().includes(query));
|
||||||
if (statusF) features = features.filter(t => t.status === statusF);
|
if (statusF) features = features.filter(t => t.status === statusF);
|
||||||
const grid = document.getElementById('features-grid');
|
|
||||||
|
// Lajittelu: uusimmat ensin, toteutetut/hylätyt loppuun
|
||||||
|
features.sort((a, b) => {
|
||||||
|
const doneA = (a.status === 'toteutettu' || a.status === 'hylatty') ? 1 : 0;
|
||||||
|
const doneB = (b.status === 'toteutettu' || b.status === 'hylatty') ? 1 : 0;
|
||||||
|
if (doneA !== doneB) return doneA - doneB;
|
||||||
|
return (b.luotu || '').localeCompare(a.luotu || '');
|
||||||
|
});
|
||||||
|
|
||||||
|
const tbody = document.getElementById('features-tbody');
|
||||||
|
const table = document.getElementById('features-table');
|
||||||
const noEl = document.getElementById('no-features');
|
const noEl = document.getElementById('no-features');
|
||||||
if (!grid) return;
|
if (!tbody) return;
|
||||||
if (!features.length) { grid.innerHTML = ''; if (noEl) noEl.style.display = ''; return; }
|
if (!features.length) { tbody.innerHTML = ''; table.style.display = 'none'; if (noEl) noEl.style.display = ''; return; }
|
||||||
if (noEl) noEl.style.display = 'none';
|
if (noEl) noEl.style.display = 'none';
|
||||||
grid.innerHTML = features.map(t => `<div class="todo-card" onclick="openFeatureRead('${t.id}')" style="cursor:pointer;">
|
table.style.display = 'table';
|
||||||
<div style="margin-bottom:0.5rem;"><span class="todo-status status-${t.status}">${todoStatusLabels[t.status]||t.status}</span></div>
|
tbody.innerHTML = features.map(t => {
|
||||||
<div style="font-weight:600;font-size:0.95rem;margin-bottom:0.4rem;">${esc(t.title)}</div>
|
const done = t.status === 'toteutettu' || t.status === 'hylatty';
|
||||||
<div style="font-size:0.82rem;color:#888;display:flex;gap:0.75rem;flex-wrap:wrap;margin-top:auto;">
|
const rowClass = done ? 'todo-row-done' : '';
|
||||||
<span>👤 ${esc(t.created_by)}</span>
|
return `<tr class="${rowClass}" onclick="openFeatureRead('${t.id}')" style="cursor:pointer;">
|
||||||
<span>${(t.luotu||'').slice(0,10)}</span>
|
<td><span class="todo-status status-${t.status}">${todoStatusLabels[t.status]||t.status}</span></td>
|
||||||
${t.comment_count > 0 ? `<span>💬 ${t.comment_count}</span>` : ''}
|
<td><strong>${esc(t.title)}</strong></td>
|
||||||
</div>
|
<td>${esc(t.created_by)}</td>
|
||||||
</div>`).join('');
|
<td class="nowrap">${(t.luotu||'').slice(0,10)}</td>
|
||||||
|
<td style="text-align:center;">${t.comment_count > 0 ? t.comment_count : ''}</td>
|
||||||
|
</tr>`;
|
||||||
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function showFeatureListView() {
|
function showFeatureListView() {
|
||||||
|
|||||||
@@ -1129,6 +1129,12 @@ span.empty {
|
|||||||
.todo-card:hover { transform:translateY(-2px); box-shadow:0 4px 12px rgba(0,0,0,0.1); border-color:var(--primary-color); }
|
.todo-card:hover { transform:translateY(-2px); box-shadow:0 4px 12px rgba(0,0,0,0.1); border-color:var(--primary-color); }
|
||||||
.todo-card.overdue { border-color:#e74c3c; }
|
.todo-card.overdue { border-color:#e74c3c; }
|
||||||
.todo-card.deadline-soon { border-color:#f39c12; }
|
.todo-card.deadline-soon { border-color:#f39c12; }
|
||||||
|
.todo-row-overdue { background:#fef2f2 !important; }
|
||||||
|
.todo-row-overdue:hover { background:#fde8e8 !important; }
|
||||||
|
.todo-row-soon { background:#fffbeb !important; }
|
||||||
|
.todo-row-soon:hover { background:#fef3c7 !important; }
|
||||||
|
.todo-row-done { opacity:0.55; }
|
||||||
|
.todo-row-done:hover { opacity:0.75; }
|
||||||
.priority-badge { display:inline-block; padding:2px 8px; border-radius:10px; font-size:0.72rem; font-weight:600; }
|
.priority-badge { display:inline-block; padding:2px 8px; border-radius:10px; font-size:0.72rem; font-weight:600; }
|
||||||
.priority-normaali { background:#e8ebf0; color:#555; }
|
.priority-normaali { background:#e8ebf0; color:#555; }
|
||||||
.priority-tarkea { background:#fff3cd; color:#856404; }
|
.priority-tarkea { background:#fff3cd; color:#856404; }
|
||||||
|
|||||||
Reference in New Issue
Block a user