Files
2026-10-21 22:00:44 +02:00

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>