Files
trf_certest/public/userarea/routine_steps.php
T
2026-09-05 13:41:26 +02:00

976 lines
43 KiB
PHP

<?php
include('include/headscript.php');
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (!$user->hasRole('Admin') && !$user->hasRole('SuperUser')) {
header('Location: import_dashboard.php');
exit;
}
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
/*
* CSRF token.
*/
if (empty($_SESSION['routine_steps_csrf'])) {
$_SESSION['routine_steps_csrf'] = bin2hex(random_bytes(32));
}
$csrfToken = $_SESSION['routine_steps_csrf'];
function cleanInput($value)
{
return trim((string)($value ?? ''));
}
/*
* Determina l'id routine dalla querystring (GET) o dal POST.
*/
$idroutine = 0;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$idroutine = (int)($_POST['idroutine'] ?? 0);
} else {
$idroutine = (int)($_GET['idroutine'] ?? 0);
}
function redirectToSteps($idroutine, $type, $message)
{
$_SESSION['routine_steps_flash'] = [
'type' => $type,
'message' => $message
];
header('Location: routine_steps.php?idroutine=' . (int)$idroutine);
exit;
}
/*
* Converte lettera colonna Excel in indice zero-based. Accetta anche numeri.
* A=0, B=1, ... Z=25, AA=26, ...
*/
function colToIndex($ref)
{
$ref = strtoupper(trim((string)$ref));
if ($ref === '') {
return null;
}
if (ctype_digit($ref)) {
return (int)$ref;
}
$index = 0;
$len = strlen($ref);
for ($i = 0; $i < $len; $i++) {
$c = $ref[$i];
if ($c < 'A' || $c > 'Z') {
return null;
}
$index = $index * 26 + (ord($c) - ord('A') + 1);
}
return $index - 1;
}
/*
* Converte indice zero-based in lettera Excel (per mostrare gli step salvati).
*/
function indexToCol($index)
{
$index = (int)$index;
if ($index < 0) {
return '';
}
$letters = '';
$index += 1;
while ($index > 0) {
$rem = ($index - 1) % 26;
$letters = chr(ord('A') + $rem) . $letters;
$index = intdiv($index - 1, 26);
}
return $letters;
}
/*
* Normalizza una stringa di colonne separate da virgola ("D, F, H") in
* un array di indici zero-based, validando ogni riferimento.
*/
function parseColumnList($raw)
{
$raw = trim((string)$raw);
if ($raw === '') {
return [];
}
$parts = preg_split('/[\s,;]+/', $raw);
$out = [];
foreach ($parts as $p) {
if ($p === '') {
continue;
}
$idx = colToIndex($p);
if ($idx === null) {
throw new Exception('Riferimento colonna non valido: "' . $p . '". Usa lettere come D, F, H.');
}
$out[] = $idx;
}
return $out;
}
/*
* Verifica esistenza routine ed engine = 1.
*/
$routine = null;
if ($idroutine > 0) {
$stmt = $pdo->prepare("SELECT idroutine, name, description, engine, headerrow FROM routine WHERE idroutine = :id LIMIT 1");
$stmt->execute([':id' => $idroutine]);
$routine = $stmt->fetch(PDO::FETCH_ASSOC);
}
if (!$routine) {
$_SESSION['routine_admin_flash'] = ['type' => 'error', 'message' => 'Routine non trovata.'];
header('Location: routine_admin.php');
exit;
}
if ((int)$routine['engine'] !== 1) {
$_SESSION['routine_admin_flash'] = ['type' => 'error', 'message' => 'Questa routine è di tipo File: gli step non sono configurabili da qui.'];
header('Location: routine_admin.php');
exit;
}
/*
* Vocabolario operazioni disponibili (deve combaciare con gli handler di _engine.php).
*/
$operations = ['merge', 'copy', 'clear', 'trim', 'replace', 'uppercase', 'lowercase'];
/*
* Gestione azioni POST.
*/
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$postedToken = $_POST['csrf_token'] ?? '';
if (!hash_equals($csrfToken, $postedToken)) {
throw new Exception('Token di sicurezza non valido.');
}
$action = cleanInput($_POST['action'] ?? '');
if ($action === 'create' || $action === 'update') {
$operation = strtolower(cleanInput($_POST['operation'] ?? ''));
$active = (int)($_POST['active'] ?? 1) === 1 ? 1 : 0;
if (!in_array($operation, $operations, true)) {
throw new Exception('Operazione non valida.');
}
/*
* Costruzione params in base all'operazione.
* Le colonne arrivano come lettere e vengono convertite in indici.
*/
$params = [];
if ($operation === 'merge') {
$sources = parseColumnList($_POST['source_columns'] ?? '');
$target = colToIndex($_POST['target_column'] ?? '');
if (empty($sources)) {
throw new Exception('Indica almeno una colonna sorgente (es. D, F, H).');
}
if ($target === null) {
throw new Exception('Indica la colonna di destinazione (es. J).');
}
$params = [
'source_columns' => $sources,
'target_column' => $target,
'separator' => (string)($_POST['separator'] ?? ' '),
'skip_empty' => isset($_POST['skip_empty']) ? true : false,
];
} elseif ($operation === 'copy') {
$src = colToIndex($_POST['source_column'] ?? '');
$dst = colToIndex($_POST['target_column'] ?? '');
if ($src === null || $dst === null) {
throw new Exception('Indica colonna sorgente e destinazione (es. D e J).');
}
$params = ['source_column' => $src, 'target_column' => $dst];
} elseif ($operation === 'clear' || $operation === 'trim' || $operation === 'uppercase' || $operation === 'lowercase') {
$cols = parseColumnList($_POST['columns'] ?? '');
if (empty($cols)) {
throw new Exception('Indica almeno una colonna (es. D, F).');
}
$params = ['columns' => $cols];
if ($operation === 'trim') {
$chars = (string)($_POST['chars'] ?? '');
if ($chars !== '') {
$params['chars'] = $chars;
}
}
} elseif ($operation === 'replace') {
$cols = parseColumnList($_POST['columns'] ?? '');
if (empty($cols)) {
throw new Exception('Indica almeno una colonna (es. D, F).');
}
$params = [
'columns' => $cols,
'search' => (string)($_POST['search'] ?? ''),
'replace' => (string)($_POST['replace'] ?? ''),
'regex' => isset($_POST['regex']) ? true : false,
];
}
$paramsJson = json_encode($params, JSON_UNESCAPED_UNICODE);
if ($action === 'create') {
// step_order = max + 1
$stmt = $pdo->prepare("SELECT COALESCE(MAX(step_order), 0) + 1 AS next_order FROM routine_step WHERE idroutine = :id");
$stmt->execute([':id' => $idroutine]);
$nextOrder = (int)$stmt->fetchColumn();
$stmt = $pdo->prepare("
INSERT INTO routine_step (idroutine, step_order, operation, params, active)
VALUES (:id, :ord, :op, :params, :active)
");
$stmt->execute([
':id' => $idroutine,
':ord' => $nextOrder,
':op' => $operation,
':params' => $paramsJson,
':active' => $active,
]);
redirectToSteps($idroutine, 'success', 'Step aggiunto correttamente.');
} else {
$idstep = (int)($_POST['idstep'] ?? 0);
if ($idstep <= 0) {
throw new Exception('ID step non valido.');
}
$stmt = $pdo->prepare("
UPDATE routine_step
SET operation = :op, params = :params, active = :active
WHERE idstep = :idstep AND idroutine = :id
");
$stmt->execute([
':op' => $operation,
':params' => $paramsJson,
':active' => $active,
':idstep' => $idstep,
':id' => $idroutine,
]);
redirectToSteps($idroutine, 'success', 'Step aggiornato correttamente.');
}
}
if ($action === 'delete') {
$idstep = (int)($_POST['idstep'] ?? 0);
if ($idstep <= 0) {
throw new Exception('ID step non valido.');
}
$stmt = $pdo->prepare("DELETE FROM routine_step WHERE idstep = :idstep AND idroutine = :id");
$stmt->execute([':idstep' => $idstep, ':id' => $idroutine]);
redirectToSteps($idroutine, 'success', 'Step eliminato.');
}
if ($action === 'move') {
$idstep = (int)($_POST['idstep'] ?? 0);
$dir = cleanInput($_POST['direction'] ?? '');
if ($idstep <= 0 || !in_array($dir, ['up', 'down'], true)) {
throw new Exception('Parametri di spostamento non validi.');
}
// Carica step correnti ordinati.
$stmt = $pdo->prepare("SELECT idstep, step_order FROM routine_step WHERE idroutine = :id ORDER BY step_order ASC, idstep ASC");
$stmt->execute([':id' => $idroutine]);
$all = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Trova posizione corrente.
$pos = null;
foreach ($all as $i => $s) {
if ((int)$s['idstep'] === $idstep) {
$pos = $i;
break;
}
}
if ($pos !== null) {
$swapWith = $dir === 'up' ? $pos - 1 : $pos + 1;
if ($swapWith >= 0 && $swapWith < count($all)) {
$a = $all[$pos];
$b = $all[$swapWith];
// Scambia gli step_order dei due record.
$upd = $pdo->prepare("UPDATE routine_step SET step_order = :ord WHERE idstep = :idstep AND idroutine = :id");
$upd->execute([':ord' => (int)$b['step_order'], ':idstep' => (int)$a['idstep'], ':id' => $idroutine]);
$upd->execute([':ord' => (int)$a['step_order'], ':idstep' => (int)$b['idstep'], ':id' => $idroutine]);
}
}
redirectToSteps($idroutine, 'success', 'Ordine aggiornato.');
}
throw new Exception('Azione non valida.');
} catch (Exception $e) {
redirectToSteps($idroutine, 'error', $e->getMessage());
}
}
/*
* Caricamento step della routine.
*/
$stmt = $pdo->prepare("SELECT idstep, step_order, operation, params, active FROM routine_step WHERE idroutine = :id ORDER BY step_order ASC, idstep ASC");
$stmt->execute([':id' => $idroutine]);
$steps = $stmt->fetchAll(PDO::FETCH_ASSOC);
/*
* Costruisce una descrizione leggibile dei params per la tabella.
*/
function describeStep($operation, array $p)
{
$colsToLetters = function ($arr) {
return implode(', ', array_map('indexToCol', $arr));
};
switch ($operation) {
case 'merge':
return 'Unisce ' . $colsToLetters($p['source_columns'] ?? []) .
' → ' . indexToCol($p['target_column'] ?? 0) .
' (sep: "' . ($p['separator'] ?? ' ') . '")';
case 'copy':
return 'Copia ' . indexToCol($p['source_column'] ?? 0) . ' → ' . indexToCol($p['target_column'] ?? 0);
case 'clear':
return 'Svuota ' . $colsToLetters($p['columns'] ?? []);
case 'trim':
return 'Trim ' . $colsToLetters($p['columns'] ?? []) . (isset($p['chars']) ? ' (chars: "' . $p['chars'] . '")' : '');
case 'uppercase':
return 'Maiuscolo ' . $colsToLetters($p['columns'] ?? []);
case 'lowercase':
return 'Minuscolo ' . $colsToLetters($p['columns'] ?? []);
case 'replace':
return 'Sostituisci in ' . $colsToLetters($p['columns'] ?? []) .
': "' . ($p['search'] ?? '') . '" → "' . ($p['replace'] ?? '') . '"' .
(!empty($p['regex']) ? ' (regex)' : '');
default:
return $operation;
}
}
/*
* Prepara i dati step per il JS (params già in lettere per il form di modifica).
*/
$stepsForJs = [];
foreach ($steps as $s) {
$p = json_decode($s['params'] ?? '[]', true);
if (!is_array($p)) {
$p = [];
}
// Converte indici -> lettere per il form.
$letters = function ($arr) {
return implode(', ', array_map('indexToCol', $arr));
};
$formData = ['operation' => $s['operation'], 'active' => (int)$s['active']];
if ($s['operation'] === 'merge') {
$formData['source_columns'] = $letters($p['source_columns'] ?? []);
$formData['target_column'] = indexToCol($p['target_column'] ?? 0);
$formData['separator'] = $p['separator'] ?? ' ';
$formData['skip_empty'] = !empty($p['skip_empty']);
} elseif ($s['operation'] === 'copy') {
$formData['source_column'] = indexToCol($p['source_column'] ?? 0);
$formData['target_column'] = indexToCol($p['target_column'] ?? 0);
} elseif (in_array($s['operation'], ['clear', 'trim', 'uppercase', 'lowercase'], true)) {
$formData['columns'] = $letters($p['columns'] ?? []);
if ($s['operation'] === 'trim') {
$formData['chars'] = $p['chars'] ?? '';
}
} elseif ($s['operation'] === 'replace') {
$formData['columns'] = $letters($p['columns'] ?? []);
$formData['search'] = $p['search'] ?? '';
$formData['replace'] = $p['replace'] ?? '';
$formData['regex'] = !empty($p['regex']);
}
$stepsForJs[(int)$s['idstep']] = $formData;
}
$flash = $_SESSION['routine_steps_flash'] ?? null;
unset($_SESSION['routine_steps_flash']);
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Step Routine - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<style>
.small-muted {
font-size: 12px;
color: #6c757d;
}
.table-actions {
white-space: nowrap;
}
.op-badge {
font-family: Consolas, Monaco, monospace;
font-size: 12px;
background: #eef2ff;
color: #4338ca;
border: 1px solid #e0e7ff;
border-radius: 6px;
padding: 2px 8px;
text-transform: uppercase;
}
.step-inactive {
opacity: 0.55;
}
.col-hint {
background: #f0fdf4;
border: 1px solid #bbf7d0;
color: #166534;
border-radius: 8px;
padding: 8px 12px;
font-size: 13px;
}
.order-btns .btn {
padding: 2px 8px;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<?php include('top_stat_widget.php'); ?>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center justify-content-between">
<div>
<h6 class="mb-0">Step routine: <?= htmlspecialchars($routine['name'], ENT_QUOTES, 'UTF-8'); ?></h6>
<small class="text-muted">
Configura le operazioni applicate all'XLS. Vengono eseguite dall'alto verso il basso.
</small>
</div>
<div>
<a href="routine_admin.php" class="btn btn-light">
<i class="bx bx-arrow-back"></i> Torna alle routine
</a>
<button type="button" class="btn btn-primary" id="btnAddStep">
<i class="bx bx-plus"></i> Aggiungi step
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="col-hint mb-3">
Le colonne si indicano con le <strong>lettere dell'Excel</strong> (A, B, C, …). Per più colonne separale con la virgola: <strong>D, F, H</strong>.
</div>
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead>
<tr>
<th style="width: 70px;">Ordine</th>
<th style="width: 120px;">Operazione</th>
<th>Dettaglio</th>
<th style="width: 90px;">Stato</th>
<th style="width: 240px;">Azioni</th>
</tr>
</thead>
<tbody>
<?php if (empty($steps)): ?>
<tr>
<td colspan="5" class="text-center small-muted py-4">
Nessuno step configurato. Usa "Aggiungi step" per iniziare.
</td>
</tr>
<?php else: ?>
<?php foreach ($steps as $i => $s): ?>
<?php
$p = json_decode($s['params'] ?? '[]', true);
if (!is_array($p)) {
$p = [];
}
$isFirst = ($i === 0);
$isLast = ($i === count($steps) - 1);
$rowClass = ((int)$s['active'] === 1) ? '' : 'step-inactive';
?>
<tr class="<?= $rowClass; ?>">
<td class="text-center">
<div class="order-btns d-inline-flex flex-column gap-1">
<button type="button" class="btn btn-sm btn-outline-secondary btnMove"
data-idstep="<?= (int)$s['idstep']; ?>" data-direction="up"
<?= $isFirst ? 'disabled' : ''; ?>>
<i class="bx bx-chevron-up"></i>
</button>
<button type="button" class="btn btn-sm btn-outline-secondary btnMove"
data-idstep="<?= (int)$s['idstep']; ?>" data-direction="down"
<?= $isLast ? 'disabled' : ''; ?>>
<i class="bx bx-chevron-down"></i>
</button>
</div>
</td>
<td><span class="op-badge"><?= htmlspecialchars($s['operation'], ENT_QUOTES, 'UTF-8'); ?></span></td>
<td><?= htmlspecialchars(describeStep($s['operation'], $p), ENT_QUOTES, 'UTF-8'); ?></td>
<td>
<?php if ((int)$s['active'] === 1): ?>
<span class="badge bg-success">Attivo</span>
<?php else: ?>
<span class="badge bg-secondary">Disattivo</span>
<?php endif; ?>
</td>
<td class="table-actions">
<button type="button" class="btn btn-sm btn-outline-primary btnEditStep"
data-idstep="<?= (int)$s['idstep']; ?>">
Modifica
</button>
<button type="button" class="btn btn-sm btn-outline-danger btnDeleteStep"
data-idstep="<?= (int)$s['idstep']; ?>">
Elimina
</button>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<hr class="my-4">
<div class="d-flex align-items-center justify-content-between mb-2">
<div>
<h6 class="mb-0">Prova la routine</h6>
<small class="text-muted">Carica un XLS di esempio: viene mostrata la prima riga dati dopo l'intestazione, elaborata con gli step qui sopra. Il file non viene salvato.</small>
</div>
</div>
<div class="row g-2 align-items-end">
<div class="col-md-8">
<label for="previewFile" class="form-label">File XLS / XLSX di prova</label>
<input type="file" class="form-control" id="previewFile" accept=".xls,.xlsx">
</div>
<div class="col-md-4">
<button type="button" class="btn btn-outline-primary w-100" id="btnRunPreview">
<i class="bx bx-play-circle"></i> Esegui anteprima
</button>
</div>
</div>
<div id="previewStatus" class="small-muted mt-2"></div>
<div class="table-responsive mt-3" id="previewResultWrapper" style="display:none;">
<table class="table table-bordered table-sm align-middle" id="previewTable">
<thead>
<tr>
<th style="width: 90px;">Colonna</th>
<th style="width: 40%;">Intestazione</th>
<th>Valore dopo la routine</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<!-- Modal Step -->
<div class="modal fade" id="stepModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<form method="POST" class="modal-content" id="stepForm">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="idroutine" value="<?= (int)$idroutine; ?>">
<input type="hidden" name="action" id="stepFormAction" value="create">
<input type="hidden" name="idstep" id="idstep" value="">
<div class="modal-header">
<h5 class="modal-title" id="stepModalTitle">Aggiungi step</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<div class="col-md-8">
<label for="operation" class="form-label">Operazione *</label>
<select class="form-select" id="operation" name="operation" required>
<option value="merge">merge — unisci più colonne in una</option>
<option value="copy">copy — copia una colonna in un'altra</option>
<option value="clear">clear — svuota colonne</option>
<option value="trim">trim — rimuovi spazi</option>
<option value="replace">replace — sostituisci testo</option>
<option value="uppercase">uppercase — MAIUSCOLO</option>
<option value="lowercase">lowercase — minuscolo</option>
</select>
</div>
<div class="col-md-4">
<label for="active" class="form-label">Stato *</label>
<select class="form-select" id="active" name="active" required>
<option value="1">Attivo</option>
<option value="0">Disattivo</option>
</select>
</div>
<!-- Campi MERGE -->
<div class="col-md-8 op-field op-merge">
<label class="form-label">Colonne sorgente *</label>
<input type="text" class="form-control" name="source_columns" id="merge_source" placeholder="es. D, F, H">
<div class="small-muted mt-1">Lettere separate da virgola.</div>
</div>
<div class="col-md-4 op-field op-merge">
<label class="form-label">Colonna destinazione *</label>
<input type="text" class="form-control" name="target_column" id="merge_target" placeholder="es. J">
</div>
<div class="col-md-8 op-field op-merge">
<label class="form-label">Separatore</label>
<input type="text" class="form-control" name="separator" id="merge_sep" value=" " placeholder="spazio">
<div class="small-muted mt-1">Carattere inserito tra i valori uniti. Default: spazio.</div>
</div>
<div class="col-md-4 op-field op-merge d-flex align-items-end">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="skip_empty" id="merge_skip" checked>
<label class="form-check-label" for="merge_skip">Ignora valori vuoti</label>
</div>
</div>
<!-- Campi COPY -->
<div class="col-md-6 op-field op-copy">
<label class="form-label">Colonna sorgente *</label>
<input type="text" class="form-control" name="source_column" id="copy_source" placeholder="es. D">
</div>
<div class="col-md-6 op-field op-copy">
<label class="form-label">Colonna destinazione *</label>
<input type="text" class="form-control" name="target_column_copy" id="copy_target" placeholder="es. J">
</div>
<!-- Campi COLONNE (clear/trim/uppercase/lowercase/replace) -->
<div class="col-md-12 op-field op-columns">
<label class="form-label">Colonne *</label>
<input type="text" class="form-control" name="columns" id="columns_input" placeholder="es. D, F">
<div class="small-muted mt-1">Una o più lettere separate da virgola.</div>
</div>
<!-- Campo TRIM chars -->
<div class="col-md-12 op-field op-trim">
<label class="form-label">Caratteri da rimuovere (opzionale)</label>
<input type="text" class="form-control" name="chars" id="trim_chars" placeholder="lascia vuoto per spazi">
</div>
<!-- Campi REPLACE -->
<div class="col-md-6 op-field op-replace">
<label class="form-label">Testo da cercare</label>
<input type="text" class="form-control" name="search" id="replace_search">
</div>
<div class="col-md-6 op-field op-replace">
<label class="form-label">Testo sostitutivo</label>
<input type="text" class="form-control" name="replace" id="replace_replace">
</div>
<div class="col-md-12 op-field op-replace">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="regex" id="replace_regex">
<label class="form-check-label" for="replace_regex">
Usa espressione regolare (regex) — solo se sai cosa stai facendo
</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Annulla</button>
<button type="submit" class="btn btn-primary" id="saveStepButton">Salva step</button>
</div>
</form>
</div>
</div>
<!-- Form nascosti -->
<form method="POST" id="deleteStepForm" style="display:none;">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="idroutine" value="<?= (int)$idroutine; ?>">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="idstep" id="deleteIdStep" value="">
</form>
<form method="POST" id="moveStepForm" style="display:none;">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="idroutine" value="<?= (int)$idroutine; ?>">
<input type="hidden" name="action" value="move">
<input type="hidden" name="idstep" id="moveIdStep" value="">
<input type="hidden" name="direction" id="moveDirection" value="">
</form>
<?php include('jsinclude.php'); ?>
<script>
const stepsData = <?= json_encode($stepsForJs, JSON_UNESCAPED_UNICODE); ?>;
document.addEventListener('DOMContentLoaded', function() {
const stepModal = new bootstrap.Modal(document.getElementById('stepModal'));
const stepFormAction = document.getElementById('stepFormAction');
const idstep = document.getElementById('idstep');
const stepModalTitle = document.getElementById('stepModalTitle');
const saveStepButton = document.getElementById('saveStepButton');
const operation = document.getElementById('operation');
const active = document.getElementById('active');
// Mappa quali gruppi di campi mostrare per ogni operazione.
const fieldGroups = {
merge: ['op-merge'],
copy: ['op-copy'],
clear: ['op-columns'],
trim: ['op-columns', 'op-trim'],
replace: ['op-columns', 'op-replace'],
uppercase: ['op-columns'],
lowercase: ['op-columns']
};
function showFieldsForOperation(op) {
// Nasconde tutti i gruppi.
document.querySelectorAll('.op-field').forEach(function(el) {
el.style.display = 'none';
});
// Mostra i gruppi dell'operazione scelta.
(fieldGroups[op] || []).forEach(function(cls) {
document.querySelectorAll('.' + cls).forEach(function(el) {
el.style.display = '';
});
});
}
operation.addEventListener('change', function() {
showFieldsForOperation(operation.value);
});
function resetStepForm() {
document.getElementById('stepForm').reset();
// reset non ripristina i checkbox "checked" di default in modo affidabile: li forziamo
document.getElementById('merge_skip').checked = true;
document.getElementById('replace_regex').checked = false;
document.getElementById('merge_sep').value = ' ';
}
function openForCreate() {
stepFormAction.value = 'create';
idstep.value = '';
stepModalTitle.textContent = 'Aggiungi step';
saveStepButton.textContent = 'Aggiungi step';
resetStepForm();
operation.value = 'merge';
active.value = '1';
showFieldsForOperation('merge');
stepModal.show();
}
function openForEdit(id) {
const data = stepsData[id];
if (!data) {
return;
}
stepFormAction.value = 'update';
idstep.value = id;
stepModalTitle.textContent = 'Modifica step';
saveStepButton.textContent = 'Aggiorna step';
resetStepForm();
operation.value = data.operation;
active.value = String(data.active);
// Popola i campi in base all'operazione.
if (data.operation === 'merge') {
document.getElementById('merge_source').value = data.source_columns || '';
document.getElementById('merge_target').value = data.target_column || '';
document.getElementById('merge_sep').value = (data.separator !== undefined ? data.separator : ' ');
document.getElementById('merge_skip').checked = !!data.skip_empty;
} else if (data.operation === 'copy') {
document.getElementById('copy_source').value = data.source_column || '';
document.getElementById('copy_target').value = data.target_column || '';
} else if (['clear', 'trim', 'uppercase', 'lowercase'].includes(data.operation)) {
document.getElementById('columns_input').value = data.columns || '';
if (data.operation === 'trim') {
document.getElementById('trim_chars').value = data.chars || '';
}
} else if (data.operation === 'replace') {
document.getElementById('columns_input').value = data.columns || '';
document.getElementById('replace_search').value = data.search || '';
document.getElementById('replace_replace').value = data.replace || '';
document.getElementById('replace_regex').checked = !!data.regex;
}
showFieldsForOperation(data.operation);
stepModal.show();
}
/*
* NOTA sul campo destinazione di copy:
* l'input HTML si chiama target_column_copy per non collidere con merge,
* ma il backend si aspetta target_column. Rinominiamo al submit.
*/
document.getElementById('stepForm').addEventListener('submit', function(e) {
if (operation.value === 'copy') {
const copyTarget = document.getElementById('copy_target');
// Crea/aggiorna un campo hidden target_column col valore di copy.
let hidden = document.getElementById('copy_target_hidden');
if (!hidden) {
hidden = document.createElement('input');
hidden.type = 'hidden';
hidden.name = 'target_column';
hidden.id = 'copy_target_hidden';
this.appendChild(hidden);
}
hidden.value = copyTarget.value;
}
});
document.getElementById('btnAddStep').addEventListener('click', openForCreate);
document.querySelectorAll('.btnEditStep').forEach(function(btn) {
btn.addEventListener('click', function() {
openForEdit(btn.dataset.idstep);
});
});
document.querySelectorAll('.btnDeleteStep').forEach(function(btn) {
btn.addEventListener('click', function() {
Swal.fire({
title: 'Eliminare lo step?',
text: 'Operazione irreversibile.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Sì, elimina',
cancelButtonText: 'Annulla',
confirmButtonColor: '#d33'
}).then(function(result) {
if (result.isConfirmed) {
document.getElementById('deleteIdStep').value = btn.dataset.idstep;
document.getElementById('deleteStepForm').submit();
}
});
});
});
document.querySelectorAll('.btnMove').forEach(function(btn) {
btn.addEventListener('click', function() {
document.getElementById('moveIdStep').value = btn.dataset.idstep;
document.getElementById('moveDirection').value = btn.dataset.direction;
document.getElementById('moveStepForm').submit();
});
});
// --- Anteprima routine su XLS caricato (nessun salvataggio) ---
const previewFile = document.getElementById('previewFile');
const btnRunPreview = document.getElementById('btnRunPreview');
const previewStatus = document.getElementById('previewStatus');
const previewResultWrapper = document.getElementById('previewResultWrapper');
const previewTableBody = document.querySelector('#previewTable tbody');
const currentIdRoutine = "<?= (int)$idroutine; ?>";
btnRunPreview.addEventListener('click', function() {
if (!previewFile.files || previewFile.files.length === 0) {
Swal.fire({
icon: 'info',
title: 'Nessun file',
text: 'Seleziona un file XLS di prova.'
});
return;
}
const formData = new FormData();
formData.append('idroutine', currentIdRoutine);
formData.append('file', previewFile.files[0]);
previewStatus.textContent = 'Elaborazione in corso...';
previewResultWrapper.style.display = 'none';
previewTableBody.innerHTML = '';
btnRunPreview.disabled = true;
fetch('routine_preview.php', {
method: 'POST',
body: formData,
cache: 'no-store'
})
.then(function(res) {
return res.json();
})
.then(function(data) {
btnRunPreview.disabled = false;
if (data.error) {
previewStatus.textContent = '';
Swal.fire({
icon: 'warning',
title: 'Anteprima non riuscita',
text: data.error
});
return;
}
previewStatus.textContent = 'Riga elaborata: riga Excel ' + data.excelrow + '.';
const letters = data.letters || [];
const headers = data.headers || [];
const row = data.row || [];
for (let i = 0; i < row.length; i++) {
const tr = document.createElement('tr');
const tdCol = document.createElement('td');
tdCol.innerHTML = '<strong>' + (letters[i] || '') + '</strong>';
const tdHead = document.createElement('td');
tdHead.className = 'small-muted';
tdHead.textContent = headers[i] || '';
const tdVal = document.createElement('td');
tdVal.textContent = row[i] || '';
tr.appendChild(tdCol);
tr.appendChild(tdHead);
tr.appendChild(tdVal);
previewTableBody.appendChild(tr);
}
previewResultWrapper.style.display = '';
})
.catch(function(err) {
btnRunPreview.disabled = false;
previewStatus.textContent = '';
Swal.fire({
icon: 'error',
title: 'Errore',
text: 'Impossibile eseguire l\'anteprima.'
});
console.error(err);
});
});
<?php if ($flash): ?>
Swal.fire({
icon: '<?= htmlspecialchars($flash['type'], ENT_QUOTES, 'UTF-8'); ?>',
title: '<?= $flash['type'] === 'success' ? 'Fatto' : ($flash['type'] === 'warning' ? 'Attenzione' : 'Errore'); ?>',
text: '<?= htmlspecialchars($flash['message'], ENT_QUOTES, 'UTF-8'); ?>'
});
<?php endif; ?>
});
</script>
</body>
</html>