Tehtäviin Tyyppi-kenttä (tekniikka, laskutus, myynti, asennus, muu)
Uusi category-sarake todosiin. Näkyy listassa badgena, lomakkeessa dropdownina ja lukunäkymässä. Tyypillä voi myös suodattaa listaa. Värikoodatut badget kullekin tyypille. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1
api.php
1
api.php
@@ -2254,6 +2254,7 @@ switch ($action) {
|
||||
'description' => $input['description'] ?? '',
|
||||
'status' => $input['status'] ?? ($type === 'task' ? 'avoin' : 'ehdotettu'),
|
||||
'priority' => $input['priority'] ?? 'normaali',
|
||||
'category' => $input['category'] ?? '',
|
||||
'assigned_to' => $input['assigned_to'] ?? '',
|
||||
'created_by' => $isNew ? currentUser() : ($input['created_by'] ?? currentUser()),
|
||||
'deadline' => $input['deadline'] ?? null,
|
||||
|
||||
11
db.php
11
db.php
@@ -454,6 +454,7 @@ function initDatabase(): void {
|
||||
description TEXT,
|
||||
status VARCHAR(30) NOT NULL DEFAULT 'avoin',
|
||||
priority VARCHAR(20) DEFAULT 'normaali',
|
||||
category VARCHAR(30) DEFAULT '',
|
||||
assigned_to VARCHAR(100) DEFAULT '',
|
||||
created_by VARCHAR(100) NOT NULL DEFAULT '',
|
||||
deadline DATE DEFAULT NULL,
|
||||
@@ -518,6 +519,7 @@ function initDatabase(): void {
|
||||
"ALTER TABLE mailboxes ADD COLUMN auto_reply_enabled BOOLEAN DEFAULT FALSE AFTER aktiivinen",
|
||||
"ALTER TABLE mailboxes ADD COLUMN auto_reply_body TEXT AFTER auto_reply_enabled",
|
||||
"ALTER TABLE companies ADD COLUMN allowed_ips TEXT DEFAULT '' AFTER enabled_modules",
|
||||
"ALTER TABLE todos ADD COLUMN category VARCHAR(30) DEFAULT '' AFTER priority",
|
||||
];
|
||||
foreach ($alters as $sql) {
|
||||
try { $db->query($sql); } catch (\Throwable $e) { /* sarake on jo olemassa / jo ajettu */ }
|
||||
@@ -1524,13 +1526,13 @@ function dbLoadTodo(string $todoId): ?array {
|
||||
|
||||
function dbSaveTodo(string $companyId, array $todo): void {
|
||||
_dbExecute("
|
||||
INSERT INTO todos (id, company_id, type, title, description, status, priority, assigned_to, created_by, deadline, luotu, muokattu, muokkaaja)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO todos (id, company_id, type, title, description, status, priority, category, assigned_to, created_by, deadline, luotu, muokattu, muokkaaja)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
title = VALUES(title), description = VALUES(description),
|
||||
status = VALUES(status), priority = VALUES(priority),
|
||||
assigned_to = VALUES(assigned_to), deadline = VALUES(deadline),
|
||||
muokattu = VALUES(muokattu), muokkaaja = VALUES(muokkaaja)
|
||||
category = VALUES(category), assigned_to = VALUES(assigned_to),
|
||||
deadline = VALUES(deadline), muokattu = VALUES(muokattu), muokkaaja = VALUES(muokkaaja)
|
||||
", [
|
||||
$todo['id'], $companyId,
|
||||
$todo['type'] ?? 'task',
|
||||
@@ -1538,6 +1540,7 @@ function dbSaveTodo(string $companyId, array $todo): void {
|
||||
$todo['description'] ?? '',
|
||||
$todo['status'] ?? 'avoin',
|
||||
$todo['priority'] ?? 'normaali',
|
||||
$todo['category'] ?? '',
|
||||
$todo['assigned_to'] ?? '',
|
||||
$todo['created_by'] ?? '',
|
||||
!empty($todo['deadline']) ? $todo['deadline'] : null,
|
||||
|
||||
22
index.html
22
index.html
@@ -456,6 +456,14 @@
|
||||
<option value="odottaa">Odottaa</option>
|
||||
<option value="valmis">Valmis</option>
|
||||
</select>
|
||||
<select id="todo-category-filter" style="padding:0.4rem 0.6rem;border-radius:6px;border:1px solid #ddd;font-size:0.85rem;">
|
||||
<option value="">Kaikki tyypit</option>
|
||||
<option value="tekniikka">Tekniikka</option>
|
||||
<option value="laskutus">Laskutus</option>
|
||||
<option value="myynti">Myynti</option>
|
||||
<option value="asennus">Asennus</option>
|
||||
<option value="muu">Muu</option>
|
||||
</select>
|
||||
<select id="todo-assigned-filter" style="padding:0.4rem 0.6rem;border-radius:6px;border:1px solid #ddd;font-size:0.85rem;">
|
||||
<option value="">Kaikki vastuuhenkilöt</option>
|
||||
</select>
|
||||
@@ -468,6 +476,7 @@
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Prioriteetti</th>
|
||||
<th>Tyyppi</th>
|
||||
<th>Tehtävä</th>
|
||||
<th>Vastuuhenkilö</th>
|
||||
<th>Deadline</th>
|
||||
@@ -539,7 +548,7 @@
|
||||
<label for="task-form-title">Otsikko *</label>
|
||||
<input type="text" id="task-form-title" required placeholder="Tehtävän otsikko">
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem;margin-bottom:1rem;">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:1rem;margin-bottom:1rem;">
|
||||
<div class="form-group">
|
||||
<label for="task-form-priority">Prioriteetti</label>
|
||||
<select id="task-form-priority">
|
||||
@@ -557,6 +566,17 @@
|
||||
<option value="valmis">Valmis</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="task-form-category">Tyyppi</label>
|
||||
<select id="task-form-category">
|
||||
<option value="">— Ei valittu —</option>
|
||||
<option value="tekniikka">Tekniikka</option>
|
||||
<option value="laskutus">Laskutus</option>
|
||||
<option value="myynti">Myynti</option>
|
||||
<option value="asennus">Asennus</option>
|
||||
<option value="muu">Muu</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="task-form-deadline">Deadline</label>
|
||||
<input type="date" id="task-form-deadline">
|
||||
|
||||
@@ -3898,6 +3898,7 @@ let currentTodoSubTab = 'tasks';
|
||||
|
||||
const todoStatusLabels = { avoin:'Avoin', kaynnissa:'Käynnissä', odottaa:'Odottaa', valmis:'Valmis', ehdotettu:'Ehdotettu', harkinnassa:'Harkinnassa', toteutettu:'Toteutettu', hylatty:'Hylätty' };
|
||||
const todoPriorityLabels = { normaali:'Normaali', tarkea:'Tärkeä', kiireellinen:'Kiireellinen' };
|
||||
const todoCategoryLabels = { tekniikka:'Tekniikka', laskutus:'Laskutus', myynti:'Myynti', asennus:'Asennus', muu:'Muu' };
|
||||
|
||||
function switchTodoSubTab(target) {
|
||||
currentTodoSubTab = target;
|
||||
@@ -3939,10 +3940,12 @@ function renderTasksList() {
|
||||
const query = (document.getElementById('todo-search-input')?.value || '').toLowerCase().trim();
|
||||
const statusF = document.getElementById('todo-status-filter')?.value || '';
|
||||
const assignF = document.getElementById('todo-assigned-filter')?.value || '';
|
||||
const catF = document.getElementById('todo-category-filter')?.value || '';
|
||||
let tasks = todosData.filter(t => t.type === 'task');
|
||||
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 (assignF) tasks = tasks.filter(t => t.assigned_to === assignF);
|
||||
if (catF) tasks = tasks.filter(t => t.category === catF);
|
||||
|
||||
// Lajittelu: deadline lähimmät ensin (null-deadlinet loppuun), sitten prioriteetti
|
||||
const today = new Date().toISOString().slice(0,10);
|
||||
@@ -3976,6 +3979,7 @@ function renderTasksList() {
|
||||
return `<tr class="${rowClass}" onclick="openTaskRead('${t.id}')" style="cursor:pointer;">
|
||||
<td><span class="todo-status status-${t.status}">${todoStatusLabels[t.status]||t.status}</span></td>
|
||||
<td><span class="priority-badge priority-${t.priority}">${todoPriorityLabels[t.priority]||t.priority}</span></td>
|
||||
<td>${t.category ? `<span class="todo-category cat-${t.category}">${todoCategoryLabels[t.category]||t.category}</span>` : '<span style="color:#ccc;">—</span>'}</td>
|
||||
<td><strong>${esc(t.title)}</strong></td>
|
||||
<td>${t.assigned_to ? esc(t.assigned_to) : '<span style="color:#ccc;">—</span>'}</td>
|
||||
<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>
|
||||
@@ -4009,6 +4013,7 @@ async function openTaskRead(id) {
|
||||
<option value="">— Ei —</option>
|
||||
</select>` : esc(t.assigned_to || '—')}</div>
|
||||
<div><strong style="font-size:0.78rem;color:#888;">Prioriteetti</strong><br>${todoPriorityLabels[t.priority]||t.priority}</div>
|
||||
<div><strong style="font-size:0.78rem;color:#888;">Tyyppi</strong><br>${t.category ? (todoCategoryLabels[t.category]||t.category) : '—'}</div>
|
||||
<div><strong style="font-size:0.78rem;color:#888;">Deadline</strong><br>${t.deadline || '—'}</div>`;
|
||||
// Populoi vastuuhenkilö-dropdown
|
||||
if (isAdmin) {
|
||||
@@ -4058,6 +4063,7 @@ async function openTaskEdit(id) {
|
||||
document.getElementById('task-form-title').value = t?.title || '';
|
||||
document.getElementById('task-form-priority').value = t?.priority || 'normaali';
|
||||
document.getElementById('task-form-status').value = t?.status || 'avoin';
|
||||
document.getElementById('task-form-category').value = t?.category || '';
|
||||
document.getElementById('task-form-deadline').value = t?.deadline || '';
|
||||
document.getElementById('task-form-desc').value = t?.description || '';
|
||||
document.getElementById('task-edit-title').textContent = t ? 'Muokkaa tehtävää' : 'Uusi tehtävä';
|
||||
@@ -4088,6 +4094,7 @@ document.getElementById('task-form')?.addEventListener('submit', async (e) => {
|
||||
title: document.getElementById('task-form-title').value.trim(),
|
||||
description: document.getElementById('task-form-desc').value.trim(),
|
||||
priority: document.getElementById('task-form-priority').value,
|
||||
category: document.getElementById('task-form-category').value,
|
||||
status: document.getElementById('task-form-status').value,
|
||||
deadline: document.getElementById('task-form-deadline').value || null,
|
||||
assigned_to: document.getElementById('task-form-assigned').value,
|
||||
@@ -4275,6 +4282,7 @@ async function deleteTimeEntry(entryId, todoId) {
|
||||
document.getElementById('todo-search-input')?.addEventListener('input', () => renderTasksList());
|
||||
document.getElementById('todo-status-filter')?.addEventListener('change', () => renderTasksList());
|
||||
document.getElementById('todo-assigned-filter')?.addEventListener('change', () => renderTasksList());
|
||||
document.getElementById('todo-category-filter')?.addEventListener('change', () => renderTasksList());
|
||||
document.getElementById('feature-search-input')?.addEventListener('input', () => renderFeaturesList());
|
||||
document.getElementById('feature-status-filter')?.addEventListener('change', () => renderFeaturesList());
|
||||
document.getElementById('btn-add-task')?.addEventListener('click', () => openTaskEdit(null));
|
||||
|
||||
@@ -1139,6 +1139,12 @@ span.empty {
|
||||
.priority-normaali { background:#e8ebf0; color:#555; }
|
||||
.priority-tarkea { background:#fff3cd; color:#856404; }
|
||||
.priority-kiireellinen { background:#fde8e8; color:#dc2626; }
|
||||
.todo-category { display:inline-block; padding:2px 8px; border-radius:10px; font-size:0.72rem; font-weight:600; }
|
||||
.cat-tekniikka { background:#e0f2fe; color:#0369a1; }
|
||||
.cat-laskutus { background:#fef3c7; color:#92400e; }
|
||||
.cat-myynti { background:#d1fae5; color:#065f46; }
|
||||
.cat-asennus { background:#ede9fe; color:#5b21b6; }
|
||||
.cat-muu { background:#f3f4f6; color:#6b7280; }
|
||||
.todo-status { display:inline-block; padding:2px 8px; border-radius:10px; font-size:0.72rem; font-weight:600; }
|
||||
.status-avoin { background:#e3f2fd; color:#1565c0; }
|
||||
.status-kaynnissa { background:#e8f5e9; color:#2e7d32; }
|
||||
|
||||
Reference in New Issue
Block a user