diff --git a/api.php b/api.php
index 98525d6..45d6596 100644
--- a/api.php
+++ b/api.php
@@ -4310,6 +4310,90 @@ switch ($action) {
}
break;
+ // ==================== DOKUMENTTIKANSIOT ====================
+
+ case 'document_folders':
+ requireAuth();
+ $companyId = requireCompany();
+ echo json_encode(dbLoadFolders($companyId));
+ break;
+
+ case 'document_folder_save':
+ requireAuth();
+ $companyId = requireCompany();
+ if ($method !== 'POST') break;
+ $input = json_decode(file_get_contents('php://input'), true);
+ if (empty($input['name'])) {
+ http_response_code(400);
+ echo json_encode(['error' => 'Kansion nimi puuttuu']);
+ break;
+ }
+ $input['created_by'] = currentUser();
+ $id = dbSaveFolder($companyId, $input);
+ echo json_encode(['id' => $id, 'name' => $input['name']]);
+ break;
+
+ case 'document_folder_delete':
+ requireAdmin();
+ $companyId = requireCompany();
+ if ($method !== 'POST') break;
+ $input = json_decode(file_get_contents('php://input'), true);
+ $result = dbDeleteFolder($companyId, $input['id'] ?? '');
+ echo json_encode(['ok' => $result]);
+ break;
+
+ case 'document_content_save':
+ requireAuth();
+ $companyId = requireCompany();
+ if ($method !== 'POST') break;
+ try {
+ $input = json_decode(file_get_contents('php://input'), true);
+ $docId = $input['document_id'] ?? '';
+ $doc = dbLoadDocument($docId);
+ if (!$doc || $doc['company_id'] !== $companyId) {
+ http_response_code(404);
+ echo json_encode(['error' => 'Dokumenttia ei löytynyt']);
+ break;
+ }
+ dbAddDocumentVersion($docId, [
+ 'content' => $input['content'] ?? '',
+ 'change_notes' => $input['change_notes'] ?? '',
+ 'created_by' => currentUser(),
+ 'filename' => '',
+ 'original_name' => '',
+ 'file_size' => strlen($input['content'] ?? ''),
+ 'mime_type' => 'text/plain'
+ ]);
+ $updated = dbLoadDocument($docId);
+ echo json_encode($updated);
+ } catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['error' => 'Tallennus epäonnistui: ' . $e->getMessage()]);
+ }
+ break;
+
+ case 'document_move':
+ requireAuth();
+ $companyId = requireCompany();
+ if ($method !== 'POST') break;
+ try {
+ $input = json_decode(file_get_contents('php://input'), true);
+ $docId = $input['document_id'] ?? '';
+ $folderId = $input['folder_id'] ?? null;
+ $doc = dbLoadDocument($docId);
+ if (!$doc || $doc['company_id'] !== $companyId) {
+ http_response_code(404);
+ echo json_encode(['error' => 'Dokumenttia ei löytynyt']);
+ break;
+ }
+ _dbExecute("UPDATE documents SET folder_id = ? WHERE id = ?", [$folderId ?: null, $docId]);
+ echo json_encode(['ok' => true]);
+ } catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['error' => 'Siirto epäonnistui: ' . $e->getMessage()]);
+ }
+ break;
+
// ==================== NETADMIN ====================
case 'netadmin_connections':
diff --git a/db.php b/db.php
index c6abb9d..e384968 100644
--- a/db.php
+++ b/db.php
@@ -542,6 +542,18 @@ function initDatabase(): void {
INDEX idx_document (document_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
+ "CREATE TABLE IF NOT EXISTS document_folders (
+ id VARCHAR(20) PRIMARY KEY,
+ company_id VARCHAR(50) NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ parent_id VARCHAR(20) DEFAULT NULL,
+ created_by VARCHAR(100) DEFAULT '',
+ luotu DATETIME,
+ FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
+ INDEX idx_company (company_id),
+ INDEX idx_parent (parent_id)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
+
"CREATE TABLE IF NOT EXISTS laitetilat (
id VARCHAR(20) PRIMARY KEY,
company_id VARCHAR(50) NOT NULL,
@@ -599,6 +611,8 @@ function initDatabase(): void {
"ALTER TABLE companies ADD COLUMN allowed_ips TEXT DEFAULT '' AFTER enabled_modules",
"ALTER TABLE todos ADD COLUMN category VARCHAR(30) DEFAULT '' AFTER priority",
"ALTER TABLE user_companies ADD COLUMN role VARCHAR(20) DEFAULT 'user' AFTER company_id",
+ "ALTER TABLE documents ADD COLUMN folder_id VARCHAR(20) DEFAULT NULL AFTER customer_id",
+ "ALTER TABLE document_versions ADD COLUMN content MEDIUMTEXT DEFAULT NULL AFTER mime_type",
];
foreach ($alters as $sql) {
try { $db->query($sql); } catch (\Throwable $e) { /* sarake on jo olemassa / jo ajettu */ }
@@ -1736,6 +1750,43 @@ function dbDeleteTodoSubtask(string $subtaskId): void {
_dbExecute("DELETE FROM todo_subtasks WHERE id = ?", [$subtaskId]);
}
+// ==================== DOKUMENTTIKANSIOT ====================
+
+function dbLoadFolders(string $companyId): array {
+ return _dbFetchAll("SELECT * FROM document_folders WHERE company_id = ? ORDER BY name", [$companyId]);
+}
+
+function dbSaveFolder(string $companyId, array $folder): string {
+ $id = $folder['id'] ?? generateId();
+ $now = date('Y-m-d H:i:s');
+ _dbExecute("
+ INSERT INTO document_folders (id, company_id, name, parent_id, created_by, luotu)
+ VALUES (:id, :companyId, :name, :parentId, :createdBy, :luotu)
+ ON DUPLICATE KEY UPDATE name = VALUES(name), parent_id = VALUES(parent_id)
+ ", [
+ 'id' => $id,
+ 'companyId' => $companyId,
+ 'name' => $folder['name'] ?? '',
+ 'parentId' => !empty($folder['parent_id']) ? $folder['parent_id'] : null,
+ 'createdBy' => $folder['created_by'] ?? '',
+ 'luotu' => $folder['luotu'] ?? $now
+ ]);
+ return $id;
+}
+
+function dbDeleteFolder(string $companyId, string $folderId): bool {
+ $folder = _dbFetchOne("SELECT parent_id FROM document_folders WHERE id = ? AND company_id = ?", [$folderId, $companyId]);
+ if (!$folder) return false;
+ // Siirrä kansion dokumentit ylätasolle
+ _dbExecute("UPDATE documents SET folder_id = ? WHERE folder_id = ? AND company_id = ?",
+ [$folder['parent_id'], $folderId, $companyId]);
+ // Siirrä alikansiot ylätasolle
+ _dbExecute("UPDATE document_folders SET parent_id = ? WHERE parent_id = ? AND company_id = ?",
+ [$folder['parent_id'], $folderId, $companyId]);
+ _dbExecute("DELETE FROM document_folders WHERE id = ? AND company_id = ?", [$folderId, $companyId]);
+ return true;
+}
+
// ==================== DOKUMENTIT ====================
function dbLoadDocuments(string $companyId, ?string $customerId = null): array {
@@ -1767,19 +1818,21 @@ function dbSaveDocument(string $companyId, array $doc): string {
$id = $doc['id'] ?? generateId();
$now = date('Y-m-d H:i:s');
_dbExecute("
- INSERT INTO documents (id, company_id, customer_id, title, description, category, current_version, created_by, luotu, muokattu, muokkaaja)
- VALUES (:id, :companyId, :customerId, :title, :description, :category, :currentVersion, :createdBy, :luotu, :muokattu, :muokkaaja)
+ INSERT INTO documents (id, company_id, customer_id, folder_id, title, description, category, current_version, created_by, luotu, muokattu, muokkaaja)
+ VALUES (:id, :companyId, :customerId, :folderId, :title, :description, :category, :currentVersion, :createdBy, :luotu, :muokattu, :muokkaaja)
ON DUPLICATE KEY UPDATE
title = VALUES(title),
description = VALUES(description),
category = VALUES(category),
customer_id = VALUES(customer_id),
+ folder_id = VALUES(folder_id),
muokattu = VALUES(muokattu),
muokkaaja = VALUES(muokkaaja)
", [
'id' => $id,
'companyId' => $companyId,
'customerId' => !empty($doc['customer_id']) ? $doc['customer_id'] : null,
+ 'folderId' => !empty($doc['folder_id']) ? $doc['folder_id'] : null,
'title' => $doc['title'] ?? '',
'description' => $doc['description'] ?? '',
'category' => $doc['category'] ?? 'muu',
@@ -1807,7 +1860,7 @@ function dbAddDocumentVersion(string $documentId, array $version): void {
$maxVersion = _dbFetchScalar("SELECT COALESCE(MAX(version_number), 0) FROM document_versions WHERE document_id = ?", [$documentId]);
$nextVersion = (int)$maxVersion + 1;
- _dbExecute("INSERT INTO document_versions (id, document_id, version_number, filename, original_name, file_size, mime_type, change_notes, created_by, luotu) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
+ _dbExecute("INSERT INTO document_versions (id, document_id, version_number, filename, original_name, file_size, mime_type, content, change_notes, created_by, luotu) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
$version['id'] ?? generateId(),
$documentId,
$nextVersion,
@@ -1815,6 +1868,7 @@ function dbAddDocumentVersion(string $documentId, array $version): void {
$version['original_name'] ?? '',
$version['file_size'] ?? 0,
$version['mime_type'] ?? '',
+ $version['content'] ?? null,
$version['change_notes'] ?? '',
$version['created_by'] ?? '',
$now
@@ -1836,7 +1890,7 @@ function dbRestoreDocumentVersion(string $documentId, string $versionId, string
$nextVersion = (int)$maxVersion + 1;
$newId = generateId();
- _dbExecute("INSERT INTO document_versions (id, document_id, version_number, filename, original_name, file_size, mime_type, change_notes, created_by, luotu) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
+ _dbExecute("INSERT INTO document_versions (id, document_id, version_number, filename, original_name, file_size, mime_type, content, change_notes, created_by, luotu) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
$newId,
$documentId,
$nextVersion,
@@ -1844,6 +1898,7 @@ function dbRestoreDocumentVersion(string $documentId, string $versionId, string
$oldVersion['original_name'],
$oldVersion['file_size'],
$oldVersion['mime_type'],
+ $oldVersion['content'] ?? null,
'Palautettu versiosta ' . $oldVersion['version_number'],
$user,
$now
diff --git a/index.html b/index.html
index a191e37..880a3cb 100644
--- a/index.html
+++ b/index.html
@@ -730,13 +730,26 @@
+
+
+
+
-
📄 Dokumentit
-
+
📄 Dokumentit
+
+
+
+
+
+
+
+
+
+
@@ -792,13 +806,31 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/script.js b/script.js
index 6cc09a0..b460fd0 100644
--- a/script.js
+++ b/script.js
@@ -284,7 +284,7 @@ function switchToTab(target, subTab) {
if (target === 'ohjeet') loadGuides();
if (target === 'todo') { loadTodos(); if (subTab) switchTodoSubTab(subTab); }
if (target === 'support') { loadTickets(); showTicketListView(); if (document.getElementById('ticket-auto-refresh').checked) startTicketAutoRefresh(); }
- if (target === 'documents') { loadDocuments(); showDocsListView(); }
+ if (target === 'documents') { loadDocuments(); showDocsListView(); if (subTab === 'kokoukset') switchDocSubTab('docs-kokoukset'); else switchDocSubTab('docs-all'); }
if (target === 'laitetilat') { loadLaitetilat(); showLaitetilatListView(); }
if (target === 'netadmin') loadNetadmin();
if (target === 'users') loadUsers();
@@ -597,6 +597,11 @@ function showDetail(id) {
Max 20 MB / tiedosto
+
+
Dokumentit
+
Ladataan...
+
+
`;
// Synkronoi prices-hidden tila detail-modaliin
@@ -604,6 +609,7 @@ function showDetail(id) {
detailModal.querySelector('.modal-content')?.classList.toggle('prices-hidden', !!pricesHidden);
detailModal.style.display = 'flex';
loadFiles(id);
+ loadCustomerDocuments(id);
document.getElementById('file-upload-input').addEventListener('change', async function () {
for (const file of this.files) {
const fd = new FormData();
@@ -620,6 +626,44 @@ function showDetail(id) {
});
}
+async function loadCustomerDocuments(customerId) {
+ const container = document.getElementById('customer-docs-list');
+ if (!container) return;
+ try {
+ const docs = await apiCall(`documents&customer_id=${customerId}`);
+ if (docs.length === 0) {
+ container.innerHTML = '
Ei dokumentteja.
';
+ return;
+ }
+ container.innerHTML = docs.map(d => {
+ const catLabel = docCategoryLabels[d.category] || d.category || 'Muu';
+ const date = d.muokattu ? new Date(d.muokattu).toLocaleDateString('fi-FI') : '';
+ return `
`;
+ }).join('');
+ } catch (e) {
+ container.innerHTML = '
Virhe ladattaessa dokumentteja.
';
+ }
+}
+
+window.openDocFromCustomer = async function(docId) {
+ detailModal.style.display = 'none';
+ switchToTab('documents');
+ try {
+ currentDocument = await apiCall(`document&id=${docId}`);
+ renderDocReadView();
+ showDocReadView();
+ } catch (e) { alert('Dokumentin avaus epäonnistui: ' + e.message); }
+};
+
+window.openDocEditForCustomer = function(customerId, forceCategory) {
+ detailModal.style.display = 'none';
+ switchToTab('documents');
+ openDocEdit(null, forceCategory || null, customerId);
+};
+
async function loadFiles(customerId) {
const fileList = document.getElementById('file-list');
if (!fileList) return;
@@ -4688,12 +4732,16 @@ function openFeatureSuggestion() {
let allDocuments = [];
let currentDocument = null;
+let allDocFolders = [];
+let currentDocFolderId = null; // null = root (kaikki)
+let docSubTabMode = 'docs-all'; // 'docs-all' | 'docs-kokoukset'
const docCategoryLabels = {
sopimus: 'Sopimus',
lasku: 'Lasku',
ohje: 'Ohje',
raportti: 'Raportti',
+ kokousmuistio: 'Kokousmuistio',
muu: 'Muu'
};
@@ -4718,7 +4766,9 @@ function showDocEditView() {
async function loadDocuments() {
try {
allDocuments = await apiCall('documents');
+ try { allDocFolders = await apiCall('document_folders'); } catch (e2) { allDocFolders = []; }
populateDocCustomerFilter();
+ renderDocFolderBar();
renderDocumentsList();
} catch (e) { console.error('Dokumenttien lataus epäonnistui:', e); }
}
@@ -4747,12 +4797,107 @@ function populateDocCustomerFilter() {
sel.value = existing || '';
}
+// ---- Kansionavigointi ----
+
+function renderDocFolderBar() {
+ const bc = document.getElementById('doc-breadcrumbs');
+ if (!bc) return;
+ // Piilotetaan kansiot kokoukset-subtabissa
+ const showFolders = docSubTabMode !== 'docs-kokoukset';
+ document.getElementById('doc-folder-bar').style.display = showFolders ? 'flex' : 'none';
+ document.getElementById('doc-folders-grid').style.display = showFolders ? 'flex' : 'none';
+ if (!showFolders) return;
+
+ let crumbs = `
📁 Kaikki`;
+ if (currentDocFolderId) {
+ const path = getDocFolderPath(currentDocFolderId);
+ path.forEach(f => {
+ crumbs += `
/${esc(f.name)}`;
+ });
+ }
+ bc.innerHTML = crumbs;
+
+ // Alikansiot
+ const subfolders = allDocFolders.filter(f => (f.parent_id || null) === currentDocFolderId);
+ const grid = document.getElementById('doc-folders-grid');
+ grid.innerHTML = subfolders.map(f =>
+ `
📁 ${esc(f.name)}
`
+ ).join('');
+}
+
+function getDocFolderPath(folderId) {
+ const path = [];
+ let current = folderId;
+ let safety = 20;
+ while (current && safety-- > 0) {
+ const folder = allDocFolders.find(f => f.id === current);
+ if (!folder) break;
+ path.unshift(folder);
+ current = folder.parent_id || null;
+ }
+ return path;
+}
+
+function navigateDocFolder(folderId) {
+ currentDocFolderId = folderId;
+ renderDocFolderBar();
+ renderDocumentsList();
+}
+
+// ---- Sub-tabit ----
+
+function switchDocSubTab(target) {
+ docSubTabMode = target;
+ document.querySelectorAll('#doc-sub-tab-bar .sub-tab').forEach(t => t.classList.remove('active'));
+ const btn = document.querySelector(`[data-doc-subtab="${target}"]`);
+ if (btn) btn.classList.add('active');
+
+ const isMeeting = target === 'docs-kokoukset';
+ document.getElementById('btn-new-document').style.display = isMeeting ? 'none' : '';
+ document.getElementById('btn-new-meeting-note').style.display = isMeeting ? '' : 'none';
+ document.getElementById('docs-list-title').textContent = isMeeting ? '📝 Kokoukset' : '📄 Dokumentit';
+
+ // Nollaa kansionavigointi kokoukset-tilassa
+ if (isMeeting) currentDocFolderId = null;
+
+ // Piilota suodattimet kokoukset-tilassa
+ document.getElementById('doc-filter-category').style.display = isMeeting ? 'none' : '';
+
+ renderDocFolderBar();
+ renderDocumentsList();
+
+ // URL hash
+ window.location.hash = isMeeting ? 'documents/kokoukset' : 'documents';
+}
+
+document.querySelectorAll('#doc-sub-tab-bar .sub-tab').forEach(btn => {
+ btn.addEventListener('click', () => switchDocSubTab(btn.dataset.docSubtab));
+});
+
+// ---- Dokumenttilista ----
+
function renderDocumentsList() {
const query = (document.getElementById('doc-search')?.value || '').toLowerCase().trim();
const filterCustomer = document.getElementById('doc-filter-customer')?.value || '';
const filterCategory = document.getElementById('doc-filter-category')?.value || '';
let filtered = allDocuments;
+
+ // Sub-tab suodatus: kokoukset = vain kokousmuistiot
+ if (docSubTabMode === 'docs-kokoukset') {
+ filtered = filtered.filter(d => d.category === 'kokousmuistio');
+ }
+
+ // Kansiosuodatus (vain "Kaikki"-tilassa)
+ if (docSubTabMode !== 'docs-kokoukset') {
+ if (currentDocFolderId !== null) {
+ filtered = filtered.filter(d => d.folder_id === currentDocFolderId);
+ } else {
+ // Juuritasolla: näytä vain ilman kansiota olevat
+ filtered = filtered.filter(d => !d.folder_id);
+ }
+ }
+
if (query) {
filtered = filtered.filter(d =>
(d.title || '').toLowerCase().includes(query) ||
@@ -4762,7 +4907,7 @@ function renderDocumentsList() {
if (filterCustomer) {
filtered = filtered.filter(d => d.customer_id === filterCustomer);
}
- if (filterCategory) {
+ if (filterCategory && docSubTabMode !== 'docs-kokoukset') {
filtered = filtered.filter(d => d.category === filterCategory);
}
@@ -4827,14 +4972,40 @@ function renderDocReadView() {
document.getElementById('doc-read-category').innerHTML = `
${docCategoryLabels[d.category] || d.category || 'Muu'}`;
document.getElementById('doc-read-version').textContent = `📌 Versio ${d.current_version || 0}`;
document.getElementById('doc-read-date').textContent = d.muokattu ? '📅 ' + new Date(d.muokattu).toLocaleDateString('fi-FI') : '';
- document.getElementById('doc-read-description').textContent = d.description || '';
+ const isMeeting = d.category === 'kokousmuistio';
+
+ // Kuvaus: kokousmuistioille näytetään osallistujat
+ if (isMeeting && d.description) {
+ document.getElementById('doc-read-description').textContent = 'Osallistujat: ' + d.description;
+ } else {
+ document.getElementById('doc-read-description').textContent = d.description || '';
+ }
// Admin-napit
const isAdmin = isCurrentUserAdmin();
document.getElementById('btn-doc-delete').style.display = isAdmin ? '' : 'none';
- // Latausnappi - piilota jos ei versioita
- document.getElementById('btn-doc-download').style.display = (d.current_version && d.current_version > 0) ? '' : 'none';
+ // Kokousmuistio vs tiedostopohjainen
+ const contentSection = document.getElementById('doc-read-content-section');
+ const inlineEditor = document.getElementById('doc-inline-editor');
+ const fileSection = document.getElementById('doc-file-section');
+ const uploadSection = document.getElementById('doc-upload-section');
+
+ if (isMeeting) {
+ // Näytä sisältö, piilota tiedosto-osiot
+ fileSection.style.display = 'none';
+ uploadSection.style.display = 'none';
+ inlineEditor.style.display = 'none';
+ contentSection.style.display = '';
+ const currentVersion = d.versions?.find(v => v.version_number === d.current_version);
+ document.getElementById('doc-read-content').textContent = currentVersion?.content || '(Tyhjä muistio)';
+ } else {
+ // Tiedostopohjainen: piilota kokousmuistio-osiot
+ contentSection.style.display = 'none';
+ inlineEditor.style.display = 'none';
+ fileSection.style.display = (d.current_version && d.current_version > 0) ? '' : 'none';
+ uploadSection.style.display = '';
+ }
// Versiohistoria
const vtbody = document.getElementById('doc-versions-tbody');
@@ -4844,14 +5015,18 @@ function renderDocReadView() {
vtbody.innerHTML = d.versions.map(v => {
const date = v.luotu ? new Date(v.luotu).toLocaleDateString('fi-FI', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : '-';
const isCurrent = v.version_number === d.current_version;
+ const sizeDisplay = isMeeting ? (v.content ? v.content.length + ' merkkiä' : '-') : formatFileSize(v.file_size || 0);
+ const downloadLink = isMeeting
+ ? `
`
+ : `
⬇️`;
return `
| v${v.version_number}${isCurrent ? ' ✓' : ''} |
${date} |
${esc(v.created_by || '-')} |
${esc(v.change_notes || '-')} |
- ${formatFileSize(v.file_size || 0)} |
+ ${sizeDisplay} |
- ⬇️
+ ${downloadLink}
${isAdmin && !isCurrent ? `` : ''}
|
`;
@@ -4924,40 +5099,134 @@ document.getElementById('btn-doc-edit-cancel')?.addEventListener('click', () =>
// Uusi dokumentti
document.getElementById('btn-new-document')?.addEventListener('click', () => { openDocEdit(null); });
+// Uusi kokousmuistio
+document.getElementById('btn-new-meeting-note')?.addEventListener('click', () => { openDocEdit(null, 'kokousmuistio'); });
-function openDocEdit(doc) {
+// Kokousmuistion inline-editori
+document.getElementById('btn-doc-edit-content')?.addEventListener('click', () => {
+ const d = currentDocument;
+ if (!d) return;
+ const currentVersion = d.versions?.find(v => v.version_number === d.current_version);
+ document.getElementById('doc-inline-content').value = currentVersion?.content || '';
+ document.getElementById('doc-inline-notes').value = '';
+ document.getElementById('doc-inline-editor').style.display = '';
+ document.getElementById('doc-read-content-section').style.display = 'none';
+});
+
+document.getElementById('btn-doc-cancel-content')?.addEventListener('click', () => {
+ document.getElementById('doc-inline-editor').style.display = 'none';
+ document.getElementById('doc-read-content-section').style.display = '';
+});
+
+document.getElementById('btn-doc-save-content')?.addEventListener('click', async () => {
+ if (!currentDocument) return;
+ const content = document.getElementById('doc-inline-content').value;
+ const notes = document.getElementById('doc-inline-notes').value || 'Muistiota päivitetty';
+ try {
+ currentDocument = await apiCall('document_content_save', 'POST', {
+ document_id: currentDocument.id,
+ content,
+ change_notes: notes
+ });
+ renderDocReadView();
+ loadDocuments();
+ } catch (e) { alert('Tallennus epäonnistui: ' + e.message); }
+});
+
+// Katso kokousmuistion vanhaa versiota
+window.viewMeetingVersion = function(versionId, versionNum) {
+ if (!currentDocument) return;
+ const v = currentDocument.versions?.find(x => x.id === versionId);
+ if (v) {
+ alert('Versio ' + versionNum + ':\n\n' + (v.content || '(Tyhjä)'));
+ }
+};
+
+// Uusi kansio
+document.getElementById('btn-new-folder')?.addEventListener('click', async () => {
+ const name = prompt('Kansion nimi:');
+ if (!name || !name.trim()) return;
+ try {
+ await apiCall('document_folder_save', 'POST', {
+ name: name.trim(),
+ parent_id: currentDocFolderId || null
+ });
+ await loadDocuments();
+ } catch (e) { alert('Kansion luonti epäonnistui: ' + e.message); }
+});
+
+function openDocEdit(doc, forceCategory, forceCustomerId) {
document.getElementById('doc-edit-id').value = doc?.id || '';
document.getElementById('doc-edit-name').value = doc?.title || '';
document.getElementById('doc-edit-description').value = doc?.description || '';
- document.getElementById('doc-edit-category').value = doc?.category || 'muu';
- document.getElementById('doc-edit-title').textContent = doc ? 'Muokkaa dokumenttia' : 'Uusi dokumentti';
+ const cat = forceCategory || doc?.category || 'muu';
+ document.getElementById('doc-edit-category').value = cat;
+ document.getElementById('doc-edit-folder-id').value = doc?.folder_id || currentDocFolderId || '';
+
+ const isMeeting = cat === 'kokousmuistio';
+ document.getElementById('doc-edit-title').textContent = doc
+ ? (isMeeting ? 'Muokkaa kokousmuistiota' : 'Muokkaa dokumenttia')
+ : (isMeeting ? 'Uusi kokousmuistio' : 'Uusi dokumentti');
// Täytä asiakas-dropdown
const custSel = document.getElementById('doc-edit-customer');
custSel.innerHTML = '
';
if (typeof customers !== 'undefined') {
customers.forEach(c => {
- custSel.innerHTML += `
`;
+ custSel.innerHTML += `
`;
});
}
- if (doc?.customer_id) custSel.value = doc.customer_id;
+ if (forceCustomerId) custSel.value = forceCustomerId;
+ else if (doc?.customer_id) custSel.value = doc.customer_id;
+
+ // Toggle kokousmuistio vs tiedostokenttä
+ toggleDocMeetingFields(cat);
+
+ // Kokousmuistio-kentät
+ if (isMeeting) {
+ const currentVersion = doc?.versions?.find(v => v.version_number === doc.current_version);
+ document.getElementById('doc-edit-content').value = currentVersion?.content || '';
+ document.getElementById('doc-edit-participants').value = doc?.description || '';
+ } else {
+ document.getElementById('doc-edit-content').value = '';
+ document.getElementById('doc-edit-participants').value = '';
+ }
// Piilota tiedostokenttä muokkaustilassa (versiot hoidetaan read-viewissä)
- document.getElementById('doc-edit-file').parentElement.style.display = doc ? 'none' : '';
+ if (!isMeeting) {
+ document.getElementById('doc-edit-file').parentElement.style.display = doc ? 'none' : '';
+ }
showDocEditView();
}
+function toggleDocMeetingFields(category) {
+ const isMeeting = category === 'kokousmuistio';
+ document.getElementById('doc-edit-meeting-fields').style.display = isMeeting ? '' : 'none';
+ document.getElementById('doc-edit-file').parentElement.style.display = isMeeting ? 'none' : '';
+ document.getElementById('doc-edit-desc-group').style.display = isMeeting ? 'none' : '';
+}
+
+document.getElementById('doc-edit-category')?.addEventListener('change', (e) => {
+ toggleDocMeetingFields(e.target.value);
+});
+
// Lomakkeen lähetys
document.getElementById('doc-edit-form')?.addEventListener('submit', async (e) => {
e.preventDefault();
const id = document.getElementById('doc-edit-id').value;
+ const cat = document.getElementById('doc-edit-category').value;
+ const isMeeting = cat === 'kokousmuistio';
+
const docData = {
id: id || undefined,
title: document.getElementById('doc-edit-name').value.trim(),
- description: document.getElementById('doc-edit-description').value.trim(),
- category: document.getElementById('doc-edit-category').value,
+ description: isMeeting
+ ? document.getElementById('doc-edit-participants').value.trim()
+ : document.getElementById('doc-edit-description').value.trim(),
+ category: cat,
customer_id: document.getElementById('doc-edit-customer').value || null,
+ folder_id: document.getElementById('doc-edit-folder-id').value || null,
created_by: currentUser?.username || ''
};
@@ -4967,18 +5236,30 @@ document.getElementById('doc-edit-form')?.addEventListener('submit', async (e) =
const saved = await apiCall('document_save', 'POST', docData);
const docId = saved.id;
- // Jos uusi dokumentti ja tiedosto valittu → lataa ensimmäinen versio
- const fileInput = document.getElementById('doc-edit-file');
- if (!id && fileInput.files.length > 0) {
- const fd = new FormData();
- fd.append('document_id', docId);
- fd.append('file', fileInput.files[0]);
- fd.append('change_notes', 'Ensimmäinen versio');
- const res = await fetch(`${API}?action=document_upload`, { method: 'POST', credentials: 'include', body: fd });
- const text = await res.text();
- let data;
- try { data = JSON.parse(text); } catch (err) { throw new Error('Tiedoston lataus epäonnistui'); }
- if (!res.ok) throw new Error(data.error || 'Virhe');
+ if (isMeeting) {
+ // Tallenna kokousmuistion sisältö ensimmäisenä versiona
+ const content = document.getElementById('doc-edit-content').value;
+ if (content || !id) {
+ await apiCall('document_content_save', 'POST', {
+ document_id: docId,
+ content: content,
+ change_notes: id ? 'Muistiota päivitetty' : 'Ensimmäinen versio'
+ });
+ }
+ } else {
+ // Jos uusi dokumentti ja tiedosto valittu → lataa ensimmäinen versio
+ const fileInput = document.getElementById('doc-edit-file');
+ if (!id && fileInput.files.length > 0) {
+ const fd = new FormData();
+ fd.append('document_id', docId);
+ fd.append('file', fileInput.files[0]);
+ fd.append('change_notes', 'Ensimmäinen versio');
+ const res = await fetch(`${API}?action=document_upload`, { method: 'POST', credentials: 'include', body: fd });
+ const text = await res.text();
+ let data;
+ try { data = JSON.parse(text); } catch (err) { throw new Error('Tiedoston lataus epäonnistui'); }
+ if (!res.ok) throw new Error(data.error || 'Virhe');
+ }
}
currentDocument = await apiCall(`document&id=${docId}`);
diff --git a/style.css b/style.css
index 4945534..9c6e576 100644
--- a/style.css
+++ b/style.css
@@ -1599,11 +1599,57 @@ span.empty {
.doc-category.cat-ohje { background: #d1fae5; color: #065f46; }
.doc-category.cat-raportti { background: #ede9fe; color: #5b21b6; }
.doc-category.cat-muu { background: #f3f4f6; color: #374151; }
+.doc-category.cat-kokousmuistio { background: #fce7f3; color: #9d174d; }
#doc-versions-table tbody tr:hover {
background: #f0f7ff !important;
}
+/* Dokumenttikansiot */
+.doc-folder-item {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ padding: 0.45rem 0.85rem;
+ background: #f3f4f6;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 0.85rem;
+ font-weight: 500;
+ color: #374151;
+ transition: background 0.15s, border-color 0.15s;
+}
+.doc-folder-item:hover { background: #e5e7eb; }
+
+#doc-breadcrumbs a { color: var(--primary-color); text-decoration: none; font-weight: 500; }
+#doc-breadcrumbs a:hover { text-decoration: underline; }
+#doc-breadcrumbs .bc-sep { color: #aaa; margin: 0 0.25rem; }
+
+#doc-inline-content {
+ border: 1px solid #d1d5db;
+ border-radius: 8px;
+ padding: 1rem;
+ font-size: 0.95rem;
+ resize: vertical;
+ width: 100%;
+ font-family: inherit;
+ line-height: 1.6;
+}
+
+/* Asiakasprofiilin dokumentit */
+.customer-doc-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0.5rem 0;
+ border-bottom: 1px solid #f0f0f0;
+ font-size: 0.85rem;
+}
+.customer-doc-item:last-child { border-bottom: none; }
+.customer-doc-item a { color: var(--primary-color); text-decoration: none; font-weight: 500; }
+.customer-doc-item a:hover { text-decoration: underline; }
+
/* ==================== LAITETILAT ==================== */
.laitetilat-grid {
display: grid;