Add ticket tags system, tag filtering, and auto-close feature

- Fix tickets API endpoint: add type, customer_name, customer_id, tags fields
- Add tags array to ticket data structure with add/remove UI
- Add tag filter input to toolbar and tag column in ticket list
- Add ticket_tags API endpoint for updating tags
- Add set_tags and auto_close_days actions to auto-rules
- Auto-close check runs on ticket list load, closes expired tickets
- Add tag CSS styles with editable tag badges in detail view

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 10:32:09 +02:00
parent f918952c3f
commit 562153e040
4 changed files with 170 additions and 1 deletions

66
api.php
View File

@@ -1430,6 +1430,22 @@ switch ($action) {
requireAuth();
$tickets = loadTickets();
// Palauta ilman viestisisältöjä (lista-näkymä)
// Auto-close tarkistus: sulje tiketit joiden auto_close_at on ohitettu
$now = date('Y-m-d H:i:s');
$autoCloseCount = 0;
foreach ($tickets as &$tc) {
if (!empty($tc['auto_close_at']) && $tc['auto_close_at'] <= $now && !in_array($tc['status'], ['suljettu'])) {
$tc['status'] = 'suljettu';
$tc['updated'] = $now;
$autoCloseCount++;
}
}
unset($tc);
if ($autoCloseCount > 0) {
saveTickets($tickets);
addLog('ticket_auto_close', '', '', "Automaattisulku: $autoCloseCount tikettiä");
}
$list = array_map(function($t) {
$msgCount = count($t['messages'] ?? []);
$lastMsg = $msgCount > 0 ? $t['messages'][$msgCount - 1] : null;
@@ -1439,7 +1455,12 @@ switch ($action) {
'from_email' => $t['from_email'],
'from_name' => $t['from_name'],
'status' => $t['status'],
'type' => $t['type'] ?? 'muu',
'assigned_to' => $t['assigned_to'] ?? '',
'customer_id' => $t['customer_id'] ?? '',
'customer_name' => $t['customer_name'] ?? '',
'tags' => $t['tags'] ?? [],
'auto_close_at' => $t['auto_close_at'] ?? '',
'created' => $t['created'],
'updated' => $t['updated'],
'message_count' => $msgCount,
@@ -1538,6 +1559,8 @@ switch ($action) {
'assigned_to' => '',
'customer_id' => '',
'customer_name' => '',
'tags' => [],
'auto_close_at' => '',
'created' => $email['date'],
'updated' => $email['date'],
'message_id' => $email['message_id'],
@@ -1564,6 +1587,16 @@ switch ($action) {
if ($match) {
if (!empty($rule['set_status'])) $ticket['status'] = $rule['set_status'];
if (!empty($rule['set_type'])) $ticket['type'] = $rule['set_type'];
if (!empty($rule['set_tags'])) {
$ruleTags = array_map('trim', explode(',', $rule['set_tags']));
$ticket['tags'] = array_values(array_unique(array_merge($ticket['tags'], $ruleTags)));
}
if (!empty($rule['auto_close_days'])) {
$days = intval($rule['auto_close_days']);
if ($days > 0) {
$ticket['auto_close_at'] = date('Y-m-d H:i:s', strtotime("+{$days} days"));
}
}
break; // First matching rule wins
}
}
@@ -1830,6 +1863,37 @@ switch ($action) {
echo json_encode(['success' => true]);
break;
case 'ticket_tags':
requireAuth();
if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? '';
$tags = $input['tags'] ?? [];
// Sanitize tags: trim, lowercase, remove empty
$tags = array_values(array_filter(array_map(function($t) {
return trim(strtolower($t));
}, $tags)));
$tickets = loadTickets();
$found = false;
foreach ($tickets as &$t) {
if ($t['id'] === $id) {
$t['tags'] = $tags;
$t['updated'] = date('Y-m-d H:i:s');
$found = true;
addLog('ticket_tags', $t['id'], $t['subject'], 'Tagit: ' . implode(', ', $tags));
echo json_encode($t);
break;
}
}
unset($t);
if (!$found) {
http_response_code(404);
echo json_encode(['error' => 'Tikettiä ei löydy']);
break;
}
saveTickets($tickets);
break;
case 'ticket_rules':
requireAuth();
$config = loadConfig();
@@ -1850,6 +1914,8 @@ switch ($action) {
'subject_contains' => trim($input['subject_contains'] ?? ''),
'set_status' => $input['set_status'] ?? '',
'set_type' => $input['set_type'] ?? '',
'set_tags' => trim($input['set_tags'] ?? ''),
'auto_close_days' => intval($input['auto_close_days'] ?? 0),
'enabled' => $input['enabled'] ?? true,
];