Dokumentit: asiakaskohtaiset kansiot
- Dokumentit-tab näyttää ensin asiakaskansioruudukon (jokainen asiakas = oma kansio) - Klikkaamalla asiakaskansiota → avaa asiakkaan dokumenttilista - Takaisin-nappi palaa kansionäkymään - Asiakas-sarake poistettu dokumenttitaulusta (tarpeeton kansiossa) - Asiakas-dropdown piilotettu dokumentin luonnissa (valitaan automaattisesti) - Hakukenttä asiakkaiden suodatukseen kansionäkymässä - Kansiot järjestetty: ensin eniten dokumentteja, sitten aakkosittain - URL hash tuki: #documents/customerId Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
35
index.html
35
index.html
@@ -686,20 +686,37 @@
|
||||
|
||||
<!-- Tab: Dokumentit -->
|
||||
<div class="tab-content" id="tab-content-documents">
|
||||
<div class="sub-tab-bar" id="doc-sub-tab-bar">
|
||||
<button class="sub-tab active" data-doc-subtab="docs-all">Kaikki</button>
|
||||
<button class="sub-tab" data-doc-subtab="docs-kokoukset">Kokoukset</button>
|
||||
</div>
|
||||
<div class="main-container">
|
||||
<!-- Listanäkymä -->
|
||||
<div id="docs-list-view">
|
||||
<!-- Asiakaskansionäkymä -->
|
||||
<div id="docs-customer-folders-view">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;flex-wrap:wrap;gap:0.5rem;">
|
||||
<h3 style="color:var(--primary-dark);margin:0;" id="docs-list-title">📄 Dokumentit</h3>
|
||||
<h3 style="color:var(--primary-dark);margin:0;">📁 Dokumentit — Asiakaskansiot</h3>
|
||||
<input type="text" id="doc-folder-search" placeholder="Hae asiakasta..." style="max-width:250px;">
|
||||
</div>
|
||||
<div id="doc-customer-folders-grid" class="laitetilat-grid"></div>
|
||||
<div id="no-doc-folders" class="empty-state" style="display:none;">
|
||||
<div class="empty-icon">📁</div>
|
||||
<p>Ei asiakkaita joilla on dokumentteja.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Asiakkaan dokumenttilista -->
|
||||
<div id="docs-list-view" style="display:none;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;flex-wrap:wrap;gap:0.5rem;">
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;">
|
||||
<button class="btn-secondary" id="btn-docs-back-to-folders">← Takaisin</button>
|
||||
<h3 style="color:var(--primary-dark);margin:0;" id="docs-list-title">📄 Dokumentit</h3>
|
||||
</div>
|
||||
<div style="display:flex;gap:0.5rem;">
|
||||
<button class="btn-primary" id="btn-new-document">+ Uusi dokumentti</button>
|
||||
<button class="btn-primary" id="btn-new-meeting-note" style="display:none;">+ Uusi kokousmuistio</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Sub-tabit asiakkaan sisällä -->
|
||||
<div class="sub-tab-bar" id="doc-sub-tab-bar">
|
||||
<button class="sub-tab active" data-doc-subtab="docs-all">Kaikki</button>
|
||||
<button class="sub-tab" data-doc-subtab="docs-kokoukset">Kokoukset</button>
|
||||
</div>
|
||||
<!-- Kansionavigointi -->
|
||||
<div id="doc-folder-bar" style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.75rem;flex-wrap:wrap;">
|
||||
<span id="doc-breadcrumbs" style="font-size:0.9rem;color:#555;"></span>
|
||||
@@ -708,9 +725,6 @@
|
||||
<div id="doc-folders-grid" style="display:flex;gap:0.5rem;margin-bottom:0.75rem;flex-wrap:wrap;"></div>
|
||||
<div style="display:flex;gap:0.5rem;margin-bottom:1rem;flex-wrap:wrap;">
|
||||
<input type="text" id="doc-search" placeholder="Hae dokumentteja..." style="flex:1;min-width:150px;">
|
||||
<select id="doc-filter-customer" style="min-width:140px;">
|
||||
<option value="">Kaikki asiakkaat</option>
|
||||
</select>
|
||||
<select id="doc-filter-category" style="min-width:120px;">
|
||||
<option value="">Kaikki kategoriat</option>
|
||||
<option value="sopimus">Sopimus</option>
|
||||
@@ -726,7 +740,6 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Otsikko</th>
|
||||
<th>Asiakas</th>
|
||||
<th>Kategoria</th>
|
||||
<th>Versio</th>
|
||||
<th>Päivitetty</th>
|
||||
|
||||
167
script.js
167
script.js
@@ -307,7 +307,18 @@ function switchToTab(target, subTab) {
|
||||
switchSupportSubTab('support-tickets');
|
||||
}
|
||||
}
|
||||
if (target === 'documents') { loadDocuments(); showDocsListView(); if (subTab === 'kokoukset') switchDocSubTab('docs-kokoukset'); else switchDocSubTab('docs-all'); }
|
||||
if (target === 'documents') {
|
||||
if (subTab && subTab !== 'kokoukset') {
|
||||
// subTab on customer_id → avaa suoraan asiakkaan kansio
|
||||
currentDocCustomerId = subTab;
|
||||
loadDocuments();
|
||||
openDocCustomerFolder(subTab);
|
||||
} else {
|
||||
currentDocCustomerId = null;
|
||||
loadDocuments();
|
||||
showDocCustomerFoldersView();
|
||||
}
|
||||
}
|
||||
if (target === 'netadmin') loadNetadmin();
|
||||
if (target === 'users') loadUsers();
|
||||
if (target === 'settings') loadSettings();
|
||||
@@ -4833,19 +4844,31 @@ const docCategoryLabels = {
|
||||
muu: 'Muu'
|
||||
};
|
||||
|
||||
let currentDocCustomerId = null; // Valittu asiakaskansio
|
||||
|
||||
function showDocCustomerFoldersView() {
|
||||
document.getElementById('docs-customer-folders-view').style.display = '';
|
||||
document.getElementById('docs-list-view').style.display = 'none';
|
||||
document.getElementById('doc-read-view').style.display = 'none';
|
||||
document.getElementById('doc-edit-view').style.display = 'none';
|
||||
}
|
||||
|
||||
function showDocsListView() {
|
||||
document.getElementById('docs-customer-folders-view').style.display = 'none';
|
||||
document.getElementById('docs-list-view').style.display = '';
|
||||
document.getElementById('doc-read-view').style.display = 'none';
|
||||
document.getElementById('doc-edit-view').style.display = 'none';
|
||||
}
|
||||
|
||||
function showDocReadView() {
|
||||
document.getElementById('docs-customer-folders-view').style.display = 'none';
|
||||
document.getElementById('docs-list-view').style.display = 'none';
|
||||
document.getElementById('doc-read-view').style.display = '';
|
||||
document.getElementById('doc-edit-view').style.display = 'none';
|
||||
}
|
||||
|
||||
function showDocEditView() {
|
||||
document.getElementById('docs-customer-folders-view').style.display = 'none';
|
||||
document.getElementById('docs-list-view').style.display = 'none';
|
||||
document.getElementById('doc-read-view').style.display = 'none';
|
||||
document.getElementById('doc-edit-view').style.display = '';
|
||||
@@ -4855,36 +4878,115 @@ async function loadDocuments() {
|
||||
try {
|
||||
allDocuments = await apiCall('documents');
|
||||
try { allDocFolders = await apiCall('document_folders'); } catch (e2) { allDocFolders = []; }
|
||||
populateDocCustomerFilter();
|
||||
renderDocFolderBar();
|
||||
renderDocumentsList();
|
||||
if (currentDocCustomerId) {
|
||||
// Ollaan asiakkaan kansion sisällä → näytä dokumenttilista
|
||||
renderDocFolderBar();
|
||||
renderDocumentsList();
|
||||
} else {
|
||||
// Näytä asiakaskansiot
|
||||
renderDocCustomerFolders();
|
||||
}
|
||||
} catch (e) { console.error('Dokumenttien lataus epäonnistui:', e); }
|
||||
}
|
||||
|
||||
function populateDocCustomerFilter() {
|
||||
const sel = document.getElementById('doc-filter-customer');
|
||||
const existing = sel.value;
|
||||
// Kerää uniikki lista asiakkaista
|
||||
const customerMap = {};
|
||||
function renderDocCustomerFolders() {
|
||||
const grid = document.getElementById('doc-customer-folders-grid');
|
||||
const noFolders = document.getElementById('no-doc-folders');
|
||||
const search = (document.getElementById('doc-folder-search')?.value || '').toLowerCase().trim();
|
||||
|
||||
// Hae asiakasnimien map
|
||||
const customerNameMap = {};
|
||||
if (typeof customers !== 'undefined') {
|
||||
customers.forEach(c => { customerNameMap[c.id] = c.yritys; });
|
||||
}
|
||||
|
||||
// Laske dokumenttien määrä per asiakas
|
||||
const docCountMap = {};
|
||||
allDocuments.forEach(d => {
|
||||
if (d.customer_id) {
|
||||
customerMap[d.customer_id] = d.customer_id; // käytetään myöhemmin nimeä jos saatavilla
|
||||
docCountMap[d.customer_id] = (docCountMap[d.customer_id] || 0) + 1;
|
||||
}
|
||||
});
|
||||
// Käytä customers-listaa nimien näyttämiseen
|
||||
sel.innerHTML = '<option value="">Kaikki asiakkaat</option>';
|
||||
if (typeof customers !== 'undefined' && customers.length > 0) {
|
||||
|
||||
// Näytä kaikki asiakkaat joilla on dokumentteja TAI kaikki aktiiviset asiakkaat
|
||||
let folderList = [];
|
||||
if (typeof customers !== 'undefined') {
|
||||
customers.forEach(c => {
|
||||
sel.innerHTML += `<option value="${c.id}">${esc(c.yritys)}</option>`;
|
||||
});
|
||||
} else {
|
||||
Object.keys(customerMap).forEach(id => {
|
||||
sel.innerHTML += `<option value="${id}">${id}</option>`;
|
||||
const count = docCountMap[c.id] || 0;
|
||||
folderList.push({ id: c.id, name: c.yritys || c.id, count });
|
||||
});
|
||||
}
|
||||
sel.value = existing || '';
|
||||
// Lisää asiakkaat jotka ovat dokumenteissa mutta eivät customers-listassa
|
||||
Object.keys(docCountMap).forEach(custId => {
|
||||
if (!folderList.find(f => f.id === custId)) {
|
||||
folderList.push({ id: custId, name: customerNameMap[custId] || custId, count: docCountMap[custId] });
|
||||
}
|
||||
});
|
||||
|
||||
// Suodata hakusanalla
|
||||
if (search) {
|
||||
folderList = folderList.filter(f => f.name.toLowerCase().includes(search));
|
||||
}
|
||||
|
||||
// Järjestä: ensin ne joilla on dokumentteja (count desc), sitten aakkosjärjestys
|
||||
folderList.sort((a, b) => {
|
||||
if (b.count !== a.count) return b.count - a.count;
|
||||
return a.name.localeCompare(b.name, 'fi');
|
||||
});
|
||||
|
||||
if (folderList.length === 0) {
|
||||
grid.innerHTML = '';
|
||||
noFolders.style.display = '';
|
||||
return;
|
||||
}
|
||||
noFolders.style.display = 'none';
|
||||
|
||||
grid.innerHTML = folderList.map(f => `
|
||||
<div class="doc-customer-folder${f.count === 0 ? ' empty' : ''}" onclick="openDocCustomerFolder('${f.id}')">
|
||||
<div class="doc-customer-folder-icon">🏢</div>
|
||||
<div class="doc-customer-folder-name">${esc(f.name)}</div>
|
||||
<div class="doc-customer-folder-count">${f.count} ${f.count === 1 ? 'dokumentti' : 'dokumenttia'}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function openDocCustomerFolder(customerId) {
|
||||
currentDocCustomerId = customerId;
|
||||
currentDocFolderId = null;
|
||||
docSubTabMode = 'docs-all';
|
||||
|
||||
// Aseta otsikko
|
||||
const customerNameMap = {};
|
||||
if (typeof customers !== 'undefined') {
|
||||
customers.forEach(c => { customerNameMap[c.id] = c.yritys; });
|
||||
}
|
||||
const name = customerNameMap[customerId] || customerId;
|
||||
document.getElementById('docs-list-title').textContent = '📄 ' + name;
|
||||
|
||||
// Reset sub-tab
|
||||
document.querySelectorAll('#doc-sub-tab-bar .sub-tab').forEach(t => t.classList.remove('active'));
|
||||
const allBtn = document.querySelector('[data-doc-subtab="docs-all"]');
|
||||
if (allBtn) allBtn.classList.add('active');
|
||||
document.getElementById('btn-new-document').style.display = '';
|
||||
document.getElementById('btn-new-meeting-note').style.display = 'none';
|
||||
|
||||
showDocsListView();
|
||||
renderDocFolderBar();
|
||||
renderDocumentsList();
|
||||
window.location.hash = 'documents/' + customerId;
|
||||
}
|
||||
|
||||
function backToDocCustomerFolders() {
|
||||
currentDocCustomerId = null;
|
||||
currentDocFolderId = null;
|
||||
showDocCustomerFoldersView();
|
||||
renderDocCustomerFolders();
|
||||
window.location.hash = 'documents';
|
||||
}
|
||||
|
||||
document.getElementById('btn-docs-back-to-folders')?.addEventListener('click', backToDocCustomerFolders);
|
||||
document.getElementById('doc-folder-search')?.addEventListener('input', renderDocCustomerFolders);
|
||||
|
||||
// ---- Kansionavigointi ----
|
||||
|
||||
function renderDocFolderBar() {
|
||||
@@ -4974,11 +5076,15 @@ document.querySelectorAll('#doc-sub-tab-bar .sub-tab').forEach(btn => {
|
||||
|
||||
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;
|
||||
|
||||
// Suodata valitun asiakkaan perusteella
|
||||
if (currentDocCustomerId) {
|
||||
filtered = filtered.filter(d => d.customer_id === currentDocCustomerId);
|
||||
}
|
||||
|
||||
// Sub-tab suodatus: kokoukset = vain kokousmuistiot
|
||||
if (docSubTabMode === 'docs-kokoukset') {
|
||||
filtered = filtered.filter(d => d.category === 'kokousmuistio');
|
||||
@@ -5000,9 +5106,6 @@ function renderDocumentsList() {
|
||||
(d.description || '').toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
if (filterCustomer) {
|
||||
filtered = filtered.filter(d => d.customer_id === filterCustomer);
|
||||
}
|
||||
if (filterCategory && docSubTabMode !== 'docs-kokoukset') {
|
||||
filtered = filtered.filter(d => d.category === filterCategory);
|
||||
}
|
||||
@@ -5017,21 +5120,13 @@ function renderDocumentsList() {
|
||||
}
|
||||
noDocsEl.style.display = 'none';
|
||||
|
||||
// Hae asiakasnimien map
|
||||
const customerNameMap = {};
|
||||
if (typeof customers !== 'undefined') {
|
||||
customers.forEach(c => { customerNameMap[c.id] = c.yritys; });
|
||||
}
|
||||
|
||||
tbody.innerHTML = filtered.map(d => {
|
||||
const customerName = d.customer_id ? (customerNameMap[d.customer_id] || d.customer_id) : '<span style="color:#aaa;">Yleinen</span>';
|
||||
const catLabel = docCategoryLabels[d.category] || d.category || '-';
|
||||
const version = d.current_version || 0;
|
||||
const date = d.muokattu ? new Date(d.muokattu).toLocaleDateString('fi-FI') : '-';
|
||||
const author = d.version_author || d.created_by || '-';
|
||||
return `<tr onclick="openDocRead('${d.id}')" style="cursor:pointer;">
|
||||
<td><strong>${esc(d.title)}</strong></td>
|
||||
<td>${customerName}</td>
|
||||
<td><span class="doc-category cat-${d.category || 'muu'}">${catLabel}</span></td>
|
||||
<td style="text-align:center;">v${version}</td>
|
||||
<td>${date}</td>
|
||||
@@ -5041,7 +5136,6 @@ function renderDocumentsList() {
|
||||
}
|
||||
|
||||
document.getElementById('doc-search')?.addEventListener('input', renderDocumentsList);
|
||||
document.getElementById('doc-filter-customer')?.addEventListener('change', renderDocumentsList);
|
||||
document.getElementById('doc-filter-category')?.addEventListener('change', renderDocumentsList);
|
||||
|
||||
async function openDocRead(docId) {
|
||||
@@ -5267,7 +5361,7 @@ function openDocEdit(doc, forceCategory, forceCustomerId) {
|
||||
? (isMeeting ? 'Muokkaa kokousmuistiota' : 'Muokkaa dokumenttia')
|
||||
: (isMeeting ? 'Uusi kokousmuistio' : 'Uusi dokumentti');
|
||||
|
||||
// Täytä asiakas-dropdown
|
||||
// Aseta asiakas automaattisesti nykyisen kansion perusteella
|
||||
const custSel = document.getElementById('doc-edit-customer');
|
||||
custSel.innerHTML = '<option value="">Ei asiakasta (yleinen)</option>';
|
||||
if (typeof customers !== 'undefined') {
|
||||
@@ -5275,8 +5369,13 @@ function openDocEdit(doc, forceCategory, forceCustomerId) {
|
||||
custSel.innerHTML += `<option value="${c.id}" ${(forceCustomerId === c.id || doc?.customer_id === c.id) ? 'selected' : ''}>${esc(c.yritys)}</option>`;
|
||||
});
|
||||
}
|
||||
if (forceCustomerId) custSel.value = forceCustomerId;
|
||||
// Aseta customer_id: kansionäkymästä tai parametrista
|
||||
const effectiveCustomerId = forceCustomerId || currentDocCustomerId;
|
||||
if (effectiveCustomerId) custSel.value = effectiveCustomerId;
|
||||
else if (doc?.customer_id) custSel.value = doc.customer_id;
|
||||
// Piilota asiakas-dropdown kun ollaan asiakkaan kansiossa (automaattinen valinta)
|
||||
const custGroup = custSel.closest('.form-group');
|
||||
if (custGroup) custGroup.style.display = currentDocCustomerId ? 'none' : '';
|
||||
|
||||
// Toggle kokousmuistio vs tiedostokenttä
|
||||
toggleDocMeetingFields(cat);
|
||||
|
||||
40
style.css
40
style.css
@@ -1640,6 +1640,46 @@ span.empty {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Asiakaskansiot dokumentit-tabissa */
|
||||
.doc-customer-folder {
|
||||
background: white;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
padding: 1.25rem;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
transition: border-color 0.2s, box-shadow 0.2s, transform 0.15s;
|
||||
}
|
||||
.doc-customer-folder:hover {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 4px 12px rgba(15,52,96,0.12);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.doc-customer-folder.empty {
|
||||
opacity: 0.5;
|
||||
border-style: dashed;
|
||||
}
|
||||
.doc-customer-folder.empty:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
.doc-customer-folder-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.doc-customer-folder-name {
|
||||
font-weight: 600;
|
||||
color: var(--primary-dark);
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 0.25rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.doc-customer-folder-count {
|
||||
font-size: 0.8rem;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/* Asiakasprofiilin dokumentit */
|
||||
.customer-doc-item {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user