White-label multi-domain tuki (Noxus Intra)
- CSS-muuttujat: kaikki kovakoodatut #0f3460/#16213e korvattu var(--primary-color)/var(--primary-dark) - Uudet API-endpointit: branding (julkinen, domain-pohjainen), company_logo, company_logo_upload - Domain-pohjainen brändäys: HTTP_HOST → yrityksen domains-arrayn matchaus - Login: domain asettaa oletusyrityksen sessioon - check_auth: palauttaa branding-objektin (primary_color, subtitle, logo_url) - company_create/update: käsittelee domains, primary_color, subtitle, logo_file - Dynaaminen login-sivu, header ja footer (logo, nimi, alaotsikko, värit) - JS: loadBranding(), applyBranding(), yritysvaihdon brändäyspäivitys - Admin-paneeli: brändäysasetukset (logo-upload, väri, alaotsikko, domainit) - Git-repo siirretty intra.noxus.fi:hin Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
186
api.php
186
api.php
@@ -1016,6 +1016,143 @@ switch ($action) {
|
||||
echo json_encode(['question' => "$a + $b = ?"]);
|
||||
break;
|
||||
|
||||
// ---------- BRANDING (julkinen) ----------
|
||||
case 'branding':
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
// Stripaa portti pois (localhost:3001 → localhost)
|
||||
$host = strtolower(explode(':', $host)[0]);
|
||||
$companies = loadCompanies();
|
||||
$matchedCompany = null;
|
||||
foreach ($companies as $comp) {
|
||||
$domains = $comp['domains'] ?? [];
|
||||
foreach ($domains as $d) {
|
||||
if (strtolower(trim($d)) === strtolower($host)) {
|
||||
$matchedCompany = $comp;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($matchedCompany) {
|
||||
$logoUrl = !empty($matchedCompany['logo_file'])
|
||||
? "api.php?action=company_logo&company_id=" . urlencode($matchedCompany['id'])
|
||||
: '';
|
||||
echo json_encode([
|
||||
'found' => true,
|
||||
'company_id' => $matchedCompany['id'],
|
||||
'nimi' => $matchedCompany['nimi'],
|
||||
'primary_color' => $matchedCompany['primary_color'] ?? '#0f3460',
|
||||
'subtitle' => $matchedCompany['subtitle'] ?? '',
|
||||
'logo_url' => $logoUrl,
|
||||
]);
|
||||
} else {
|
||||
// Noxus Intra -oletusbrändäys
|
||||
echo json_encode([
|
||||
'found' => false,
|
||||
'company_id' => '',
|
||||
'nimi' => 'Noxus Intra',
|
||||
'primary_color' => '#0f3460',
|
||||
'subtitle' => 'Hallintapaneeli',
|
||||
'logo_url' => '',
|
||||
]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'company_logo':
|
||||
$companyId = $_GET['company_id'] ?? '';
|
||||
if (empty($companyId) || !preg_match('/^[a-z0-9-]+$/', $companyId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Virheellinen company_id']);
|
||||
break;
|
||||
}
|
||||
$companies = loadCompanies();
|
||||
$logoFile = '';
|
||||
foreach ($companies as $comp) {
|
||||
if ($comp['id'] === $companyId) {
|
||||
$logoFile = $comp['logo_file'] ?? '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($logoFile)) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Logoa ei löydy']);
|
||||
break;
|
||||
}
|
||||
$logoPath = DATA_DIR . '/companies/' . $companyId . '/' . $logoFile;
|
||||
if (!file_exists($logoPath)) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Logotiedostoa ei löydy']);
|
||||
break;
|
||||
}
|
||||
$ext = strtolower(pathinfo($logoFile, PATHINFO_EXTENSION));
|
||||
$mimeTypes = ['png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'svg' => 'image/svg+xml', 'webp' => 'image/webp'];
|
||||
$mime = $mimeTypes[$ext] ?? 'application/octet-stream';
|
||||
header('Content-Type: ' . $mime);
|
||||
header('Cache-Control: public, max-age=3600');
|
||||
readfile($logoPath);
|
||||
exit;
|
||||
|
||||
case 'company_logo_upload':
|
||||
requireAdmin();
|
||||
if ($method !== 'POST') break;
|
||||
$companyId = $_POST['company_id'] ?? '';
|
||||
if (empty($companyId) || !preg_match('/^[a-z0-9-]+$/', $companyId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Virheellinen company_id']);
|
||||
break;
|
||||
}
|
||||
if (!isset($_FILES['logo']) || $_FILES['logo']['error'] !== UPLOAD_ERR_OK) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Logotiedosto puuttuu tai virhe uploadissa']);
|
||||
break;
|
||||
}
|
||||
$file = $_FILES['logo'];
|
||||
// Validoi koko (max 2MB)
|
||||
if ($file['size'] > 2 * 1024 * 1024) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Logo on liian suuri (max 2MB)']);
|
||||
break;
|
||||
}
|
||||
// Validoi tyyppi
|
||||
$allowedTypes = ['image/png', 'image/jpeg', 'image/svg+xml', 'image/webp'];
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$detectedType = finfo_file($finfo, $file['tmp_name']);
|
||||
finfo_close($finfo);
|
||||
if (!in_array($detectedType, $allowedTypes)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Sallitut tiedostotyypit: PNG, JPG, SVG, WebP']);
|
||||
break;
|
||||
}
|
||||
$extMap = ['image/png' => 'png', 'image/jpeg' => 'jpg', 'image/svg+xml' => 'svg', 'image/webp' => 'webp'];
|
||||
$ext = $extMap[$detectedType] ?? 'png';
|
||||
$newFilename = 'logo.' . $ext;
|
||||
$compDir = DATA_DIR . '/companies/' . $companyId;
|
||||
if (!file_exists($compDir)) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Yritystä ei löydy']);
|
||||
break;
|
||||
}
|
||||
// Poista vanha logo
|
||||
$companies = loadCompanies();
|
||||
foreach ($companies as &$comp) {
|
||||
if ($comp['id'] === $companyId) {
|
||||
$oldLogo = $comp['logo_file'] ?? '';
|
||||
if ($oldLogo && $oldLogo !== $newFilename && file_exists($compDir . '/' . $oldLogo)) {
|
||||
unlink($compDir . '/' . $oldLogo);
|
||||
}
|
||||
$comp['logo_file'] = $newFilename;
|
||||
break;
|
||||
}
|
||||
}
|
||||
unset($comp);
|
||||
saveCompanies($companies);
|
||||
move_uploaded_file($file['tmp_name'], $compDir . '/' . $newFilename);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'logo_file' => $newFilename,
|
||||
'logo_url' => "api.php?action=company_logo&company_id=" . urlencode($companyId),
|
||||
]);
|
||||
break;
|
||||
|
||||
// ---------- AUTH ----------
|
||||
case 'login':
|
||||
if ($method !== 'POST') break;
|
||||
@@ -1050,8 +1187,24 @@ switch ($action) {
|
||||
// Multi-company: aseta käyttäjän yritykset sessioon
|
||||
$userCompanies = $u['companies'] ?? [];
|
||||
$_SESSION['companies'] = $userCompanies;
|
||||
// Valitse ensimmäinen yritys oletukseksi
|
||||
$_SESSION['company_id'] = !empty($userCompanies) ? $userCompanies[0] : '';
|
||||
// Domain-pohjainen oletusyritys
|
||||
$host = strtolower(explode(':', $_SERVER['HTTP_HOST'] ?? '')[0]);
|
||||
$domainCompanyId = '';
|
||||
$allComps = loadCompanies();
|
||||
foreach ($allComps as $dc) {
|
||||
foreach ($dc['domains'] ?? [] as $d) {
|
||||
if (strtolower(trim($d)) === strtolower($host)) {
|
||||
$domainCompanyId = $dc['id'];
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Jos domain matchaa ja käyttäjällä on oikeus → käytä sitä
|
||||
if ($domainCompanyId && in_array($domainCompanyId, $userCompanies)) {
|
||||
$_SESSION['company_id'] = $domainCompanyId;
|
||||
} else {
|
||||
$_SESSION['company_id'] = !empty($userCompanies) ? $userCompanies[0] : '';
|
||||
}
|
||||
// Hae yritysten nimet
|
||||
$allCompanies = loadCompanies();
|
||||
$companyList = [];
|
||||
@@ -1116,6 +1269,20 @@ switch ($action) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Brändäystiedot aktiivisesta yrityksestä
|
||||
$branding = ['primary_color' => '#0f3460', 'subtitle' => '', 'logo_url' => '', 'company_nimi' => ''];
|
||||
$activeCompanyId = $_SESSION['company_id'] ?? '';
|
||||
foreach ($allCompanies as $bc) {
|
||||
if ($bc['id'] === $activeCompanyId) {
|
||||
$branding['primary_color'] = $bc['primary_color'] ?? '#0f3460';
|
||||
$branding['subtitle'] = $bc['subtitle'] ?? '';
|
||||
$branding['company_nimi'] = $bc['nimi'] ?? '';
|
||||
$branding['logo_url'] = !empty($bc['logo_file'])
|
||||
? "api.php?action=company_logo&company_id=" . urlencode($bc['id'])
|
||||
: '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
echo json_encode([
|
||||
'authenticated' => true,
|
||||
'user_id' => $_SESSION['user_id'],
|
||||
@@ -1125,6 +1292,7 @@ switch ($action) {
|
||||
'companies' => $companyList,
|
||||
'company_id' => $_SESSION['company_id'] ?? '',
|
||||
'signatures' => $userSignatures,
|
||||
'branding' => $branding,
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['authenticated' => false]);
|
||||
@@ -2479,9 +2647,18 @@ switch ($action) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
// Brändäyskentät
|
||||
$domains = [];
|
||||
if (isset($input['domains']) && is_array($input['domains'])) {
|
||||
$domains = array_values(array_filter(array_map('trim', $input['domains'])));
|
||||
}
|
||||
$company = [
|
||||
'id' => $id,
|
||||
'nimi' => $nimi,
|
||||
'domains' => $domains,
|
||||
'primary_color' => trim($input['primary_color'] ?? '#0f3460'),
|
||||
'subtitle' => trim($input['subtitle'] ?? ''),
|
||||
'logo_file' => '',
|
||||
'luotu' => date('Y-m-d H:i:s'),
|
||||
'aktiivinen' => true,
|
||||
];
|
||||
@@ -2521,6 +2698,11 @@ switch ($action) {
|
||||
if ($c['id'] === $id) {
|
||||
if (isset($input['nimi'])) $c['nimi'] = trim($input['nimi']);
|
||||
if (isset($input['aktiivinen'])) $c['aktiivinen'] = (bool)$input['aktiivinen'];
|
||||
if (isset($input['domains']) && is_array($input['domains'])) {
|
||||
$c['domains'] = array_values(array_filter(array_map('trim', $input['domains'])));
|
||||
}
|
||||
if (isset($input['primary_color'])) $c['primary_color'] = trim($input['primary_color']);
|
||||
if (isset($input['subtitle'])) $c['subtitle'] = trim($input['subtitle']);
|
||||
$found = true;
|
||||
echo json_encode($c);
|
||||
break;
|
||||
|
||||
46
index.html
46
index.html
@@ -3,15 +3,16 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CuituNet Intra - Asiakashallinta</title>
|
||||
<title>Noxus Intra</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Login -->
|
||||
<div id="login-screen" class="login-screen">
|
||||
<div class="login-box">
|
||||
<h1>CuituNet Intra</h1>
|
||||
<p>Kirjaudu sisään</p>
|
||||
<img id="login-logo" src="" alt="Logo" style="height:48px;margin-bottom:0.75rem;display:none;">
|
||||
<h1 id="login-title">Noxus Intra</h1>
|
||||
<p id="login-subtitle">Kirjaudu sisään</p>
|
||||
<form id="login-form">
|
||||
<input type="text" id="login-username" placeholder="Käyttäjätunnus" required autofocus>
|
||||
<input type="password" id="login-password" placeholder="Salasana" required>
|
||||
@@ -26,7 +27,7 @@
|
||||
</div>
|
||||
<!-- Salasanan palautuspyyntö -->
|
||||
<div class="login-box" id="forgot-box" style="display:none">
|
||||
<h1>CuituNet Intra</h1>
|
||||
<h1 class="login-brand-title">Noxus Intra</h1>
|
||||
<p>Salasanan palautus</p>
|
||||
<form id="forgot-form">
|
||||
<input type="text" id="forgot-username" placeholder="Käyttäjätunnus" required autofocus>
|
||||
@@ -38,7 +39,7 @@
|
||||
</div>
|
||||
<!-- Uusi salasana (reset token) -->
|
||||
<div class="login-box" id="reset-box" style="display:none">
|
||||
<h1>CuituNet Intra</h1>
|
||||
<h1 class="login-brand-title">Noxus Intra</h1>
|
||||
<p>Aseta uusi salasana</p>
|
||||
<form id="reset-form">
|
||||
<input type="password" id="reset-password" placeholder="Uusi salasana" required>
|
||||
@@ -55,10 +56,11 @@
|
||||
<header>
|
||||
<div class="header-left">
|
||||
<div class="header-brand" id="brand-home" style="cursor:pointer;">
|
||||
<span class="brand-icon">⚡</span>
|
||||
<img id="header-logo" src="" alt="Logo" style="height:32px;display:none;">
|
||||
<span class="brand-icon" id="header-brand-icon">⚡</span>
|
||||
<div>
|
||||
<h1>CuituNet Intra</h1>
|
||||
<span class="subtitle">Kuituasiakkaiden hallinta</span>
|
||||
<h1 id="header-title">Noxus Intra</h1>
|
||||
<span class="subtitle" id="header-subtitle">Hallintapaneeli</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -514,16 +516,36 @@
|
||||
<div id="company-detail-view" style="display:none;">
|
||||
<button class="btn-secondary" id="btn-company-back" style="color:#555;border-color:#ddd;margin-bottom:1rem;">← Takaisin yrityslistaan</button>
|
||||
<div class="table-card" style="padding:1.5rem;">
|
||||
<h3 style="color:#0f3460;margin-bottom:0.5rem;" id="company-detail-title">Yrityksen asetukset</h3>
|
||||
<div class="form-grid" style="max-width:400px;margin-bottom:1.5rem;">
|
||||
<h3 style="margin-bottom:0.5rem;" id="company-detail-title">Yrityksen asetukset</h3>
|
||||
<div class="form-grid" style="max-width:600px;margin-bottom:1.5rem;">
|
||||
<div class="form-group">
|
||||
<label>Yrityksen nimi</label>
|
||||
<input type="text" id="company-edit-nimi">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn-primary" id="btn-save-company-name" style="font-size:0.85rem;">Tallenna nimi</button>
|
||||
<label>Alaotsikko</label>
|
||||
<input type="text" id="company-edit-subtitle" placeholder="esim. Asiakashallinta">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Pääväri</label>
|
||||
<div style="display:flex;align-items:center;gap:0.5rem;">
|
||||
<input type="color" id="company-edit-color" value="#0f3460" style="width:50px;height:36px;border:none;cursor:pointer;">
|
||||
<input type="text" id="company-edit-color-text" placeholder="#0f3460" style="width:100px;font-family:monospace;font-size:0.85rem;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Logo</label>
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;">
|
||||
<img id="company-logo-preview" src="" style="height:36px;display:none;border-radius:4px;">
|
||||
<input type="file" id="company-logo-upload" accept="image/png,image/jpeg,image/svg+xml,image/webp" style="font-size:0.82rem;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group full-width">
|
||||
<label>Domainit (yksi per rivi)</label>
|
||||
<textarea id="company-edit-domains" rows="3" placeholder="intra.yritys.fi intra.toinen.fi" style="font-family:monospace;font-size:0.85rem;"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-primary" id="btn-save-company-settings" style="font-size:0.85rem;">Tallenna asetukset</button>
|
||||
</div>
|
||||
<!-- Postilaatikot -->
|
||||
<div class="table-card" style="padding:1.5rem;margin-top:1rem;">
|
||||
@@ -592,7 +614,7 @@
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>CuituNet Intra — Asiakashallintajärjestelmä</p>
|
||||
<p id="footer-text">Noxus Intra © 2026</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
||||
124
script.js
124
script.js
@@ -136,6 +136,7 @@ async function checkAuth() {
|
||||
availableCompanies = data.companies || [];
|
||||
currentCompany = availableCompanies.find(c => c.id === data.company_id) || availableCompanies[0] || null;
|
||||
currentUserSignatures = data.signatures || {};
|
||||
if (data.branding) applyBranding(data.branding);
|
||||
showDashboard();
|
||||
}
|
||||
} catch (e) { /* not logged in */ }
|
||||
@@ -171,6 +172,7 @@ document.getElementById('btn-logout').addEventListener('click', async () => {
|
||||
document.getElementById('login-captcha').value = '';
|
||||
showLoginView();
|
||||
loadCaptcha();
|
||||
loadBranding(); // Domain-pohjainen brändäys uudelleen
|
||||
});
|
||||
|
||||
async function showDashboard() {
|
||||
@@ -206,6 +208,11 @@ async function switchCompany(companyId) {
|
||||
try {
|
||||
await apiCall('company_switch', 'POST', { company_id: companyId });
|
||||
currentCompany = availableCompanies.find(c => c.id === companyId) || null;
|
||||
// Päivitä brändäys vaihdetun yrityksen mukaan
|
||||
try {
|
||||
const auth = await apiCall('check_auth');
|
||||
if (auth.branding) applyBranding(auth.branding);
|
||||
} catch (e2) {}
|
||||
// Lataa uudelleen aktiivinen tab
|
||||
const hash = window.location.hash.replace('#', '') || 'customers';
|
||||
switchToTab(hash);
|
||||
@@ -1837,6 +1844,20 @@ async function showCompanyDetail(id) {
|
||||
const comp = companiesTabData.find(c => c.id === id);
|
||||
document.getElementById('company-detail-title').textContent = (comp ? comp.nimi : id) + ' — Asetukset';
|
||||
document.getElementById('company-edit-nimi').value = comp ? comp.nimi : '';
|
||||
// Brändäyskentät
|
||||
document.getElementById('company-edit-subtitle').value = comp?.subtitle || '';
|
||||
const color = comp?.primary_color || '#0f3460';
|
||||
document.getElementById('company-edit-color').value = color;
|
||||
document.getElementById('company-edit-color-text').value = color;
|
||||
document.getElementById('company-edit-domains').value = (comp?.domains || []).join('\n');
|
||||
// Logo-esikatselu
|
||||
const logoPreview = document.getElementById('company-logo-preview');
|
||||
if (comp?.logo_file) {
|
||||
logoPreview.src = 'api.php?action=company_logo&company_id=' + encodeURIComponent(id) + '&t=' + Date.now();
|
||||
logoPreview.style.display = '';
|
||||
} else {
|
||||
logoPreview.style.display = 'none';
|
||||
}
|
||||
|
||||
// Vaihda aktiivinen yritys jotta API-kutsut kohdistuvat oikein
|
||||
await apiCall('company_switch', 'POST', { company_id: id });
|
||||
@@ -1853,18 +1874,60 @@ document.getElementById('btn-company-back').addEventListener('click', () => {
|
||||
renderCompaniesTable();
|
||||
});
|
||||
|
||||
document.getElementById('btn-save-company-name').addEventListener('click', async () => {
|
||||
// Synkronoi color picker <-> text input
|
||||
document.getElementById('company-edit-color').addEventListener('input', function() {
|
||||
document.getElementById('company-edit-color-text').value = this.value;
|
||||
});
|
||||
document.getElementById('company-edit-color-text').addEventListener('input', function() {
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(this.value)) {
|
||||
document.getElementById('company-edit-color').value = this.value;
|
||||
}
|
||||
});
|
||||
|
||||
// Logo-upload
|
||||
document.getElementById('company-logo-upload').addEventListener('change', async function() {
|
||||
if (!this.files[0] || !currentCompanyDetail) return;
|
||||
const formData = new FormData();
|
||||
formData.append('logo', this.files[0]);
|
||||
formData.append('company_id', currentCompanyDetail);
|
||||
try {
|
||||
const res = await fetch('api.php?action=company_logo_upload', { method: 'POST', body: formData, credentials: 'include' });
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || 'Virhe');
|
||||
// Päivitä preview
|
||||
const preview = document.getElementById('company-logo-preview');
|
||||
preview.src = data.logo_url + '&t=' + Date.now();
|
||||
preview.style.display = '';
|
||||
// Päivitä paikallinen data
|
||||
const comp = companiesTabData.find(c => c.id === currentCompanyDetail);
|
||||
if (comp) comp.logo_file = data.logo_file;
|
||||
} catch (e) { alert(e.message); }
|
||||
this.value = ''; // Reset file input
|
||||
});
|
||||
|
||||
document.getElementById('btn-save-company-settings').addEventListener('click', async () => {
|
||||
const nimi = document.getElementById('company-edit-nimi').value.trim();
|
||||
if (!nimi) return;
|
||||
const subtitle = document.getElementById('company-edit-subtitle').value.trim();
|
||||
const primary_color = document.getElementById('company-edit-color').value;
|
||||
const domainsText = document.getElementById('company-edit-domains').value;
|
||||
const domains = domainsText.split('\n').map(d => d.trim()).filter(d => d);
|
||||
try {
|
||||
await apiCall('company_update', 'POST', { id: currentCompanyDetail, nimi });
|
||||
alert('Nimi tallennettu!');
|
||||
await apiCall('company_update', 'POST', { id: currentCompanyDetail, nimi, subtitle, primary_color, domains });
|
||||
alert('Asetukset tallennettu!');
|
||||
// Päivitä paikalliset tiedot
|
||||
const comp = companiesTabData.find(c => c.id === currentCompanyDetail);
|
||||
if (comp) comp.nimi = nimi;
|
||||
if (comp) { comp.nimi = nimi; comp.subtitle = subtitle; comp.primary_color = primary_color; comp.domains = domains; }
|
||||
const avail = availableCompanies.find(c => c.id === currentCompanyDetail);
|
||||
if (avail) avail.nimi = nimi;
|
||||
populateCompanySelector();
|
||||
// Jos tämä on aktiivinen yritys → päivitä brändäys heti
|
||||
if (currentCompany && currentCompany.id === currentCompanyDetail) {
|
||||
applyBranding({
|
||||
nimi, subtitle, primary_color,
|
||||
logo_url: comp?.logo_file ? 'api.php?action=company_logo&company_id=' + encodeURIComponent(currentCompanyDetail) + '&t=' + Date.now() : ''
|
||||
});
|
||||
}
|
||||
} catch (e) { alert(e.message); }
|
||||
});
|
||||
|
||||
@@ -1985,6 +2048,59 @@ async function toggleCompanyUser(userId, companyId, add) {
|
||||
} catch (e) { alert(e.message); }
|
||||
}
|
||||
|
||||
// ==================== BRANDING ====================
|
||||
|
||||
function applyBranding(branding) {
|
||||
const color = branding.primary_color || '#0f3460';
|
||||
const nimi = branding.nimi || 'Noxus Intra';
|
||||
const subtitle = branding.subtitle || '';
|
||||
const logoUrl = branding.logo_url || '';
|
||||
|
||||
// CSS-muuttuja
|
||||
document.documentElement.style.setProperty('--primary-color', color);
|
||||
// Laske tumma variantti
|
||||
document.documentElement.style.setProperty('--primary-dark', color);
|
||||
|
||||
// Login-sivu
|
||||
const loginLogo = document.getElementById('login-logo');
|
||||
const loginTitle = document.getElementById('login-title');
|
||||
const loginSubtitle = document.getElementById('login-subtitle');
|
||||
if (loginLogo) {
|
||||
if (logoUrl) { loginLogo.src = logoUrl; loginLogo.style.display = ''; }
|
||||
else { loginLogo.style.display = 'none'; }
|
||||
}
|
||||
if (loginTitle) loginTitle.textContent = nimi;
|
||||
if (loginSubtitle) loginSubtitle.textContent = subtitle || 'Kirjaudu sisään';
|
||||
// Muut login-boxien otsikot
|
||||
document.querySelectorAll('.login-brand-title').forEach(el => el.textContent = nimi);
|
||||
|
||||
// Header
|
||||
const headerLogo = document.getElementById('header-logo');
|
||||
const headerIcon = document.getElementById('header-brand-icon');
|
||||
const headerTitle = document.getElementById('header-title');
|
||||
const headerSubtitle = document.getElementById('header-subtitle');
|
||||
if (headerLogo) {
|
||||
if (logoUrl) { headerLogo.src = logoUrl; headerLogo.style.display = ''; if (headerIcon) headerIcon.style.display = 'none'; }
|
||||
else { headerLogo.style.display = 'none'; if (headerIcon) headerIcon.style.display = ''; }
|
||||
}
|
||||
if (headerTitle) headerTitle.textContent = nimi;
|
||||
if (headerSubtitle) headerSubtitle.textContent = subtitle || 'Hallintapaneeli';
|
||||
|
||||
// Sivun title
|
||||
document.title = nimi;
|
||||
}
|
||||
|
||||
async function loadBranding() {
|
||||
try {
|
||||
const data = await apiCall('branding');
|
||||
applyBranding(data);
|
||||
} catch (e) {
|
||||
// Oletusbrändäys
|
||||
applyBranding({ nimi: 'Noxus Intra', primary_color: '#0f3460', subtitle: 'Hallintapaneeli', logo_url: '' });
|
||||
}
|
||||
}
|
||||
|
||||
// Init
|
||||
loadBranding();
|
||||
loadCaptcha();
|
||||
checkAuth();
|
||||
|
||||
79
style.css
79
style.css
@@ -1,3 +1,10 @@
|
||||
:root {
|
||||
--primary-color: #0f3460;
|
||||
--primary-dark: #16213e;
|
||||
--primary-light: color-mix(in srgb, var(--primary-color) 10%, white);
|
||||
--primary-hover: color-mix(in srgb, var(--primary-color) 85%, black);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -17,7 +24,7 @@ body {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #0f3460, #16213e);
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
|
||||
}
|
||||
|
||||
.login-box {
|
||||
@@ -34,7 +41,7 @@ body {
|
||||
.login-box h1 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.login-box p {
|
||||
@@ -54,13 +61,13 @@ body {
|
||||
|
||||
.login-box input:focus {
|
||||
outline: none;
|
||||
border-color: #0f3460;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.login-box button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #0f3460;
|
||||
background: var(--primary-color);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
@@ -70,7 +77,7 @@ body {
|
||||
}
|
||||
|
||||
.login-box button:hover {
|
||||
background: #16213e;
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.error {
|
||||
@@ -88,7 +95,7 @@ body {
|
||||
.forgot-link {
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
font-size: 0.85rem;
|
||||
text-decoration: none;
|
||||
opacity: 0.7;
|
||||
@@ -110,7 +117,7 @@ body {
|
||||
.captcha-question {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
white-space: nowrap;
|
||||
min-width: 120px;
|
||||
text-align: center;
|
||||
@@ -127,7 +134,7 @@ body {
|
||||
|
||||
/* Header */
|
||||
header {
|
||||
background: linear-gradient(135deg, #0f3460, #16213e);
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
|
||||
color: #fff;
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
@@ -221,7 +228,7 @@ header {
|
||||
}
|
||||
|
||||
.sidebar-stats .stat-card.highlight {
|
||||
background: #0f3460;
|
||||
background: var(--primary-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -253,7 +260,7 @@ header {
|
||||
}
|
||||
|
||||
.stat-highlight {
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.sidebar-stats .stat-card.trivia .stat-value {
|
||||
@@ -285,7 +292,7 @@ header {
|
||||
|
||||
.speed-item.top {
|
||||
font-weight: 700;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.speed-bar {
|
||||
@@ -297,7 +304,7 @@ header {
|
||||
}
|
||||
|
||||
.speed-item.top .speed-bar {
|
||||
background: #0f3460;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Toolbar */
|
||||
@@ -331,7 +338,7 @@ header {
|
||||
|
||||
.search-bar input:focus {
|
||||
outline: none;
|
||||
border-color: #0f3460;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(15,52,96,0.1);
|
||||
}
|
||||
|
||||
@@ -350,7 +357,7 @@ table {
|
||||
}
|
||||
|
||||
thead th {
|
||||
background: #16213e;
|
||||
background: var(--primary-dark);
|
||||
color: #fff;
|
||||
padding: 11px 14px;
|
||||
text-align: left;
|
||||
@@ -401,7 +408,7 @@ tbody td {
|
||||
|
||||
.price-cell {
|
||||
font-weight: 700;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.actions-cell {
|
||||
@@ -460,7 +467,7 @@ tbody td {
|
||||
|
||||
#total-billing {
|
||||
font-size: 0.95rem;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
@@ -604,7 +611,7 @@ footer {
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 1.3rem;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
@@ -635,7 +642,7 @@ form {
|
||||
|
||||
form h3 {
|
||||
font-size: 0.95rem;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
margin: 1.25rem 0 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 2px solid #f0f2f5;
|
||||
@@ -688,7 +695,7 @@ form h3:first-of-type {
|
||||
.form-group textarea:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: #0f3460;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(15,52,96,0.1);
|
||||
}
|
||||
|
||||
@@ -704,7 +711,7 @@ form h3:first-of-type {
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: #0f3460;
|
||||
accent-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
@@ -744,7 +751,7 @@ form h3:first-of-type {
|
||||
.liittyma-row-title {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Detail view */
|
||||
@@ -762,7 +769,7 @@ form h3:first-of-type {
|
||||
|
||||
.detail-section h3 {
|
||||
font-size: 0.95rem;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 2px solid #f0f2f5;
|
||||
@@ -794,7 +801,7 @@ form h3:first-of-type {
|
||||
}
|
||||
|
||||
.detail-value a {
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -820,7 +827,7 @@ span.empty {
|
||||
.liittyma-num {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 0.4rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
@@ -830,7 +837,7 @@ span.empty {
|
||||
text-align: right;
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
margin-top: 0.5rem;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 2px solid #e8ebf0;
|
||||
@@ -869,7 +876,7 @@ span.empty {
|
||||
.file-name {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -995,12 +1002,12 @@ span.empty {
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: #0f3460;
|
||||
border-bottom-color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
border-bottom-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
@@ -1023,7 +1030,7 @@ span.empty {
|
||||
}
|
||||
|
||||
.role-admin {
|
||||
background: #0f3460;
|
||||
background: var(--primary-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -1257,7 +1264,7 @@ span.empty {
|
||||
|
||||
.ticket-reply-form textarea:focus {
|
||||
outline: none;
|
||||
border-color: #0f3460;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(15, 52, 96, 0.1);
|
||||
}
|
||||
|
||||
@@ -1279,9 +1286,9 @@ span.empty {
|
||||
}
|
||||
|
||||
.btn-reply-tab.active {
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
background: #fff;
|
||||
border-color: #0f3460;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Ticket tags */
|
||||
@@ -1339,7 +1346,7 @@ span.empty {
|
||||
border-radius: 6px;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
color: #0f3460;
|
||||
color: var(--primary-color);
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
@@ -1347,7 +1354,7 @@ span.empty {
|
||||
|
||||
.company-selector:hover,
|
||||
.company-selector:focus {
|
||||
border-color: #0f3460;
|
||||
border-color: var(--primary-color);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -1357,7 +1364,7 @@ span.empty {
|
||||
}
|
||||
|
||||
.mailbox-item:hover {
|
||||
border-color: #0f3460 !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
/* Company badge */
|
||||
|
||||
Reference in New Issue
Block a user