Ohjeet-moduuli: Confluence-tyylinen tietopankki asiakaspalvelijoille

Uusi moduuli "Ohjeet" jossa ylläpitäjä voi kirjoittaa ohjeita
asiakaspalvelijoille miten asioita tehdään.

Ominaisuudet:
- Korttipohjainen listanäkymä (grid) hakutoiminnolla ja kategoriasuodatuksella
- Markdown-editori toolbarilla (B, I, H2, H3, listat, linkit, koodi, lainaukset)
- Esikatselu-toggle muokkausnäkymässä
- Artikkelien lukunäkymä renderoitulla Markdownilla
- Kategorioiden hallinta (lisää/poista)
- Tagit ja kiinnitys (pinned) -toiminto
- Oikeushallinta: kaikki lukevat, admin luo/muokkaa/poistaa
- Moduuli näkyy/piiloutuu yrityskohtaisista asetuksista
- Muutokset kirjautuvat muutoslokiin

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 12:19:00 +02:00
parent f4f11505d2
commit 7c4060bfa8
5 changed files with 636 additions and 2 deletions

90
db.php
View File

@@ -419,6 +419,32 @@ function initDatabase(): void {
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
INDEX idx_company_customer (company_id, customer_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"CREATE TABLE IF NOT EXISTS guide_categories (
id VARCHAR(20) PRIMARY KEY,
company_id VARCHAR(50) NOT NULL,
nimi VARCHAR(255) NOT NULL,
sort_order INT DEFAULT 0,
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
INDEX idx_company (company_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"CREATE TABLE IF NOT EXISTS guides (
id VARCHAR(20) PRIMARY KEY,
company_id VARCHAR(50) NOT NULL,
category_id VARCHAR(20) DEFAULT NULL,
title VARCHAR(500) NOT NULL,
content LONGTEXT,
tags VARCHAR(500) DEFAULT '',
author VARCHAR(100) DEFAULT '',
pinned TINYINT(1) DEFAULT 0,
luotu DATETIME,
muokattu DATETIME NULL,
muokkaaja VARCHAR(100) DEFAULT '',
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
INDEX idx_company (company_id),
INDEX idx_category (category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
];
foreach ($tables as $i => $sql) {
@@ -951,6 +977,70 @@ function dbDeleteIpam(string $id): void {
_dbExecute("DELETE FROM ipam WHERE id = ?", [$id]);
}
// ==================== OHJEET (GUIDES) ====================
function dbLoadGuideCategories(string $companyId): array {
return _dbFetchAll("SELECT * FROM guide_categories WHERE company_id = ? ORDER BY sort_order, nimi", [$companyId]);
}
function dbSaveGuideCategory(string $companyId, array $cat): void {
_dbExecute("
INSERT INTO guide_categories (id, company_id, nimi, sort_order)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE nimi = VALUES(nimi), sort_order = VALUES(sort_order)
", [$cat['id'], $companyId, $cat['nimi'] ?? '', $cat['sort_order'] ?? 0]);
}
function dbDeleteGuideCategory(string $catId): void {
_dbExecute("DELETE FROM guide_categories WHERE id = ?", [$catId]);
}
function dbLoadGuides(string $companyId): array {
$rows = _dbFetchAll("
SELECT g.*, gc.nimi AS category_name
FROM guides g
LEFT JOIN guide_categories gc ON g.category_id = gc.id
WHERE g.company_id = ?
ORDER BY g.pinned DESC, g.muokattu DESC, g.luotu DESC
", [$companyId]);
foreach ($rows as &$r) {
$r['pinned'] = (bool)$r['pinned'];
}
return $rows;
}
function dbLoadGuide(string $guideId): ?array {
$rows = _dbFetchAll("
SELECT g.*, gc.nimi AS category_name
FROM guides g
LEFT JOIN guide_categories gc ON g.category_id = gc.id
WHERE g.id = ?
", [$guideId]);
if (empty($rows)) return null;
$r = $rows[0];
$r['pinned'] = (bool)$r['pinned'];
return $r;
}
function dbSaveGuide(string $companyId, array $g): void {
_dbExecute("
INSERT INTO guides (id, company_id, category_id, title, content, tags, author, pinned, luotu, muokattu, muokkaaja)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
category_id = VALUES(category_id), title = VALUES(title), content = VALUES(content),
tags = VALUES(tags), pinned = VALUES(pinned), muokattu = VALUES(muokattu), muokkaaja = VALUES(muokkaaja)
", [
$g['id'], $companyId, !empty($g['category_id']) ? $g['category_id'] : null,
$g['title'] ?? '', $g['content'] ?? '', $g['tags'] ?? '',
$g['author'] ?? '', $g['pinned'] ? 1 : 0,
$g['luotu'] ?? date('Y-m-d H:i:s'), $g['muokattu'] ?? null, $g['muokkaaja'] ?? ''
]);
}
function dbDeleteGuide(string $guideId): void {
_dbExecute("DELETE FROM guides WHERE id = ?", [$guideId]);
}
// ==================== LIIDIT ====================
function dbLoadLeads(string $companyId): array {