added photo school
This commit is contained in:
parent
aba16ce7f3
commit
2734679938
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
public/userarea/photoschool/1_1769673301_dsc_2857.jpg
Normal file
BIN
public/userarea/photoschool/1_1769673301_dsc_2857.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
BIN
public/userarea/photoschool/1_1769673304_giocattolo-975apd-1.jpg
Normal file
BIN
public/userarea/photoschool/1_1769673304_giocattolo-975apd-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 212 KiB |
@ -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>
|
||||
Loading…
x
Reference in New Issue
Block a user