diff --git a/api.php b/api.php
index c9ba867..dc48f61 100644
--- a/api.php
+++ b/api.php
@@ -1882,47 +1882,12 @@ switch ($action) {
echo json_encode(['success' => true]);
break;
- // ---------- SIJAINNIT (SITES) ----------
+ // ---------- SIJAINNIT (SITES) — YHDISTETTY LAITETILOIHIN ----------
+ // sites-endpoint palauttaa nyt laitetilat (taaksepäin yhteensopivuus)
case 'sites':
requireAuth();
$companyId = requireCompany();
- echo json_encode(dbLoadSites($companyId));
- break;
-
- case 'site_save':
- requireAdmin();
- $companyId = requireCompany();
- if ($method !== 'POST') break;
- $input = json_decode(file_get_contents('php://input'), true);
- $site = [
- 'id' => $input['id'] ?? generateId(),
- 'nimi' => trim($input['nimi'] ?? ''),
- 'osoite' => trim($input['osoite'] ?? ''),
- 'kaupunki' => trim($input['kaupunki'] ?? ''),
- ];
- if (empty($site['nimi'])) {
- http_response_code(400);
- echo json_encode(['error' => 'Sijainnin nimi vaaditaan']);
- break;
- }
- dbSaveSite($companyId, $site);
- dbAddLog($companyId, currentUser(), 'site_save', $site['id'], $site['nimi'], (isset($input['id']) ? 'Muokkasi' : 'Lisäsi') . ' sijainnin');
- echo json_encode($site);
- break;
-
- case 'site_delete':
- requireAdmin();
- $companyId = requireCompany();
- if ($method !== 'POST') break;
- $input = json_decode(file_get_contents('php://input'), true);
- $id = $input['id'] ?? '';
- // Hae nimi logitusta varten
- $sites = dbLoadSites($companyId);
- $siteName = '';
- foreach ($sites as $s) { if ($s['id'] === $id) { $siteName = $s['nimi']; break; } }
- dbDeleteSite($id);
- dbAddLog($companyId, currentUser(), 'site_delete', $id, $siteName, 'Poisti sijainnin');
- echo json_encode(['success' => true]);
+ echo json_encode(dbLoadLaitetilat($companyId));
break;
// ---------- LAITTEET (DEVICES) ----------
@@ -1943,7 +1908,7 @@ switch ($action) {
'nimi' => trim($input['nimi'] ?? ''),
'hallintaosoite' => trim($input['hallintaosoite'] ?? ''),
'serial' => trim($input['serial'] ?? ''),
- 'site_id' => $input['site_id'] ?? null,
+ 'site_id' => null,
'laitetila_id' => $input['laitetila_id'] ?? null,
'funktio' => trim($input['funktio'] ?? ''),
'tyyppi' => trim($input['tyyppi'] ?? ''),
@@ -1979,7 +1944,7 @@ switch ($action) {
// Päivitä olemassa oleva: merkitse varatuksi laitteelle
$existing['tila'] = 'varattu';
$existing['nimi'] = $device['nimi'];
- $existing['site_id'] = $device['site_id'];
+ $existing['site_id'] = $device['laitetila_id'];
$existing['muokattu'] = date('Y-m-d H:i:s');
$existing['muokkaaja'] = currentUser();
dbSaveIpam($companyId, $existing);
@@ -1991,7 +1956,7 @@ switch ($action) {
'nimi' => $device['nimi'],
'verkko' => $cleanIp,
'vlan_id' => null,
- 'site_id' => $device['site_id'],
+ 'site_id' => $device['laitetila_id'],
'tila' => 'varattu',
'asiakas' => '',
'lisatiedot' => 'Automaattinen varaus laitteelta: ' . $device['nimi'],
diff --git a/db.php b/db.php
index fe558f0..c5217a5 100644
--- a/db.php
+++ b/db.php
@@ -639,6 +639,20 @@ function initDatabase(): void {
try {
$db->query("UPDATE users SET role = 'user' WHERE role = 'admin'");
} catch (\Throwable $e) { /* ohitetaan */ }
+
+ // Migraatio: yhdistä sites → laitetilat (kopioi vanhat sijainnit laitetiloiksi)
+ try {
+ // Kopioi sites-taulun rivit laitetilat-tauluun (ohita duplikaatit)
+ $db->query("
+ INSERT IGNORE INTO laitetilat (id, company_id, nimi, kuvaus, osoite, luotu, muokattu, muokkaaja)
+ SELECT id, company_id, nimi, '', CONCAT(IFNULL(osoite,''), IF(kaupunki != '', CONCAT(', ', kaupunki), '')), NOW(), NOW(), ''
+ FROM sites
+ ");
+ // Päivitä laitteiden laitetila_id vanhoista site_id-viittauksista
+ $db->query("UPDATE devices SET laitetila_id = site_id WHERE laitetila_id IS NULL AND site_id IS NOT NULL");
+ // Päivitä IPAM-merkintöjen site_id viittaamaan laitetiloihin (jo sama ID)
+ // (ei tarvitse muuttaa koska ID:t ovat samat)
+ } catch (\Throwable $e) { /* ohitetaan */ }
}
// ==================== YRITYKSET ====================
@@ -1040,40 +1054,16 @@ function dbDeleteCustomer(string $customerId): void {
_dbExecute("DELETE FROM customers WHERE id = ?", [$customerId]);
}
-// ==================== SIJAINNIT (SITES) ====================
-
-function dbLoadSites(string $companyId): array {
- return _dbFetchAll("SELECT * FROM sites WHERE company_id = ? ORDER BY nimi", [$companyId]);
-}
-
-function dbSaveSite(string $companyId, array $site): void {
- _dbExecute("
- INSERT INTO sites (id, company_id, nimi, osoite, kaupunki)
- VALUES (:id, :company_id, :nimi, :osoite, :kaupunki)
- ON DUPLICATE KEY UPDATE
- nimi = VALUES(nimi), osoite = VALUES(osoite), kaupunki = VALUES(kaupunki)
- ", [
- 'id' => $site['id'],
- 'company_id' => $companyId,
- 'nimi' => $site['nimi'] ?? '',
- 'osoite' => $site['osoite'] ?? '',
- 'kaupunki' => $site['kaupunki'] ?? '',
- ]);
-}
-
-function dbDeleteSite(string $siteId): void {
- // Nollaa viittaavien laitteiden site_id
- _dbExecute("UPDATE devices SET site_id = NULL WHERE site_id = ?", [$siteId]);
- _dbExecute("DELETE FROM sites WHERE id = ?", [$siteId]);
-}
+// ==================== SIJAINNIT (SITES) — POISTETTU, KÄYTETÄÄN LAITETILOJA ====================
+// Sites on yhdistetty laitetiloihin. Migraatio kopioi vanhat sites → laitetilat.
+// dbLoadSites, dbSaveSite, dbDeleteSite poistettu.
// ==================== LAITTEET (DEVICES) ====================
function dbLoadDevices(string $companyId): array {
$devices = _dbFetchAll("
- SELECT d.*, s.nimi AS site_name, lt.nimi AS laitetila_name
+ SELECT d.*, lt.nimi AS laitetila_name
FROM devices d
- LEFT JOIN sites s ON d.site_id = s.id
LEFT JOIN laitetilat lt ON d.laitetila_id = lt.id
WHERE d.company_id = ?
ORDER BY d.nimi
@@ -1121,9 +1111,9 @@ function dbDeleteDevice(string $deviceId): void {
function dbLoadIpam(string $companyId): array {
$rows = _dbFetchAll("
- SELECT i.*, s.nimi AS site_name
+ SELECT i.*, lt.nimi AS site_name
FROM ipam i
- LEFT JOIN sites s ON i.site_id = s.id
+ LEFT JOIN laitetilat lt ON i.site_id = lt.id
WHERE i.company_id = ?
ORDER BY i.tyyppi, i.vlan_id, i.verkko
", [$companyId]);
diff --git a/index.html b/index.html
index 6473bff..a3879f0 100644
--- a/index.html
+++ b/index.html
@@ -203,7 +203,6 @@
-
@@ -241,35 +240,6 @@
-
-
-
-
-
-
-
-
-
-
- | Nimi |
- Osoite |
- Kaupunki |
- Laitteita |
- Toiminnot |
-
-
-
-
-
-
Ei sijainteja vielä. Lisää ensimmäinen sijainti.
-
-
-
- 0 sijaintia
-
-
-
-
@@ -1763,15 +1733,9 @@
-
-
-
-
-
+
@@ -1860,7 +1824,7 @@
-
+
diff --git a/script.js b/script.js
index 6d0ee60..7b574b3 100644
--- a/script.js
+++ b/script.js
@@ -274,9 +274,9 @@ function switchToTab(target, subTab) {
if (target === 'customers') loadCustomers();
if (target === 'leads') loadLeads();
if (target === 'tekniikka') {
- loadDevices(); loadSitesTab(); loadIpam();
+ loadDevices(); loadIpam();
// Palauta sub-tab
- const validSubTabs = ['devices', 'sites', 'ipam'];
+ const validSubTabs = ['devices', 'ipam'];
if (subTab && validSubTabs.includes(subTab)) switchSubTab(subTab);
}
if (target === 'archive') loadArchive();
@@ -2493,8 +2493,6 @@ async function showCompanyDetail(id) {
// Lataa postilaatikot
loadMailboxes();
- // Lataa sijainnit
- loadSites();
// Lataa käyttäjäoikeudet
loadCompanyUsers(id);
}
@@ -2848,7 +2846,6 @@ async function toggleCompanyUser(userId, companyId, add) {
// ==================== LAITTEET (DEVICES) ====================
let devicesData = [];
-let sitesData = [];
async function loadDevices() {
try {
@@ -2865,7 +2862,7 @@ function renderDevices() {
(d.nimi || '').toLowerCase().includes(query) ||
(d.hallintaosoite || '').toLowerCase().includes(query) ||
(d.serial || '').toLowerCase().includes(query) ||
- (d.site_name || '').toLowerCase().includes(query) ||
+ (d.laitetila_name || '').toLowerCase().includes(query) ||
(d.funktio || '').toLowerCase().includes(query) ||
(d.tyyppi || '').toLowerCase().includes(query) ||
(d.malli || '').toLowerCase().includes(query)
@@ -2884,7 +2881,7 @@ function renderDevices() {
${esc(d.nimi)} |
${esc(d.hallintaosoite || '-')} |
${esc(d.serial || '-')} |
- ${d.site_name ? esc(d.site_name) : '-'} |
+ ${d.laitetila_name ? esc(d.laitetila_name) : '-'} |
${esc(d.tyyppi || '-')} |
${esc(d.funktio || '-')} |
${esc(d.malli || '-')} |
@@ -2924,8 +2921,7 @@ async function editDevice(id) {
document.getElementById('device-form-malli').value = d.malli || '';
document.getElementById('device-form-ping-check').checked = d.ping_check || false;
document.getElementById('device-form-lisatiedot').value = d.lisatiedot || '';
- await loadSitesAndLaitetilatForDropdown();
- document.getElementById('device-form-site').value = d.site_id || '';
+ await loadLaitetilatForDropdown();
document.getElementById('device-form-laitetila').value = d.laitetila_id || '';
document.getElementById('device-modal-title').textContent = 'Muokkaa laitetta';
document.getElementById('device-modal').style.display = 'flex';
@@ -2939,17 +2935,14 @@ async function deleteDevice(id, name) {
} catch (e) { alert(e.message); }
}
-async function loadSitesForDropdown() { await loadSitesAndLaitetilatForDropdown(); }
+async function loadSitesForDropdown() { await loadLaitetilatForDropdown(); }
+async function loadSitesAndLaitetilatForDropdown() { await loadLaitetilatForDropdown(); }
-async function loadSitesAndLaitetilatForDropdown() {
+async function loadLaitetilatForDropdown() {
try {
- const [sites, tilat] = await Promise.all([apiCall('sites'), apiCall('laitetilat')]);
- sitesData = sites;
- const siteSel = document.getElementById('device-form-site');
- siteSel.innerHTML = '' +
- sitesData.map(s => ``).join('');
+ const tilat = await apiCall('laitetilat');
const tilaSel = document.getElementById('device-form-laitetila');
- tilaSel.innerHTML = '' +
+ tilaSel.innerHTML = '' +
tilat.map(t => ``).join('');
} catch (e) { console.error(e); }
}
@@ -2957,7 +2950,7 @@ async function loadSitesAndLaitetilatForDropdown() {
document.getElementById('btn-add-device')?.addEventListener('click', async () => {
document.getElementById('device-form-id').value = '';
document.getElementById('device-form').reset();
- await loadSitesAndLaitetilatForDropdown();
+ await loadLaitetilatForDropdown();
document.getElementById('device-modal-title').textContent = 'Lisää laite';
document.getElementById('device-modal').style.display = 'flex';
});
@@ -2976,7 +2969,6 @@ document.getElementById('device-form')?.addEventListener('submit', async (e) =>
nimi: document.getElementById('device-form-nimi').value.trim(),
hallintaosoite: document.getElementById('device-form-hallintaosoite').value.trim(),
serial: document.getElementById('device-form-serial').value.trim(),
- site_id: document.getElementById('device-form-site').value || null,
laitetila_id: document.getElementById('device-form-laitetila').value || null,
funktio: document.getElementById('device-form-funktio').value.trim(),
tyyppi: document.getElementById('device-form-tyyppi').value.trim(),
@@ -3011,86 +3003,8 @@ document.querySelectorAll('#tab-content-tekniikka .sub-tab').forEach(btn => {
btn.addEventListener('click', () => switchSubTab(btn.dataset.subtab));
});
-// ==================== SIJAINNIT (SITES) — TEKNIIKKA TAB ====================
-
-let sitesTabData = [];
-
-async function loadSitesTab() {
- try {
- sitesData = await apiCall('sites');
- sitesTabData = sitesData;
- renderSitesTab();
- } catch (e) { console.error(e); }
-}
-
-function renderSitesTab() {
- const query = (document.getElementById('site-search-input')?.value || '').toLowerCase().trim();
- let filtered = sitesTabData;
- if (query) {
- filtered = sitesTabData.filter(s =>
- (s.nimi || '').toLowerCase().includes(query) ||
- (s.osoite || '').toLowerCase().includes(query) ||
- (s.kaupunki || '').toLowerCase().includes(query)
- );
- }
- const tbody = document.getElementById('site-tbody');
- const noSites = document.getElementById('no-sites-tab');
- if (filtered.length === 0) {
- tbody.innerHTML = '';
- if (noSites) noSites.style.display = 'block';
- } else {
- if (noSites) noSites.style.display = 'none';
- tbody.innerHTML = filtered.map(s => {
- const deviceCount = devicesData.filter(d => d.site_id === s.id).length;
- return `
- | ${esc(s.nimi)} |
- ${esc(s.osoite || '-')} |
- ${esc(s.kaupunki || '-')} |
- ${deviceCount} |
-
-
-
- |
-
`;
- }).join('');
- }
- document.getElementById('site-count').textContent = filtered.length + ' sijaintia';
-}
-
-function editSiteTab(id) {
- const s = sitesData.find(x => x.id === id);
- if (!s) return;
- document.getElementById('site-form-id').value = s.id;
- document.getElementById('site-form-nimi').value = s.nimi || '';
- document.getElementById('site-form-osoite').value = s.osoite || '';
- document.getElementById('site-form-kaupunki').value = s.kaupunki || '';
- document.getElementById('site-form-title').textContent = 'Muokkaa sijaintia';
- document.getElementById('site-form-container').style.display = '';
- // Varmista että asetukset-tab ja yrityksen tiedot näkyvissä, scrollaa lomakkeeseen
- switchToTab('settings');
- document.getElementById('company-detail-view').style.display = '';
- document.getElementById('companies-list-view').style.display = 'none';
- setTimeout(() => document.getElementById('site-form-container')?.scrollIntoView({ behavior: 'smooth', block: 'center' }), 100);
-}
-
-// Alias vanhalle editSite-funktiolle
-function editSite(id) { editSiteTab(id); }
-
-async function deleteSite(id, name) {
- if (!confirm(`Poistetaanko sijainti "${name}"? Laitteet joissa tämä sijainti on menettävät sijainti-viittauksen.`)) return;
- try {
- await apiCall('site_delete', 'POST', { id });
- loadSitesTab();
- loadDevices();
- } catch (e) { alert(e.message); }
-}
-
-// Alias loadSites (Tekniikka sub-tab kutsuun)
-async function loadSites() { await loadSitesTab(); }
-
-function renderSites() {}
-
-document.getElementById('site-search-input')?.addEventListener('input', () => renderSitesTab());
+// ==================== SIJAINNIT — YHDISTETTY LAITETILOIHIN ====================
+// Sites-koodi poistettu: sijainnit hallitaan nyt Laitetilat-välilehdellä.
// ==================== IPAM ====================
@@ -3521,12 +3435,13 @@ async function ipamAddFromFree(verkko) {
document.getElementById('ipam-form-nimi')?.focus();
}
+let _ipamLaitetilatCache = null;
async function loadIpamSitesDropdown() {
try {
- if (!sitesData || sitesData.length === 0) sitesData = await apiCall('sites');
+ if (!_ipamLaitetilatCache || _ipamLaitetilatCache.length === 0) _ipamLaitetilatCache = await apiCall('laitetilat');
const sel = document.getElementById('ipam-form-site');
sel.innerHTML = '' +
- sitesData.map(s => ``).join('');
+ _ipamLaitetilatCache.map(t => ``).join('');
} catch (e) { console.error(e); }
}