249 lines
11 KiB
PHP
249 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* admin/manual_add.php
|
|
* Menu de navigation mis a jour (ajout Import .ntm, Edition de site).
|
|
*/
|
|
declare(strict_types=1);
|
|
require_once __DIR__ . '/admin_auth.php';
|
|
require_once __DIR__ . '/../lib/db.php';
|
|
require_once __DIR__ . '/_nav.php';
|
|
|
|
require_admin_auth();
|
|
$db = pdo();
|
|
$user = current_admin_username();
|
|
|
|
$operators = $db->query('SELECT id, country_id, mcc, mnc, brand FROM operators ORDER BY brand')->fetchAll(PDO::FETCH_ASSOC);
|
|
$successMessage = null;
|
|
$errorMessage = null;
|
|
|
|
function composeLocationDesc(string $tech, ?string $enb, ?string $band, ?string $frequency, $azimuth, ?string $bandwidth, string $address): string
|
|
{
|
|
$techLabel = ['2G' => 'GSM', '3G' => 'UMTS', '4G' => 'LTE', '5G' => 'NR', 'CDMA' => 'CDMA'][$tech] ?? $tech;
|
|
$segments = [];
|
|
|
|
if ($enb !== null && $enb !== '') {
|
|
$segments[] = "eNB ID $enb";
|
|
}
|
|
|
|
$radioLine = trim($techLabel . ' ' . ($band ?? '') . ' ' . ($frequency ?? ''));
|
|
if ($radioLine !== $techLabel) {
|
|
$segments[] = $radioLine;
|
|
}
|
|
|
|
if ($azimuth !== null && $azimuth !== '') {
|
|
$segments[] = $azimuth . '°';
|
|
}
|
|
|
|
if ($bandwidth !== null && $bandwidth !== '') {
|
|
$segments[] = $bandwidth;
|
|
}
|
|
|
|
if ($address !== '') {
|
|
$segments[] = $address;
|
|
}
|
|
|
|
return implode(' - ', $segments);
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
require_csrf_token();
|
|
|
|
$operatorId = (int)($_POST['operator_id'] ?? 0);
|
|
$lat = (float)($_POST['lat'] ?? 0);
|
|
$lon = (float)($_POST['lon'] ?? 0);
|
|
$addressDesc = trim($_POST['address_desc'] ?? '');
|
|
$batchLabel = trim($_POST['batch_label'] ?? ('Saisie manuelle - ' . $addressDesc));
|
|
|
|
$techs = $_POST['cell_tech'] ?? [];
|
|
$cellIds = $_POST['cell_id'] ?? [];
|
|
$areaCodes = $_POST['cell_area_code'] ?? [];
|
|
$subIds = $_POST['cell_sub_id'] ?? [];
|
|
$subIds2 = $_POST['cell_sub_id2'] ?? [];
|
|
$arfcns = $_POST['cell_arfcn'] ?? [];
|
|
$bands = $_POST['cell_band'] ?? [];
|
|
$frequencies = $_POST['cell_frequency'] ?? [];
|
|
$azimuths = $_POST['cell_azimuth'] ?? [];
|
|
$bandwidths = $_POST['cell_bandwidth'] ?? [];
|
|
|
|
if (!$operatorId || !$lat || !$lon || empty($techs)) {
|
|
$errorMessage = "Operateur, coordonnees et au moins une cellule sont obligatoires.";
|
|
} else {
|
|
$opStmt = $db->prepare('SELECT mcc, mnc FROM operators WHERE id = ?');
|
|
$opStmt->execute([$operatorId]);
|
|
$op = $opStmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$op) {
|
|
$errorMessage = "Operateur invalide.";
|
|
} else {
|
|
$db->beginTransaction();
|
|
try {
|
|
$batchStmt = $db->prepare('INSERT INTO staging_batches (label, created_by) VALUES (?, ?)');
|
|
$batchStmt->execute([$batchLabel, $user]);
|
|
$batchId = (int)$db->lastInsertId();
|
|
|
|
$insertStmt = $db->prepare('
|
|
INSERT INTO staging_cells
|
|
(batch_id, tech, mcc, mnc, operator_guess, cell_id, area_code, sub_id, sub_id2,
|
|
band, frequency, azimuth, bandwidth, address_desc,
|
|
lat, lon, location_desc, arfcn, source, status)
|
|
VALUES
|
|
(:batch_id, :tech, :mcc, :mnc, :operator_guess, :cell_id, :area_code, :sub_id, :sub_id2,
|
|
:band, :frequency, :azimuth, :bandwidth, :address_desc,
|
|
:lat, :lon, :location_desc, :arfcn, "manual_form", "pending")
|
|
');
|
|
|
|
$count = 0;
|
|
foreach ($techs as $i => $tech) {
|
|
if ($tech === '' || $cellIds[$i] === '' || $areaCodes[$i] === '') continue;
|
|
|
|
$enb = $subIds2[$i] !== '' ? $subIds2[$i] : null;
|
|
$band = $bands[$i] !== '' ? $bands[$i] : null;
|
|
$frequency = $frequencies[$i] !== '' ? $frequencies[$i] : null;
|
|
$azimuth = $azimuths[$i] !== '' ? (int)$azimuths[$i] : null;
|
|
$bandwidth = $bandwidths[$i] !== '' ? $bandwidths[$i] : null;
|
|
|
|
$composedLocation = composeLocationDesc($tech, $enb, $band, $frequency, $azimuth, $bandwidth, $addressDesc);
|
|
|
|
$insertStmt->execute([
|
|
':batch_id' => $batchId, ':tech' => $tech, ':mcc' => $op['mcc'], ':mnc' => $op['mnc'], ':operator_guess' => $operatorId,
|
|
':cell_id' => $cellIds[$i], ':area_code' => $areaCodes[$i],
|
|
':sub_id' => $subIds[$i] !== '' ? $subIds[$i] : null, ':sub_id2' => $enb,
|
|
':band' => $band, ':frequency' => $frequency, ':azimuth' => $azimuth, ':bandwidth' => $bandwidth,
|
|
':address_desc' => $addressDesc,
|
|
':lat' => $lat, ':lon' => $lon, ':location_desc' => $composedLocation,
|
|
':arfcn' => $arfcns[$i] !== '' ? $arfcns[$i] : null,
|
|
]);
|
|
$count++;
|
|
}
|
|
$db->commit();
|
|
$successMessage = "$count cellule(s) envoyee(s) en validation (lot #$batchId).";
|
|
} catch (Throwable $e) {
|
|
$db->rollBack();
|
|
$errorMessage = "Erreur : " . $e->getMessage();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8"><title>NetMonster Admin - Ajout manuel</title>
|
|
<link rel="stylesheet" href="assets/theme.css">
|
|
<style>
|
|
table#cellsTable th, table#cellsTable td { font-size: .78rem; }
|
|
table#cellsTable input, table#cellsTable select { font-size: .78rem; padding: .35rem; }
|
|
.preview-line { font-family: monospace; font-size: .78rem; color: var(--text-muted); background: var(--surface-alt); padding: .3rem .5rem; border-radius: 4px; margin-top: .2rem; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<?php render_admin_nav('manual_add.php', $user); ?>
|
|
|
|
<div class="page-wrap">
|
|
<h1>Ajout manuel d'un site</h1>
|
|
<p class="page-subtitle">Un site peut porter plusieurs cellules/secteurs, chacune avec sa propre bande, frequence, azimut et largeur de bande.</p>
|
|
|
|
<?php if ($successMessage): ?><p class="msg-ok"><?= htmlspecialchars($successMessage) ?></p><?php endif; ?>
|
|
<?php if ($errorMessage): ?><p class="msg-err"><?= htmlspecialchars($errorMessage) ?></p><?php endif; ?>
|
|
|
|
<form method="post" id="manualForm">
|
|
<?= csrf_field() ?>
|
|
<fieldset>
|
|
<legend>Localisation du site</legend>
|
|
<div class="form-grid wide-3">
|
|
<div>
|
|
<label>Operateur</label>
|
|
<select name="operator_id" required>
|
|
<option value="">-- choisir --</option>
|
|
<?php foreach ($operators as $o): ?><option value="<?= $o['id'] ?>"><?= htmlspecialchars($o['brand'] . ' (' . $o['mcc'] . '-' . $o['mnc'] . ')') ?></option><?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div><label>Latitude</label><input type="number" step="0.000001" name="lat" required></div>
|
|
<div><label>Longitude</label><input type="number" step="0.000001" name="lon" required></div>
|
|
</div>
|
|
<div class="form-grid" style="margin-top:0.6rem;">
|
|
<div><label>Adresse / lieu (ex: E25 Echangeur Burange/Hellange, Dudelange)</label><input type="text" name="address_desc" placeholder="Description du lieu"></div>
|
|
<div><label>Libelle du lot (optionnel)</label><input type="text" name="batch_label"></div>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset>
|
|
<legend>Cellules / secteurs de ce site</legend>
|
|
<table class="data-table" id="cellsTable">
|
|
<thead>
|
|
<tr>
|
|
<th>Techno</th><th>Cell ID</th><th>Area code</th>
|
|
<th>Sub ID (BSIC/PSC/PCI)</th><th>eNB/RNC</th>
|
|
<th>Bande</th><th>Frequence</th><th>Azimut (°)</th><th>Largeur bande</th>
|
|
<th>ARFCN</th><th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="cellsBody"></tbody>
|
|
</table>
|
|
<button type="button" class="btn-primary" onclick="addCellRow()">+ Ajouter une cellule</button>
|
|
<p class="page-subtitle" style="margin-top:.8rem;">Apercu du champ Location genere pour la premiere ligne :</p>
|
|
<div class="preview-line" id="previewLine">—</div>
|
|
</fieldset>
|
|
|
|
<button type="submit" class="primary">Envoyer en validation</button>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
const techLabels = { '2G': 'GSM', '3G': 'UMTS', '4G': 'LTE', '5G': 'NR', 'CDMA': 'CDMA' };
|
|
|
|
function addCellRow() {
|
|
const tbody = document.getElementById('cellsBody');
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td><select name="cell_tech[]" required onchange="updatePreview()">
|
|
<option value="">--</option>
|
|
<option value="2G">2G</option><option value="3G">3G</option>
|
|
<option value="4G">4G</option><option value="5G">5G</option><option value="CDMA">CDMA</option>
|
|
</select></td>
|
|
<td><input type="text" name="cell_id[]" required></td>
|
|
<td><input type="text" name="cell_area_code[]" required></td>
|
|
<td><input type="text" name="cell_sub_id[]"></td>
|
|
<td><input type="text" name="cell_sub_id2[]" placeholder="ex: 4620" oninput="updatePreview()"></td>
|
|
<td><input type="text" name="cell_band[]" placeholder="ex: B7" oninput="updatePreview()"></td>
|
|
<td><input type="text" name="cell_frequency[]" placeholder="ex: 700MHz" oninput="updatePreview()"></td>
|
|
<td><input type="number" name="cell_azimuth[]" min="0" max="359" placeholder="ex: 280" oninput="updatePreview()"></td>
|
|
<td><input type="text" name="cell_bandwidth[]" placeholder="ex: 10MHz" oninput="updatePreview()"></td>
|
|
<td><input type="text" name="cell_arfcn[]"></td>
|
|
<td><button type="button" class="reject-small" onclick="this.closest('tr').remove(); updatePreview();">Retirer</button></td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
tr.querySelector('select[name="cell_tech[]"]').addEventListener('change', updatePreview);
|
|
updatePreview();
|
|
}
|
|
|
|
function updatePreview() {
|
|
const firstRow = document.querySelector('#cellsBody tr');
|
|
const preview = document.getElementById('previewLine');
|
|
if (!firstRow) { preview.textContent = '—'; return; }
|
|
|
|
const tech = firstRow.querySelector('[name="cell_tech[]"]').value;
|
|
const enb = firstRow.querySelector('[name="cell_sub_id2[]"]').value;
|
|
const band = firstRow.querySelector('[name="cell_band[]"]').value;
|
|
const freq = firstRow.querySelector('[name="cell_frequency[]"]').value;
|
|
const az = firstRow.querySelector('[name="cell_azimuth[]"]').value;
|
|
const bw = firstRow.querySelector('[name="cell_bandwidth[]"]').value;
|
|
const address = document.querySelector('[name="address_desc"]').value;
|
|
|
|
const techLabel = techLabels[tech] || tech || '';
|
|
const segments = [];
|
|
if (enb) segments.push('eNB ID ' + enb);
|
|
const radioLine = [techLabel, band, freq].filter(Boolean).join(' ');
|
|
if (radioLine && radioLine !== techLabel) segments.push(radioLine);
|
|
if (az) segments.push(az + '°');
|
|
if (bw) segments.push(bw);
|
|
if (address) segments.push(address);
|
|
|
|
preview.textContent = segments.length ? segments.join(' - ') : '—';
|
|
}
|
|
|
|
document.querySelector('[name="address_desc"]')?.addEventListener('input', updatePreview);
|
|
addCellRow();
|
|
</script>
|
|
</body>
|
|
</html>
|