push calendar

This commit is contained in:
2025-12-11 15:12:48 +01:00
parent 5bf666402c
commit a49b777d85
7 changed files with 1138 additions and 497 deletions
+20
View File
@@ -0,0 +1,20 @@
<?php
header('Content-Type: application/json');
require __DIR__ . '/../includes/db.php';
try {
$stmt = $pdo->query("SELECT id, event_date AS date, event_type AS type, person_id, duration FROM pf_events ORDER BY event_date");
$events = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'status' => 'success',
'events' => $events,
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'status' => 'error',
'message' => 'Erreur lors de la récupération des événements : ' . $e->getMessage(),
]);
}
+36
View File
@@ -0,0 +1,36 @@
<?php
header('Content-Type: application/json');
require __DIR__ . '/../includes/db.php';
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['action']) || !isset($input['event_id'])) {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Action ou ID manquant.']);
exit;
}
$action = $input['action'];
$eventId = $input['event_id'];
try {
if ($action === 'delete') {
$stmt = $pdo->prepare("DELETE FROM pf_events WHERE id = ?");
$stmt->execute([$eventId]);
echo json_encode(['status' => 'success', 'message' => 'Événement supprimé.']);
} elseif ($action === 'update' && isset($input['new_type'])) {
$stmt = $pdo->prepare("UPDATE pf_events SET event_type = ? WHERE id = ?");
$stmt->execute([$input['new_type'], $eventId]);
echo json_encode(['status' => 'success', 'message' => 'Événement mis à jour.']);
} else {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Action non valide ou données manquantes.']);
}
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
?>
+62
View File
@@ -0,0 +1,62 @@
<?php
header('Content-Type: application/json');
require __DIR__ . '/../includes/db.php';
$eventsToSave = json_decode(file_get_contents('php://input'), true);
if (empty($eventsToSave) || !is_array($eventsToSave)) {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Aucune donnée d\'événement reçue.']);
exit;
}
$inserted = [];
try {
$pdo->beginTransaction();
$sql = "INSERT INTO pf_events (event_date, event_type, person_id, duration)
VALUES (:event_date, :event_type, :person_id, :duration)";
$stmt = $pdo->prepare($sql);
foreach ($eventsToSave as $event) {
$person_id = null;
// Si un nom de personne est fourni
if (!empty($event['person'])) {
$personStmt = $pdo->prepare("SELECT id FROM pf_people WHERE name = ?");
$personStmt->execute([$event['person']]);
$personRow = $personStmt->fetch();
if ($personRow) {
$person_id = $personRow['id'];
}
}
$stmt->execute([
':event_date' => $event['date'],
':event_type' => $event['type'],
':person_id' => $person_id,
':duration' => $event['duration'] ?? 1.0,
]);
$inserted[] = [
'id' => $pdo->lastInsertId(),
'date' => $event['date'],
'type' => $event['type'],
'duration' => $event['duration'] ?? 1.0,
'person_id' => $person_id,
];
}
$pdo->commit();
echo json_encode([
'status' => 'success',
'inserted' => $inserted,
]);
} catch (Exception $e) {
$pdo->rollBack();
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => 'Erreur lors de la sauvegarde : ' . $e->getMessage()]);
}
+128 -5
View File
@@ -83,6 +83,31 @@ body {
} }
/* === CARDS & FLEX === */ /* === CARDS & FLEX === */
.pf-card.pf-card--wide {
min-width: 400px; /* tu peux monter à 500 ou 600 selon ton goût */
flex: 1 1 400px; /* laisse la carte prendre plus de place dans la ligne */
}
/* Le tableau des vacances occupe toute la largeur disponible */
#schoolHolidaysTable {
width: auto;
table-layout: auto;
border-collapse: collapse;
}
.pf-card.pf-card--wide .pf-table-wrapper {
width: 100%;
overflow-x: auto;
}
/* En-têtes et cellules sans retour à la ligne */
#schoolHolidaysTable th,
#schoolHolidaysTable td {
white-space: nowrap; /* pas de retour à la ligne dans les cellules */
overflow: visible; /* pas de tronquage */
padding: 4px 8px;
text-align: center;
}
.pf-flex { .pf-flex {
display: flex; display: flex;
} }
@@ -121,6 +146,59 @@ label {
} }
/* === TABLES === */ /* === TABLES === */
/* Appliquer un layout fixe pour harmoniser la largeur des colonnes */
#planningTable {
table-layout: fixed;
width: 100%; /* le tableau prend toute la largeur disponible */
}
/* Colonnes : texte centré et césures possibles */
#planningTable th,
#planningTable td {
text-align: center;
white-space: nowrap; /* évite les retours à la ligne sauvages */
overflow: hidden;
text-overflow: ellipsis; /* coupe avec "..." si le texte déborde */
}
/* Colonnes "Mois" et "Semaine" un peu plus étroites */
#planningTable th:nth-child(1),
#planningTable td:nth-child(1) {
width: 2%; /* Mois */
}
#planningTable th:nth-child(2),
#planningTable td:nth-child(2) {
width: 2%; /* Semaine */
}
/* Colonnes jours (Lundi à Vendredi) réparties sur une largeur raisonnable */
#planningTable th:nth-child(3),
#planningTable td:nth-child(3),
#planningTable th:nth-child(4),
#planningTable td:nth-child(4),
#planningTable th:nth-child(5),
#planningTable td:nth-child(5),
#planningTable th:nth-child(6),
#planningTable td:nth-child(6),
#planningTable th:nth-child(7),
#planningTable td:nth-child(7) {
width: 2%; /* ajustable selon ton rendu */
}
/* Colonnes de totaux (Off / Extra / Centre / Avis) un peu plus compactes */
#planningTable th:nth-child(8),
#planningTable td:nth-child(8),
#planningTable th:nth-child(9),
#planningTable td:nth-child(9),
#planningTable th:nth-child(10),
#planningTable td:nth-child(10),
#planningTable th:nth-child(11),
#planningTable td:nth-child(11) {
width: 4%;
}
.pf-table-wrapper { .pf-table-wrapper {
max-height: 600px; max-height: 600px;
overflow: auto; overflow: auto;
@@ -166,11 +244,6 @@ input[type="number"] {
background: #ffe0e0; background: #ffe0e0;
} }
/* Jour de vacances scolaires dans le calendrier */
.fc-day--school-holiday {
background-color: #fff5cc; /* choisis le jaune qui te convient */
}
.fc-row--bank-holiday { .fc-row--bank-holiday {
background: #d9f7be; background: #d9f7be;
} }
@@ -242,3 +315,53 @@ input[type="number"] {
background-color: #f0f4f8; background-color: #f0f4f8;
border-color: #b0c4de; border-color: #b0c4de;
} }
/* --- Couleurs de base pour la légende et les jours --- */
.fc-day--school-holiday {
background-color: #e5d9f2;
}
.fc-day--public-holiday {
background-color: #e0e0e0;
}
.fc-day--off-carole {
background-color: #ffe9a7;
}
.fc-day--extra-off-carole {
background-color: #ffd59b;
}
.fc-day--has-guard {
box-sizing: border-box;
}
/* Bordure verte pour Centre */
.fc-day--has-guard.fc-day--centre {
border: 2px solid #4caf50;
}
/* Bordure bleue pour Avis */
.fc-day--has-guard.fc-day--avis {
border: 2px solid #2196f3;
}
/* --- Combinaisons avec dégradés --- */
/* Combinaison Vacances + Off Carole */
.fc-day--school-holiday.fc-day--off-carole {
background-image: linear-gradient(45deg, #e5d9f2 49%, #ffe9a7 51%);
}
/* Combinaison Vacances + Extra Off Carole */
.fc-day--school-holiday.fc-day--extra-off-carole {
background-image: linear-gradient(45deg, #e5d9f2 49%, #ffd59b 51%);
}
/* Eléments pour la légende */
.pf-legend-item {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.pf-legend-color {
width: 20px;
height: 20px;
border: 1px solid #ccc;
margin-right: 10px;
flex-shrink: 0;
}
File diff suppressed because it is too large Load Diff
+79 -92
View File
@@ -1,68 +1,98 @@
<?php <?php
// debug temporaire si besoin // Active l'affichage des erreurs pour le développement
// ini_set('display_errors', 1); ini_set('display_errors', 1);
// ini_set('display_startup_errors', 1); error_reporting(E_ALL);
// error_reporting(E_ALL);
// On se connecte à la base de données
require __DIR__ . '/includes/db.php';
// --- On récupère TOUS les événements sauvegardés en base ---
$stmt_events = $pdo->query("SELECT * FROM pf_events");
$dbEvents = $stmt_events->fetchAll();
// --- Configuration de la page ---
$pageTitle = "PachaFamily - Family Calendar"; $pageTitle = "PachaFamily - Family Calendar";
$activePage = "family-calendar"; $activePage = "family-calendar";
require __DIR__ . '/header.php'; require __DIR__ . '/header.php';
?> ?>
<!-- ===================================================================== -->
<!-- INJECTION DES DONNÉES DU SERVEUR VERS JAVASCRIPT -->
<!-- Cette variable `serverData` sera lue par le script JS au démarrage. -->
<!-- ===================================================================== -->
<script>
const serverData = <?php echo json_encode($dbEvents, JSON_NUMERIC_CHECK); ?>;
</script>
<h1>Family Calendar</h1> <h1>Family Calendar</h1>
<!-- ===================================================================== -->
<!-- PANNEAU DE CONTRÔLE : Légende, Récapitulatif et Vacances -->
<!-- ===================================================================== -->
<section class="pf-section pf-section--panel"> <section class="pf-section pf-section--panel">
<div class="pf-flex pf-flex--wrap pf-gap-lg"> <div class="pf-flex pf-flex--wrap pf-gap-lg">
<!-- LÉGENDE -->
<div class="pf-card pf-card--small"> <div class="pf-card pf-card--small">
<h2 class="pf-card-title">Soldes initiaux</h2> <h2 class="pf-card-title">Légende</h2>
<div class="pf-card-body"> <div class="pf-card-body">
<p><strong>Alex</strong></p> <div class="pf-legend-item">
<label>CP : <div class="pf-legend-color fc-day--school-holiday"></div>
<input id="alexCpInit" type="number" step="0.25" value="24.5"> <span>Vacances scolaires</span>
</label><br> </div>
<label>RTT : <div class="pf-legend-item">
<input id="alexRttInit" type="number" step="0.25" value="1.5"> <div class="pf-legend-color fc-day--public-holiday"></div>
</label><br> <span>Jour férié</span>
<label>JA : </div>
<input id="alexJaInit" type="number" step="0.25" value="1"> <div class="pf-legend-item">
</label> <div class="pf-legend-color fc-day--off-carole"></div>
<span>Off Carole</span>
<p><strong>Laia</strong></p> </div>
<label>CP : <div class="pf-legend-item">
<input id="laiaCpInit" type="number" step="0.25" value="19"> <div class="pf-legend-color fc-day--extra-off-carole"></div>
</label><br> <span>Extra Off Carole</span>
<label>RTT : </div>
<input id="laiaRttInit" type="number" step="0.25" value="4">
</label><br>
<label>JA :
<input id="laiaJaInit" type="number" step="0.25" value="4">
</label>
</div> </div>
</div> </div>
<!-- RÉCAPITULATIF ANNUEL -->
<div class="pf-card pf-card--small"> <div class="pf-card pf-card--small">
<h2 class="pf-card-title">Filtres</h2> <h2 class="pf-card-title">Récapitulatif annuel</h2>
<div class="pf-card-body" id="globalSummary">
<!-- Rempli par le JavaScript -->
</div>
</div>
<!-- VACANCES SCOLAIRES -->
<div class="pf-card pf-card--small pf-card--wide">
<h2 class="pf-card-title">Vacances scolaires - Zone C (2025-2026)</h2>
<div class="pf-card-body"> <div class="pf-card-body">
<label> <div class="pf-table-wrapper">
<input id="showOnlyCaroleOff" type="checkbox"> <table id="schoolHolidaysTable" class="fc-holidays-table">
Voir uniquement les semaines ou Carole est en conges <thead>
</label><br> <tr>
<label> <th>Période</th>
<input id="showOnlySchoolHoliday" type="checkbox"> <th>Du</th>
Voir uniquement les vacances scolaires <th>Au</th>
</label> <th>Zones</th>
</div> </tr>
</div> </thead>
<tbody>
<div class="pf-card pf-card--small">
<h2 class="pf-card-title">Resume</h2>
<div class="pf-card-body" id="summaryText">
<!-- Rempli par JS --> <!-- Rempli par JS -->
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<!-- ===================================================================== -->
<!-- PLANNING PRINCIPAL -->
<!-- ===================================================================== -->
<section class="pf-section"> <section class="pf-section">
<h2>Planning par semaine</h2> <h2>Planning par semaine</h2>
<div class="pf-table-wrapper"> <div class="pf-table-wrapper">
@@ -80,69 +110,26 @@ require __DIR__ . '/header.php';
<th># Extra off Carole</th> <th># Extra off Carole</th>
<th>#Centre</th> <th>#Centre</th>
<th>#Avis</th> <th>#Avis</th>
<th colspan="2">Alex</th>
<th colspan="2">Laia</th>
</tr>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th>jours</th>
<th>jours</th>
<th>jours</th>
<th>jours</th>
<th>Total</th>
<th>Détail</th>
<th>Total</th>
<th>Détail</th>
</tr> </tr>
</thead> </thead>
<tbody id="planningBody"> <tbody id="planningBody">
<!-- Rempli par family-calendar.js --> <!-- Le contenu de ce tableau est entièrement généré par family-calendar.js -->
</tbody> </tbody>
</table> </table>
<div id="selectionMenu" class="fc-selection-menu">
<div class="fc-menu-section">
<strong>Congé Carole</strong>
<button data-type="OFF_CAROLE"># Off Carole</button>
<button data-type="EXTRA_OFF_CAROLE"># Extra off Carole</button>
</div>
<div class="fc-menu-section">
<strong>Mode de Garde</strong>
<button data-type="CENTRE"># Centre</button>
<button data-type="AVIS"># Avis</button>
</div>
</div>
</div>
</section>
<section class="pf-section"> <!-- Le menu contextuel est caché par défaut et son contenu est généré par JS -->
<h2>Vacances scolaires - Zone C (2025-2026)</h2> <div id="selectionMenu" class="fc-selection-menu"></div>
<p>Source : data.education.gouv.fr - calendrier officiel.</p>
<div class="pf-table-wrapper">
<table id="schoolHolidaysTable" class="fc-holidays-table">
<thead>
<tr>
<th>Période</th>
<th>Du</th>
<th>Au</th>
<th>Zones</th>
</tr>
</thead>
<tbody>
<!-- Rempli par JS -->
</tbody>
</table>
</div> </div>
</section> </section>
<!-- ===================================================================== -->
<!-- CHARGEMENT DU SCRIPT JAVASCRIPT PRINCIPAL -->
<!-- ===================================================================== -->
<script src="/assets/js/family-calendar.js"></script> <script src="/assets/js/family-calendar.js"></script>
<?php <?php
// Inclusion du pied de page
require __DIR__ . '/footer.php'; require __DIR__ . '/footer.php';
?>
+54
View File
@@ -0,0 +1,54 @@
<?php
// Activer erreurs
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Détection de l'environnement
$isLocal = (strpos($_SERVER['HTTP_HOST'] ?? 'localhost', 'localhost') !== false ||
strpos($_SERVER['HTTP_HOST'] ?? '', '127.0.0.1') !== false ||
strpos($_SERVER['HTTP_HOST'] ?? '', '::1') !== false);
if ($isLocal) {
// Configuration XAMPP local
$host = 'localhost';
$db = 'percolo314'; // Même nom que sur OVH pour simplifier
$user = 'root';
$pass = ''; // Généralement vide sur XAMPP
} else {
// Configuration serveur OVH
$host = 'percolo314.mysql.db';
$db = 'percolo314';
$user = 'percolo314';
$pass = 'Wxcvbn99';
}
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_general_ci",
PDO::ATTR_TIMEOUT => 30,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
// Forcer la collation
$pdo->exec("SET collation_connection = utf8mb4_general_ci");
$pdo->exec("SET collation_database = utf8mb4_general_ci");
$pdo->exec("SET collation_server = utf8mb4_general_ci");
// Debug optionnel (à commenter en production)
// $env = $isLocal ? 'LOCAL' : 'SERVEUR';
// echo "<!-- Connecté en $env sur $host/$db -->";
} catch (\PDOException $e) {
$environment = $isLocal ? 'local (XAMPP)' : 'serveur (OVH)';
die("Erreur de connexion ($environment) : " . $e->getMessage());
}
?>