Lisää sähköpostiallekirjoitus per käyttäjä per postilaatikko
- Allekirjoitukset tallennetaan users.json:iin (signatures-objekti, avaimena mailbox_id) - Käyttäjälomakkeessa dynaamiset textareat jokaiselle postilaatikolle - Allekirjoitus liitetään automaattisesti sähköpostivastauksiin (ticket_reply) - Esikatselu näkyy tikettivastauslomakkeen alla - Muistiinpanoihin (ticket_note) ei lisätä allekirjoitusta - Uusi all_mailboxes endpoint palauttaa kaikki käyttäjän postilaatikot - check_auth ja login palauttavat nyt myös user_id ja signatures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
67
api.php
67
api.php
@@ -1067,6 +1067,7 @@ switch ($action) {
|
|||||||
'role' => $u['role'],
|
'role' => $u['role'],
|
||||||
'companies' => $companyList,
|
'companies' => $companyList,
|
||||||
'company_id' => $_SESSION['company_id'],
|
'company_id' => $_SESSION['company_id'],
|
||||||
|
'signatures' => $u['signatures'] ?? [],
|
||||||
]);
|
]);
|
||||||
$found = true;
|
$found = true;
|
||||||
break;
|
break;
|
||||||
@@ -1107,13 +1108,23 @@ switch ($action) {
|
|||||||
$companyList[] = ['id' => $comp['id'], 'nimi' => $comp['nimi']];
|
$companyList[] = ['id' => $comp['id'], 'nimi' => $comp['nimi']];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Hae allekirjoitukset
|
||||||
|
$userSignatures = [];
|
||||||
|
foreach ($users as $uu) {
|
||||||
|
if ($uu['id'] === $_SESSION['user_id']) {
|
||||||
|
$userSignatures = $uu['signatures'] ?? [];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'authenticated' => true,
|
'authenticated' => true,
|
||||||
|
'user_id' => $_SESSION['user_id'],
|
||||||
'username' => $_SESSION['username'],
|
'username' => $_SESSION['username'],
|
||||||
'nimi' => $_SESSION['nimi'],
|
'nimi' => $_SESSION['nimi'],
|
||||||
'role' => $_SESSION['role'],
|
'role' => $_SESSION['role'],
|
||||||
'companies' => $companyList,
|
'companies' => $companyList,
|
||||||
'company_id' => $_SESSION['company_id'] ?? '',
|
'company_id' => $_SESSION['company_id'] ?? '',
|
||||||
|
'signatures' => $userSignatures,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
echo json_encode(['authenticated' => false]);
|
echo json_encode(['authenticated' => false]);
|
||||||
@@ -1232,6 +1243,12 @@ switch ($action) {
|
|||||||
$allCompanies = loadCompanies();
|
$allCompanies = loadCompanies();
|
||||||
$validIds = array_column($allCompanies, 'id');
|
$validIds = array_column($allCompanies, 'id');
|
||||||
$companies = array_values(array_filter($companies, fn($c) => in_array($c, $validIds)));
|
$companies = array_values(array_filter($companies, fn($c) => in_array($c, $validIds)));
|
||||||
|
$signatures = [];
|
||||||
|
if (isset($input['signatures']) && is_array($input['signatures'])) {
|
||||||
|
foreach ($input['signatures'] as $mbId => $sig) {
|
||||||
|
$signatures[(string)$mbId] = (string)$sig;
|
||||||
|
}
|
||||||
|
}
|
||||||
$newUser = [
|
$newUser = [
|
||||||
'id' => generateId(),
|
'id' => generateId(),
|
||||||
'username' => $username,
|
'username' => $username,
|
||||||
@@ -1240,6 +1257,7 @@ switch ($action) {
|
|||||||
'email' => $email,
|
'email' => $email,
|
||||||
'role' => $role,
|
'role' => $role,
|
||||||
'companies' => $companies,
|
'companies' => $companies,
|
||||||
|
'signatures' => $signatures,
|
||||||
'luotu' => date('Y-m-d H:i:s'),
|
'luotu' => date('Y-m-d H:i:s'),
|
||||||
];
|
];
|
||||||
$users[] = $newUser;
|
$users[] = $newUser;
|
||||||
@@ -1269,6 +1287,13 @@ switch ($action) {
|
|||||||
if (!empty($input['password'])) {
|
if (!empty($input['password'])) {
|
||||||
$u['password_hash'] = password_hash($input['password'], PASSWORD_DEFAULT);
|
$u['password_hash'] = password_hash($input['password'], PASSWORD_DEFAULT);
|
||||||
}
|
}
|
||||||
|
if (isset($input['signatures']) && is_array($input['signatures'])) {
|
||||||
|
$sigs = [];
|
||||||
|
foreach ($input['signatures'] as $mbId => $sig) {
|
||||||
|
$sigs[(string)$mbId] = (string)$sig;
|
||||||
|
}
|
||||||
|
$u['signatures'] = $sigs;
|
||||||
|
}
|
||||||
$found = true;
|
$found = true;
|
||||||
addLog('user_update', '', '', "Muokkasi käyttäjää: {$u['username']}");
|
addLog('user_update', '', '', "Muokkasi käyttäjää: {$u['username']}");
|
||||||
// Päivitä sessio jos muokattiin kirjautunutta käyttäjää
|
// Päivitä sessio jos muokattiin kirjautunutta käyttäjää
|
||||||
@@ -2022,8 +2047,20 @@ switch ($action) {
|
|||||||
$replyMailbox = $companyConf['mailboxes'][0];
|
$replyMailbox = $companyConf['mailboxes'][0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hae käyttäjän allekirjoitus tälle postilaatikolle
|
||||||
|
$mailboxId = $t['mailbox_id'] ?? '';
|
||||||
|
$signature = '';
|
||||||
|
$usersForSig = loadUsers();
|
||||||
|
foreach ($usersForSig as $sigUser) {
|
||||||
|
if ($sigUser['id'] === $_SESSION['user_id']) {
|
||||||
|
$signature = trim($sigUser['signatures'][$mailboxId] ?? '');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$emailBody = $signature ? $body . "\n\n-- \n" . $signature : $body;
|
||||||
|
|
||||||
$subject = 'Re: ' . $t['subject'];
|
$subject = 'Re: ' . $t['subject'];
|
||||||
$sent = sendTicketMail($t['from_email'], $subject, $body, $lastMsgId, trim($allRefs), $replyMailbox);
|
$sent = sendTicketMail($t['from_email'], $subject, $emailBody, $lastMsgId, trim($allRefs), $replyMailbox);
|
||||||
|
|
||||||
if (!$sent) {
|
if (!$sent) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
@@ -2031,13 +2068,13 @@ switch ($action) {
|
|||||||
break 2;
|
break 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add reply to ticket
|
// Add reply to ticket (tallennetaan allekirjoituksen kanssa)
|
||||||
$reply = [
|
$reply = [
|
||||||
'id' => generateId(),
|
'id' => generateId(),
|
||||||
'type' => 'reply_out',
|
'type' => 'reply_out',
|
||||||
'from' => currentUser(),
|
'from' => currentUser(),
|
||||||
'from_name' => $_SESSION['nimi'] ?? currentUser(),
|
'from_name' => $_SESSION['nimi'] ?? currentUser(),
|
||||||
'body' => $body,
|
'body' => $emailBody,
|
||||||
'timestamp' => date('Y-m-d H:i:s'),
|
'timestamp' => date('Y-m-d H:i:s'),
|
||||||
'message_id' => '',
|
'message_id' => '',
|
||||||
];
|
];
|
||||||
@@ -2399,6 +2436,30 @@ switch ($action) {
|
|||||||
echo json_encode(loadCompanies());
|
echo json_encode(loadCompanies());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'all_mailboxes':
|
||||||
|
requireAuth();
|
||||||
|
// Palauttaa kaikki postilaatikot käyttäjän yrityksistä (allekirjoituksia varten)
|
||||||
|
$userCompanyIds = $_SESSION['companies'] ?? [];
|
||||||
|
$allCompanies = loadCompanies();
|
||||||
|
$result = [];
|
||||||
|
foreach ($allCompanies as $comp) {
|
||||||
|
if (!in_array($comp['id'], $userCompanyIds)) continue;
|
||||||
|
$oldCompanyId = $_SESSION['company_id'] ?? '';
|
||||||
|
$_SESSION['company_id'] = $comp['id'];
|
||||||
|
$conf = loadCompanyConfig();
|
||||||
|
$_SESSION['company_id'] = $oldCompanyId;
|
||||||
|
foreach ($conf['mailboxes'] ?? [] as $mb) {
|
||||||
|
$result[] = [
|
||||||
|
'id' => $mb['id'],
|
||||||
|
'nimi' => $mb['nimi'] ?? $mb['imap_user'] ?? '',
|
||||||
|
'company_id' => $comp['id'],
|
||||||
|
'company_nimi' => $comp['nimi'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo json_encode($result);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'company_create':
|
case 'company_create':
|
||||||
requireAdmin();
|
requireAdmin();
|
||||||
if ($method !== 'POST') break;
|
if ($method !== 'POST') break;
|
||||||
|
|||||||
@@ -331,6 +331,7 @@
|
|||||||
<button class="btn-reply-tab" data-reply-type="note">📝 Muistiinpano</button>
|
<button class="btn-reply-tab" data-reply-type="note">📝 Muistiinpano</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea id="ticket-reply-body" rows="5" placeholder="Kirjoita vastaus..."></textarea>
|
<textarea id="ticket-reply-body" rows="5" placeholder="Kirjoita vastaus..."></textarea>
|
||||||
|
<div id="signature-preview" style="display:none;padding:0.5rem 0.75rem;margin-top:0.25rem;border-left:3px solid #d0d5dd;color:#888;font-size:0.82rem;white-space:pre-line;"></div>
|
||||||
<div style="display:flex;justify-content:flex-end;gap:0.5rem;margin-top:0.5rem;">
|
<div style="display:flex;justify-content:flex-end;gap:0.5rem;margin-top:0.5rem;">
|
||||||
<button class="btn-primary" id="btn-send-reply">Lähetä vastaus</button>
|
<button class="btn-primary" id="btn-send-reply">Lähetä vastaus</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -735,6 +736,11 @@
|
|||||||
<div id="user-company-checkboxes" style="display:flex;flex-wrap:wrap;gap:0.75rem;margin-top:0.25rem;"></div>
|
<div id="user-company-checkboxes" style="display:flex;flex-wrap:wrap;gap:0.75rem;margin-top:0.25rem;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="user-signatures-section" style="display:none;margin-top:1rem;border-top:1px solid #e5e7eb;padding-top:1rem;">
|
||||||
|
<h3 style="color:#0f3460;font-size:1rem;margin-bottom:0.75rem;">Sähköpostiallekirjoitukset</h3>
|
||||||
|
<p style="color:#888;font-size:0.82rem;margin-bottom:0.75rem;">Allekirjoitus liitetään automaattisesti sähköpostivastausten loppuun.</p>
|
||||||
|
<div id="user-signatures-list"></div>
|
||||||
|
</div>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button type="submit" class="btn-primary">Tallenna</button>
|
<button type="submit" class="btn-primary">Tallenna</button>
|
||||||
<button type="button" class="btn-secondary" id="user-form-cancel">Peruuta</button>
|
<button type="button" class="btn-secondary" id="user-form-cancel">Peruuta</button>
|
||||||
|
|||||||
57
script.js
57
script.js
@@ -7,6 +7,7 @@ let currentUser = { username: '', nimi: '', role: '' };
|
|||||||
let currentCompany = null; // {id, nimi}
|
let currentCompany = null; // {id, nimi}
|
||||||
let availableCompanies = []; // [{id, nimi}, ...]
|
let availableCompanies = []; // [{id, nimi}, ...]
|
||||||
let currentTicketCompanyId = ''; // Avatun tiketin yritys (cross-company tuki)
|
let currentTicketCompanyId = ''; // Avatun tiketin yritys (cross-company tuki)
|
||||||
|
let currentUserSignatures = {}; // {mailbox_id: "allekirjoitus teksti"}
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
const loginScreen = document.getElementById('login-screen');
|
const loginScreen = document.getElementById('login-screen');
|
||||||
@@ -131,9 +132,10 @@ async function checkAuth() {
|
|||||||
try {
|
try {
|
||||||
const data = await apiCall('check_auth');
|
const data = await apiCall('check_auth');
|
||||||
if (data.authenticated) {
|
if (data.authenticated) {
|
||||||
currentUser = { username: data.username, nimi: data.nimi, role: data.role };
|
currentUser = { username: data.username, nimi: data.nimi, role: data.role, id: data.user_id };
|
||||||
availableCompanies = data.companies || [];
|
availableCompanies = data.companies || [];
|
||||||
currentCompany = availableCompanies.find(c => c.id === data.company_id) || availableCompanies[0] || null;
|
currentCompany = availableCompanies.find(c => c.id === data.company_id) || availableCompanies[0] || null;
|
||||||
|
currentUserSignatures = data.signatures || {};
|
||||||
showDashboard();
|
showDashboard();
|
||||||
}
|
}
|
||||||
} catch (e) { /* not logged in */ }
|
} catch (e) { /* not logged in */ }
|
||||||
@@ -147,9 +149,10 @@ loginForm.addEventListener('submit', async (e) => {
|
|||||||
try {
|
try {
|
||||||
const data = await apiCall('login', 'POST', { username, password, captcha: parseInt(captcha) });
|
const data = await apiCall('login', 'POST', { username, password, captcha: parseInt(captcha) });
|
||||||
loginError.style.display = 'none';
|
loginError.style.display = 'none';
|
||||||
currentUser = { username: data.username, nimi: data.nimi, role: data.role };
|
currentUser = { username: data.username, nimi: data.nimi, role: data.role, id: data.user_id };
|
||||||
availableCompanies = data.companies || [];
|
availableCompanies = data.companies || [];
|
||||||
currentCompany = availableCompanies.find(c => c.id === data.company_id) || availableCompanies[0] || null;
|
currentCompany = availableCompanies.find(c => c.id === data.company_id) || availableCompanies[0] || null;
|
||||||
|
currentUserSignatures = data.signatures || {};
|
||||||
showDashboard();
|
showDashboard();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
loginError.textContent = err.message;
|
loginError.textContent = err.message;
|
||||||
@@ -976,6 +979,27 @@ function openUserForm(user = null) {
|
|||||||
</label>`
|
</label>`
|
||||||
).join('');
|
).join('');
|
||||||
});
|
});
|
||||||
|
// Allekirjoitukset per postilaatikko
|
||||||
|
const sigSection = document.getElementById('user-signatures-section');
|
||||||
|
const sigList = document.getElementById('user-signatures-list');
|
||||||
|
const userSigs = user ? (user.signatures || {}) : {};
|
||||||
|
apiCall('all_mailboxes').then(mailboxes => {
|
||||||
|
if (mailboxes.length === 0) {
|
||||||
|
sigSection.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sigSection.style.display = '';
|
||||||
|
sigList.innerHTML = mailboxes.map(mb =>
|
||||||
|
`<div style="margin-bottom:0.75rem;">
|
||||||
|
<label style="font-weight:600;font-size:0.85rem;color:#333;">${esc(mb.company_nimi)} — ${esc(mb.nimi)}</label>
|
||||||
|
<textarea class="sig-textarea" data-mailbox-id="${mb.id}" rows="3"
|
||||||
|
style="width:100%;margin-top:0.25rem;padding:8px;border:1px solid #ddd;border-radius:6px;font-size:0.85rem;font-family:inherit;resize:vertical;"
|
||||||
|
placeholder="esim.\nJukka\nYritys Oy\ninfo@yritys.fi">${esc(userSigs[mb.id] || '')}</textarea>
|
||||||
|
</div>`
|
||||||
|
).join('');
|
||||||
|
}).catch(() => {
|
||||||
|
sigSection.style.display = 'none';
|
||||||
|
});
|
||||||
userModal.style.display = 'flex';
|
userModal.style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -999,12 +1023,20 @@ document.getElementById('user-form').addEventListener('submit', async (e) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const id = document.getElementById('user-form-id').value;
|
const id = document.getElementById('user-form-id').value;
|
||||||
const companies = [...document.querySelectorAll('.user-company-cb:checked')].map(cb => cb.value);
|
const companies = [...document.querySelectorAll('.user-company-cb:checked')].map(cb => cb.value);
|
||||||
|
// Kerää allekirjoitukset
|
||||||
|
const signatures = {};
|
||||||
|
document.querySelectorAll('.sig-textarea').forEach(ta => {
|
||||||
|
const mbId = ta.dataset.mailboxId;
|
||||||
|
const val = ta.value.trim();
|
||||||
|
if (val) signatures[mbId] = val;
|
||||||
|
});
|
||||||
const data = {
|
const data = {
|
||||||
username: document.getElementById('user-form-username').value,
|
username: document.getElementById('user-form-username').value,
|
||||||
nimi: document.getElementById('user-form-nimi').value,
|
nimi: document.getElementById('user-form-nimi').value,
|
||||||
email: document.getElementById('user-form-email').value,
|
email: document.getElementById('user-form-email').value,
|
||||||
role: document.getElementById('user-form-role').value,
|
role: document.getElementById('user-form-role').value,
|
||||||
companies,
|
companies,
|
||||||
|
signatures,
|
||||||
};
|
};
|
||||||
const pw = document.getElementById('user-form-password').value;
|
const pw = document.getElementById('user-form-password').value;
|
||||||
if (pw) data.password = pw;
|
if (pw) data.password = pw;
|
||||||
@@ -1014,6 +1046,12 @@ document.getElementById('user-form').addEventListener('submit', async (e) => {
|
|||||||
else { await apiCall('user_create', 'POST', data); }
|
else { await apiCall('user_create', 'POST', data); }
|
||||||
userModal.style.display = 'none';
|
userModal.style.display = 'none';
|
||||||
loadUsers();
|
loadUsers();
|
||||||
|
// Päivitä omat allekirjoitukset (check_auth palauttaa tuoreet)
|
||||||
|
const auth = await apiCall('check_auth');
|
||||||
|
if (auth.authenticated) {
|
||||||
|
currentUser = { username: auth.username, nimi: auth.nimi, role: auth.role, id: auth.user_id };
|
||||||
|
currentUserSignatures = auth.signatures || {};
|
||||||
|
}
|
||||||
} catch (e) { alert(e.message); }
|
} catch (e) { alert(e.message); }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1348,6 +1386,17 @@ async function showTicketDetail(id, companyId = '') {
|
|||||||
document.querySelector('.btn-reply-tab[data-reply-type="reply"]').classList.add('active');
|
document.querySelector('.btn-reply-tab[data-reply-type="reply"]').classList.add('active');
|
||||||
document.getElementById('btn-send-reply').textContent = 'Lähetä vastaus';
|
document.getElementById('btn-send-reply').textContent = 'Lähetä vastaus';
|
||||||
|
|
||||||
|
// Allekirjoituksen esikatselu
|
||||||
|
const sigPreview = document.getElementById('signature-preview');
|
||||||
|
const mailboxId = ticket.mailbox_id || '';
|
||||||
|
const sig = currentUserSignatures[mailboxId] || '';
|
||||||
|
if (sig) {
|
||||||
|
sigPreview.textContent = '-- \n' + sig;
|
||||||
|
sigPreview.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
sigPreview.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e) { alert(e.message); }
|
} catch (e) { alert(e.message); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1376,12 +1425,16 @@ document.querySelectorAll('.btn-reply-tab').forEach(btn => {
|
|||||||
ticketReplyType = btn.dataset.replyType;
|
ticketReplyType = btn.dataset.replyType;
|
||||||
const textarea = document.getElementById('ticket-reply-body');
|
const textarea = document.getElementById('ticket-reply-body');
|
||||||
const sendBtn = document.getElementById('btn-send-reply');
|
const sendBtn = document.getElementById('btn-send-reply');
|
||||||
|
const sigPrev = document.getElementById('signature-preview');
|
||||||
if (ticketReplyType === 'note') {
|
if (ticketReplyType === 'note') {
|
||||||
textarea.placeholder = 'Kirjoita sisäinen muistiinpano...';
|
textarea.placeholder = 'Kirjoita sisäinen muistiinpano...';
|
||||||
sendBtn.textContent = 'Tallenna muistiinpano';
|
sendBtn.textContent = 'Tallenna muistiinpano';
|
||||||
|
sigPrev.style.display = 'none';
|
||||||
} else {
|
} else {
|
||||||
textarea.placeholder = 'Kirjoita vastaus...';
|
textarea.placeholder = 'Kirjoita vastaus...';
|
||||||
sendBtn.textContent = 'Lähetä vastaus';
|
sendBtn.textContent = 'Lähetä vastaus';
|
||||||
|
// Näytä allekirjoitus jos on asetettu
|
||||||
|
if (sigPrev.textContent.trim()) sigPrev.style.display = 'block';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user