added photo school

This commit is contained in:
Claudio 2026-01-29 08:55:33 +01:00
parent aba16ce7f3
commit 2734679938
4 changed files with 369 additions and 2 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

View File

@ -55,6 +55,11 @@ if ($is_new) {
} else {
// se esiste, sincronizza school_id in sessione
$_SESSION['school_id'] = (int)$school['id'];
// Carica foto esistenti
$stmtPhotos = $pdo->prepare("SELECT id, filename FROM school_photos WHERE school_id = ? ORDER BY sort_order ASC, id ASC");
$stmtPhotos->execute([$school['id']]);
$existingPhotos = $stmtPhotos->fetchAll(PDO::FETCH_ASSOC);
}
@ -69,10 +74,69 @@ function generateSlug($string)
return $slug;
}
// Ridimensiona immagine con GD (max 1920 px lato lungo, qualità 82%)
function resizeAndSaveImage($tmp_name, $target_path, $maxDimension = 1920, $quality = 82)
{
list($width, $height, $type) = getimagesize($tmp_name);
if ($width <= $maxDimension && $height <= $maxDimension) {
return move_uploaded_file($tmp_name, $target_path);
}
$ratio = min($maxDimension / $width, $maxDimension / $height);
$newW = (int)($width * $ratio);
$newH = (int)($height * $ratio);
$src = null;
switch ($type) {
case IMAGETYPE_JPEG:
$src = imagecreatefromjpeg($tmp_name);
break;
case IMAGETYPE_PNG:
$src = imagecreatefrompng($tmp_name);
break;
case IMAGETYPE_GIF:
$src = imagecreatefromgif($tmp_name);
break;
default:
return false;
}
if (!$src) return false;
$dst = imagecreatetruecolor($newW, $newH);
// Trasparenza per PNG
if ($type == IMAGETYPE_PNG) {
imagealphablending($dst, false);
imagesavealpha($dst, true);
$transparent = imagecolorallocatealpha($dst, 255, 255, 255, 127);
imagefilledrectangle($dst, 0, 0, $newW, $newH, $transparent);
}
imagecopyresampled($dst, $src, 0, 0, 0, 0, $newW, $newH, $width, $height);
$success = false;
switch ($type) {
case IMAGETYPE_JPEG:
$success = imagejpeg($dst, $target_path, $quality);
break;
case IMAGETYPE_PNG:
$success = imagepng($dst, $target_path, (int)(9 - ($quality / 10)));
break;
case IMAGETYPE_GIF:
$success = imagegif($dst, $target_path);
break;
}
imagedestroy($src);
imagedestroy($dst);
return $success;
}
// POST - Salvataggio
$success_message = $error = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !isset($_POST['action'])) {
$name = trim($_POST['name'] ?? '');
$slug = generateSlug(trim($_POST['slug'] ?? $name));
$website = trim($_POST['website'] ?? '');
@ -88,7 +152,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$longitude = !empty($_POST['longitude']) ? floatval($_POST['longitude']) : null;
$owner_name = trim($_POST['owner_name'] ?? '');
$vat_number = trim($_POST['vat_number'] ?? '');
$status = in_array($_POST['status'] ?? 'active', ['active', 'inactive', 'suspended']) ? $_POST['status'] : 'active';
$rawStatus = $_POST['status'] ?? 'active';
$status = in_array($rawStatus, ['active', 'inactive', 'suspended'], true) ? $rawStatus : 'active';
// Validazioni
if (empty($name)) $error = "Il nome della scuola è obbligatorio.";
@ -199,6 +264,92 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
}
// AJAX per gestione foto
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
header('Content-Type: application/json; charset=utf-8');
if ($is_new || empty($school['id'])) {
echo json_encode(['success' => false, 'error' => 'Prima salva il profilo scuola']);
exit;
}
$school_id = (int)$school['id'];
if ($_POST['action'] === 'upload_photos') {
$currentCount = $pdo->query("SELECT COUNT(*) FROM school_photos WHERE school_id = $school_id")->fetchColumn();
$canAdd = 5 - $currentCount;
if ($canAdd <= 0) {
echo json_encode(['success' => false, 'error' => 'Limite di 5 foto raggiunto']);
exit;
}
$uploaded = [];
$errors = [];
foreach ($_FILES['photos']['tmp_name'] ?? [] as $i => $tmp) {
if ($canAdd <= count($uploaded)) break;
if (empty($tmp) || $_FILES['photos']['error'][$i] !== 0) continue;
$origName = $_FILES['photos']['name'][$i];
$ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION));
if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
$errors[] = $origName . ' - formato non supportato';
continue;
}
$safeName = preg_replace('/[^a-z0-9._-]/i', '', pathinfo($origName, PATHINFO_FILENAME));
$newFilename = "photoschool/{$school_id}_" . time() . "_{$safeName}.{$ext}";
if (resizeAndSaveImage($tmp, $newFilename)) {
$stmt = $pdo->prepare("INSERT INTO school_photos
(school_id, filename, original_name, mime_type, file_size, sort_order)
VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([
$school_id,
$newFilename,
$origName,
$_FILES['photos']['type'][$i] ?: 'image/jpeg',
(int)$_FILES['photos']['size'][$i],
$currentCount + count($uploaded)
]);
$uploaded[] = [
'id' => $pdo->lastInsertId(),
'filename' => $newFilename
];
} else {
$errors[] = $origName . ' - errore elaborazione';
}
}
echo json_encode([
'success' => !empty($uploaded),
'uploaded' => $uploaded,
'errors' => $errors,
'remaining' => 5 - ($currentCount + count($uploaded))
]);
exit;
}
if ($_POST['action'] === 'delete_photo' && !empty($_POST['photo_id'])) {
$photoId = (int)$_POST['photo_id'];
$stmt = $pdo->prepare("SELECT filename FROM school_photos WHERE id = ? AND school_id = ?");
$stmt->execute([$photoId, $school_id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row) {
if (file_exists($row['filename'])) @unlink($row['filename']);
$pdo->prepare("DELETE FROM school_photos WHERE id = ?")->execute([$photoId]);
}
echo json_encode(['success' => true]);
exit;
}
echo json_encode(['success' => false, 'error' => 'Azione non valida']);
exit;
}
?>
<!doctype html>
@ -277,6 +428,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
letter-spacing: 1px;
margin: 0 auto 1rem;
}
/* Thumbnails foto scuola */
.school-photo-thumb {
cursor: zoom-in;
transition: transform .15s ease, box-shadow .15s ease;
box-shadow: 0 2px 10px rgba(0, 0, 0, .08);
}
.school-photo-thumb:hover {
transform: scale(1.03);
box-shadow: 0 10px 25px rgba(0, 0, 0, .16);
}
</style>
</head>
@ -463,6 +626,49 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div>
</div>
</div>
<!-- Sezione foto scuole -->
<?php if (!$is_new): ?>
<div class="row mt-5">
<div class="col-12">
<hr class="my-4">
<h5>Foto della scuola (max 5)</h5>
<div id="photos-dropzone" style="border: 2px dashed #adb5bd; border-radius: 10px; padding: 30px; text-align: center; background: #f8f9fa; min-height: 160px; cursor: pointer;">
<p class="mb-2">Trascina le immagini qui oppure</p>
<button type="button" class="btn btn-outline-primary btn-sm" id="btn-select-photos">Seleziona file</button>
<input type="file" id="photos-input" name="photos[]" multiple accept="image/jpeg,image/png,image/gif" style="display:none;">
<div class="mt-3 text-muted">Foto rimanenti: <strong id="photos-remaining"><?php echo 5 - count($existingPhotos ?? []); ?></strong></div>
</div>
<div id="photos-preview" class="mt-4 d-flex flex-wrap gap-3">
<?php foreach ($existingPhotos ?? [] as $photo): ?>
<div class="position-relative" style="width:160px; height:160px;" data-photo-id="<?php echo $photo['id']; ?>">
<img class="school-photo-thumb"
src="<?php echo htmlspecialchars($photo['filename']); ?>"
alt="Foto scuola"
style="width:100%; height:100%; object-fit:cover; border-radius:8px; border:1px solid #dee2e6;">
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0 translate-middle rounded-circle p-0" style="width:28px; height:28px; font-size:18px; line-height:1;" data-delete-id="<?php echo $photo['id']; ?>">×</button>
</div>
<?php endforeach; ?>
</div>
<!-- Modal zoom foto -->
<div class="modal fade" id="photoZoomModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content bg-dark border-0">
<div class="modal-header border-0">
<button type="button" class="btn-close btn-close-white ms-auto" data-bs-dismiss="modal" aria-label="Chiudi"></button>
</div>
<div class="modal-body text-center p-0 pb-4">
<img id="photoZoomImg" src="" alt="Foto scuola" style="max-width:100%; max-height:80vh; object-fit:contain;">
</div>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
</form>
</div>
@ -645,6 +851,167 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
reader.readAsDataURL(file);
});
</script>
<script>
// Gestione foto
const dropzone = document.getElementById('photos-dropzone');
const fileInput = document.getElementById('photos-input');
const btnSelect = document.getElementById('btn-select-photos');
const previewContainer = document.getElementById('photos-preview');
const remainingSpan = document.getElementById('photos-remaining');
let remainingPhotos = parseInt(remainingSpan?.innerText || '0');
function updateRemainingCount() {
if (remainingSpan) remainingSpan.innerText = remainingPhotos;
if (remainingPhotos <= 0 && dropzone) {
dropzone.style.opacity = '0.5';
dropzone.style.pointerEvents = 'none';
}
}
function addPhotoPreview(url, photoId) {
const wrapper = document.createElement('div');
wrapper.className = 'position-relative';
wrapper.style.width = '160px';
wrapper.style.height = '160px';
wrapper.dataset.photoId = photoId;
wrapper.innerHTML = `
<img class="school-photo-thumb" src="${url}" alt="Foto" style="width:100%; height:100%; object-fit:cover; border-radius:8px; border:1px solid #dee2e6;">
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0 translate-middle rounded-circle p-0" style="width:28px;height:28px;font-size:18px;line-height:1;" data-delete-id="${photoId}">×</button>
`;
previewContainer.appendChild(wrapper);
}
function deletePhoto(btn) {
const wrapper = btn.closest('.position-relative');
const photoId = wrapper.dataset.photoId;
if (!photoId) {
wrapper.remove();
remainingPhotos++;
updateRemainingCount();
return;
}
if (!confirm('Vuoi eliminare questa foto?')) return;
fetch('', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'action=delete_photo&photo_id=' + photoId
})
.then(r => r.json())
.then(data => {
if (data.success) {
wrapper.remove();
remainingPhotos++;
updateRemainingCount();
} else {
alert('Errore durante l\'eliminazione');
}
})
.catch(() => alert('Errore di connessione'));
}
// Eventi
if (dropzone) {
dropzone.addEventListener('click', (e) => {
// se clicco sul bottone (o dentro al bottone), NON devo aprire due volte
if (e.target.closest('#btn-select-photos')) return;
fileInput.click();
});
btnSelect?.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation(); // IMPORTANTISSIMO: evita il click anche sulla dropzone
fileInput.click();
});
['dragover', 'dragenter'].forEach(ev => dropzone.addEventListener(ev, e => {
e.preventDefault();
dropzone.classList.add('bg-light', 'border-primary');
}));
['dragleave', 'drop'].forEach(ev => dropzone.addEventListener(ev, e => {
e.preventDefault();
dropzone.classList.remove('bg-light', 'border-primary');
}));
dropzone.addEventListener('drop', e => handleFiles(e.dataTransfer.files));
fileInput.addEventListener('change', e => {
handleFiles(e.target.files);
fileInput.value = '';
});
}
function handleFiles(files) {
if (remainingPhotos <= 0) return alert('Limite di 5 foto raggiunto');
const formData = new FormData();
formData.append('action', 'upload_photos');
let added = 0;
for (let file of files) {
if (added >= remainingPhotos) break;
if (!file.type.startsWith('image/')) continue;
formData.append('photos[]', file);
added++;
}
if (added === 0) return;
fetch('', {
method: 'POST',
body: formData
})
.then(r => r.json())
.then(data => {
if (data.success) {
data.uploaded.forEach(item => {
addPhotoPreview(item.filename, item.id);
});
remainingPhotos -= data.uploaded.length;
updateRemainingCount();
}
if (data.errors && data.errors.length) {
alert('Problemi con alcuni file:\n' + data.errors.join('\n'));
}
})
.catch(err => {
console.error(err);
alert('Errore durante il caricamento');
});
}
previewContainer?.addEventListener('click', e => {
// Se clicco la X: elimina
if (e.target.hasAttribute('data-delete-id')) {
e.preventDefault();
deletePhoto(e.target);
return;
}
// Se clicco una foto: zoom
if (e.target && e.target.tagName === 'IMG') {
const modalEl = document.getElementById('photoZoomModal');
const zoomImg = document.getElementById('photoZoomImg');
if (!modalEl || !zoomImg) return;
zoomImg.src = e.target.src;
// Bootstrap modal
const modal = new bootstrap.Modal(modalEl);
modal.show();
}
});
updateRemainingCount();
</script>
</body>
</html>