layout
This commit is contained in:
@@ -1,36 +0,0 @@
|
|||||||
<?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()]);
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
+23
-17
@@ -3,17 +3,22 @@
|
|||||||
ini_set('display_errors', 1);
|
ini_set('display_errors', 1);
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
// On se connecte à la base de données
|
require __DIR__ . '/includes/auth.php';
|
||||||
|
require_login('/family-calendar.php');
|
||||||
|
|
||||||
require __DIR__ . '/includes/db.php';
|
require __DIR__ . '/includes/db.php';
|
||||||
|
|
||||||
// --- On récupère TOUS les événements sauvegardés en base ---
|
// --- On récupère TOUS les événements sauvegardés en base ---
|
||||||
$stmt_events = $pdo->query("SELECT * FROM pf_events");
|
$stmt_events = $pdo->query("SELECT * FROM pf_events");
|
||||||
$dbEvents = $stmt_events->fetchAll();
|
$dbEvents = $stmt_events->fetchAll();
|
||||||
|
|
||||||
// --- Configuration de la page ---
|
|
||||||
$pageTitle = "PachaFamily - Family Calendar";
|
$pageTitle = "PachaFamily - Family Calendar";
|
||||||
$activePage = "family-calendar";
|
$activePage = "family-calendar";
|
||||||
|
$bodyClass = "pf-family-calendar";
|
||||||
|
$pageCss = "/modules/family-calendar/family-calendar.css";
|
||||||
|
|
||||||
require __DIR__ . '/header.php';
|
require __DIR__ . '/header.php';
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
@@ -26,7 +31,6 @@ require __DIR__ . '/header.php';
|
|||||||
|
|
||||||
<h1>Family Calendar</h1>
|
<h1>Family Calendar</h1>
|
||||||
|
|
||||||
|
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
<!-- PANNEAU DE CONTRÔLE : Légende, Récapitulatif et Vacances -->
|
<!-- PANNEAU DE CONTRÔLE : Légende, Récapitulatif et Vacances -->
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
@@ -38,23 +42,32 @@ require __DIR__ . '/header.php';
|
|||||||
<h2 class="pf-card-title">Légende</h2>
|
<h2 class="pf-card-title">Légende</h2>
|
||||||
<div class="pf-card-body">
|
<div class="pf-card-body">
|
||||||
<div class="pf-legend-item">
|
<div class="pf-legend-item">
|
||||||
<div class="pf-legend-color fc-day--school-holiday"></div>
|
<div class="pf-legend-color fc-legend-school-holiday"></div>
|
||||||
<span>Vacances scolaires</span>
|
<span>Vacances scolaires</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-legend-item">
|
<div class="pf-legend-item">
|
||||||
<div class="pf-legend-color fc-day--public-holiday"></div>
|
<div class="pf-legend-color fc-legend-public-holiday"></div>
|
||||||
<span>Jour férié</span>
|
<span>Jour férié</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-legend-item">
|
<div class="pf-legend-item">
|
||||||
<div class="pf-legend-color fc-day--off-carole"></div>
|
<div class="pf-legend-color fc-legend-off-carole"></div>
|
||||||
<span>Off Carole</span>
|
<span>Off Carole</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-legend-item">
|
<div class="pf-legend-item">
|
||||||
<div class="pf-legend-color fc-day--extra-off-carole"></div>
|
<div class="pf-legend-color fc-legend-extra-off-carole"></div>
|
||||||
<span>Extra Off Carole</span>
|
<span>Extra Off Carole</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pf-legend-item">
|
||||||
|
<div class="pf-legend-color fc-legend-centre"></div>
|
||||||
|
<span>Centre</span>
|
||||||
|
</div>
|
||||||
|
<div class="pf-legend-item">
|
||||||
|
<div class="pf-legend-color fc-legend-avis"></div>
|
||||||
|
<span>Avis</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- RÉCAPITULATIF ANNUEL -->
|
<!-- RÉCAPITULATIF ANNUEL -->
|
||||||
<div class="pf-card pf-card--small">
|
<div class="pf-card pf-card--small">
|
||||||
@@ -86,10 +99,9 @@ require __DIR__ . '/header.php';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
<!-- CALENDRIER MENSUEL -->
|
<!-- CALENDRIER MENSUEL -->
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
@@ -110,15 +122,11 @@ require __DIR__ . '/header.php';
|
|||||||
</div>
|
</div>
|
||||||
<div class="fc-calendar-and-summary">
|
<div class="fc-calendar-and-summary">
|
||||||
<div id="fc-month-calendar" class="fc-month-calendar">
|
<div id="fc-month-calendar" class="fc-month-calendar">
|
||||||
<!-- Le calendrier mensuel sera généré par JavaScript -->
|
|
||||||
</div>
|
|
||||||
<div id="fc-month-summary" class="fc-month-summary">
|
|
||||||
<!-- Le tableau récapitulatif sera généré par JavaScript -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Le menu contextuel pour le calendrier mensuel -->
|
<!-- Le menu contextuel pour le calendrier mensuel -->
|
||||||
<div id="fc-month-selectionMenu" class="fc-selection-menu"></div>
|
<div id="fc-month-selectionMenu" class="fc-selection-menu"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
@@ -153,12 +161,10 @@ require __DIR__ . '/header.php';
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
<!-- CHARGEMENT DU SCRIPT JAVASCRIPT PRINCIPAL -->
|
<!-- CHARGEMENT DU SCRIPT JAVASCRIPT PRINCIPAL -->
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
<script src="/assets/js/family-calendar.js"></script>
|
<script src="/modules/family-calendar/family-calendar.js"></script>
|
||||||
|
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Inclusion du pied de page
|
// Inclusion du pied de page
|
||||||
|
|||||||
@@ -5,5 +5,7 @@
|
|||||||
<small>© <?php echo date('Y'); ?> PachaFamily</small>
|
<small>© <?php echo date('Y'); ?> PachaFamily</small>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
</div> <!-- FERMETURE pf-page -->
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+209
@@ -0,0 +1,209 @@
|
|||||||
|
/* === RESET SIMPLE === */
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === BASE HTML / BODY === */
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||||
|
sans-serif;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === LAYOUT GLOBAL (structure page + footer collé en bas) === */
|
||||||
|
|
||||||
|
.pf-page {
|
||||||
|
min-height: 100vh; /* au minimum la hauteur de la fenêtre */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-main {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === LAYOUT CONTAINER / HEADER / NAV / FOOTER === */
|
||||||
|
|
||||||
|
.pf-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-header {
|
||||||
|
background: #243b53;
|
||||||
|
color: #fff;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-header-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-logo {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-nav-link {
|
||||||
|
color: #d9e2ec;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-nav-link:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-nav-link--active {
|
||||||
|
background: #102a43;
|
||||||
|
color: #f0f4f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-main {
|
||||||
|
padding: 24px 10px 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-section {
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-section--panel {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px 16px 20px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-footer {
|
||||||
|
border-top: 1px solid #d9e2ec;
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #627d98;
|
||||||
|
background: #f0f4f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sur certaines pages (comme la home), on pourra override le header via une classe de body */
|
||||||
|
|
||||||
|
/* === FLEX & CARDS GÉNÉRIQUES === */
|
||||||
|
|
||||||
|
.pf-flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-flex--wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-gap-lg {
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-card {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #d9e2ec;
|
||||||
|
padding: 12px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-card--small {
|
||||||
|
min-width: 240px;
|
||||||
|
max-width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Large card (utilisée pour le tableau des vacances) */
|
||||||
|
.pf-card.pf-card--wide {
|
||||||
|
min-width: 400px;
|
||||||
|
flex: 1 1 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-card-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-card-body {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === TABLES GÉNÉRIQUES === */
|
||||||
|
|
||||||
|
.pf-table-wrapper {
|
||||||
|
max-height: 600px;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #d9e2ec;
|
||||||
|
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-table th,
|
||||||
|
.pf-table td {
|
||||||
|
border: 1px solid #d9e2ec;
|
||||||
|
padding: 4px 6px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-table thead th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: #f0f4f8;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-table--compact th,
|
||||||
|
.pf-table--compact td {
|
||||||
|
padding: 3px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === FORMULAIRES / TEXTE GÉNÉRIQUE === */
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
width: 3.1rem;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Récapitulatif global (utilisé dans le calendrier mais style simple) */
|
||||||
|
#globalSummary p {
|
||||||
|
margin: 4px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#globalSummary strong {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #243b53;
|
||||||
|
}
|
||||||
+31
-6
@@ -1,4 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
// S'assurer que la session est démarrée si on veut afficher l'utilisateur
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
if (!isset($pageTitle)) {
|
if (!isset($pageTitle)) {
|
||||||
$pageTitle = "PachaFamily";
|
$pageTitle = "PachaFamily";
|
||||||
}
|
}
|
||||||
@@ -7,21 +12,41 @@ if (!isset($activePage)) {
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr" class="<?= htmlspecialchars($bodyClass ?? '') ?>">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title><?php echo htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8'); ?></title>
|
<title><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
<link rel="stylesheet" href="/assets/css/style.css">
|
|
||||||
|
<!-- CSS globale -->
|
||||||
|
<link rel="stylesheet" href="/global.css">
|
||||||
|
|
||||||
|
<!-- CSS spécifique à la page (optionnel) -->
|
||||||
|
<?php if (!empty($pageCss)): ?>
|
||||||
|
<link rel="stylesheet" href="<?= htmlspecialchars($pageCss, ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
<?php endif; ?>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="<?= htmlspecialchars($bodyClass ?? '') ?>">
|
||||||
|
<div class="pf-page"><!-- WRAPPER OUVERT ICI -->
|
||||||
|
|
||||||
<header class="pf-header">
|
<header class="pf-header">
|
||||||
<div class="pf-container pf-header-content">
|
<div class="pf-container pf-header-content">
|
||||||
<div class="pf-logo">PachaFamily</div>
|
<div class="pf-logo">PachaFamily</div>
|
||||||
|
|
||||||
<nav class="pf-nav">
|
<nav class="pf-nav">
|
||||||
<a href="/index.php"
|
<a href="/index.php"
|
||||||
class="pf-nav-link <?php echo $activePage === 'home' ? 'pf-nav-link--active' : ''; ?>">Accueil</a>
|
class="pf-nav-link <?= $activePage === 'home' ? 'pf-nav-link--active' : ''; ?>">Accueil</a>
|
||||||
|
|
||||||
<a href="/family-calendar.php"
|
<a href="/family-calendar.php"
|
||||||
class="pf-nav-link <?php echo $activePage === 'family-calendar' ? 'pf-nav-link--active' : ''; ?>">Family calendar</a>
|
class="pf-nav-link <?= $activePage === 'family-calendar' ? 'pf-nav-link--active' : ''; ?>">Family calendar</a>
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION['user'])): ?>
|
||||||
|
<span class="pf-nav-user">
|
||||||
|
Bonjour <?= htmlspecialchars($_SESSION['user']['display_name'] ?? $_SESSION['user']['username'], ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</span>
|
||||||
|
<a href="/logout.php" class="pf-nav-link pf-nav-link--secondary">Se déconnecter</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="/login.php" class="pf-nav-link pf-nav-link--secondary">Se connecter</a>
|
||||||
|
<?php endif; ?>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
// includes/auth.php
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function require_login(string $redirectTarget = null): void
|
||||||
|
{
|
||||||
|
if (!isset($_SESSION['user'])) {
|
||||||
|
if ($redirectTarget === null) {
|
||||||
|
$redirectTarget = $_SERVER['REQUEST_URI'] ?? '/index.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
$redirect = urlencode($redirectTarget);
|
||||||
|
header("Location: /login.php?redirect=$redirect");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
<?php
|
|
||||||
$pageTitle = "PachaFamily - Accueil";
|
|
||||||
$activePage = "home";
|
|
||||||
require __DIR__ . '/header.php';
|
|
||||||
?>
|
|
||||||
|
|
||||||
<h1>Bienvenue sur PachaFamily</h1>
|
|
||||||
<p>Centre de controle de l'organisation familiale.</p>
|
|
||||||
|
|
||||||
<section class="pf-section">
|
|
||||||
<h2>Modules</h2>
|
|
||||||
<ul>
|
|
||||||
<li><a href="/family-calendar.php">Family calendar (conges, vacances, garde)</a></li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<?php
|
|
||||||
require __DIR__ . '/footer.php';
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
// Protection de la page : nécessite d'être connecté
|
||||||
|
require __DIR__ . '/includes/auth.php';
|
||||||
|
require_login('/index.php');
|
||||||
|
|
||||||
|
// Configuration de la page
|
||||||
|
$pageTitle = "PachaFamily - Accueil";
|
||||||
|
$activePage = "home";
|
||||||
|
$bodyClass = "pf-home";
|
||||||
|
$pageCss = "/modules/home/home.css";
|
||||||
|
|
||||||
|
require __DIR__ . '/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="pf-container">
|
||||||
|
<h1>Bienvenue sur PachaFamily</h1>
|
||||||
|
<p>Centre de contrôle de l'organisation familiale.</p>
|
||||||
|
|
||||||
|
<?php if (isset($_SESSION['user'])): ?>
|
||||||
|
<div class="pf-user-info">
|
||||||
|
Connecté en tant que
|
||||||
|
<strong><?= htmlspecialchars($_SESSION['user']['display_name'] ?? $_SESSION['user']['username']) ?></strong>
|
||||||
|
– <a href="/logout.php">Se déconnecter</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<section class="pf-section">
|
||||||
|
<h2>Modules</h2>
|
||||||
|
|
||||||
|
<div class="pf-flex pf-flex--wrap pf-gap-lg pf-modules-cards">
|
||||||
|
<!-- Card : Family calendar -->
|
||||||
|
<a href="/family-calendar.php" class="pf-card pf-card--module">
|
||||||
|
<div class="pf-card-icon">📅</div>
|
||||||
|
<h3 class="pf-card-title">Family calendar</h3>
|
||||||
|
<div class="pf-card-body">
|
||||||
|
Gérez les congés, vacances et la garde sur un calendrier partagé
|
||||||
|
pour toute la famille.
|
||||||
|
</div>
|
||||||
|
<span class="pf-card-cta">Accéder au module</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Exemple de futur module -->
|
||||||
|
<div class="pf-card pf-card--module pf-card--disabled">
|
||||||
|
<div class="pf-card-icon">💰</div>
|
||||||
|
<h3 class="pf-card-title">Budget familial</h3>
|
||||||
|
<div class="pf-card-body">
|
||||||
|
Bientôt disponible : suivi des dépenses, catégories et budget mensuel.
|
||||||
|
</div>
|
||||||
|
<span class="pf-card-cta">À venir</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Autre exemple -->
|
||||||
|
<div class="pf-card pf-card--module pf-card--disabled">
|
||||||
|
<div class="pf-card-icon">📦</div>
|
||||||
|
<h3 class="pf-card-title">Liste de courses</h3>
|
||||||
|
<div class="pf-card-body">
|
||||||
|
Centralisez les listes de courses et les partages entre membres.
|
||||||
|
</div>
|
||||||
|
<span class="pf-card-cta">À venir</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require __DIR__ . '/footer.php';
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
require __DIR__ . '/includes/db.php'; // Doit définir $pdo (PDO connecté à ta DB)
|
||||||
|
|
||||||
|
$pageTitle = "PachaFamily - Login";
|
||||||
|
$activePage = "login";
|
||||||
|
|
||||||
|
// Si l'utilisateur est déjà connecté, on le renvoie à l'accueil
|
||||||
|
if (isset($_SESSION['user'])) {
|
||||||
|
header('Location: /index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$username = trim($_POST['username'] ?? '');
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
|
||||||
|
if ($username === '' || $password === '') {
|
||||||
|
$error = "Merci de renseigner identifiant et mot de passe.";
|
||||||
|
} else {
|
||||||
|
// On récupère l'utilisateur dans la table pf_users
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, username, password_hash, display_name
|
||||||
|
FROM pf_users
|
||||||
|
WHERE username = :username
|
||||||
|
");
|
||||||
|
$stmt->execute([':username' => $username]);
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Vérification du mot de passe
|
||||||
|
if ($user && password_verify($password, $user['password_hash'])) {
|
||||||
|
// Connexion OK : on stocke les infos utiles en session
|
||||||
|
$_SESSION['user'] = [
|
||||||
|
'id' => (int)$user['id'],
|
||||||
|
'username' => $user['username'],
|
||||||
|
'display_name' => $user['display_name'],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Redirection : soit la page demandée, soit l'accueil
|
||||||
|
$redirectTo = $_GET['redirect'] ?? '/index.php';
|
||||||
|
header('Location: ' . $redirectTo);
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = "Identifiants incorrects.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require __DIR__ . '/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<h1>Connexion</h1>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="pf-error"><?= htmlspecialchars($error) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="post" action="/login.php<?= isset($_GET['redirect']) ? '?redirect=' . urlencode($_GET['redirect']) : '' ?>">
|
||||||
|
<div class="pf-form-group">
|
||||||
|
<label for="username">Identifiant</label>
|
||||||
|
<input type="text" id="username" name="username" required autofocus>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pf-form-group">
|
||||||
|
<label for="password">Mot de passe</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit">Se connecter</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require __DIR__ . '/footer.php';
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// On vide toutes les variables de session
|
||||||
|
$_SESSION = [];
|
||||||
|
|
||||||
|
// On détruit la session
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
// Optionnel : supprimer le cookie de session (plus propre)
|
||||||
|
if (ini_get("session.use_cookies")) {
|
||||||
|
$params = session_get_cookie_params();
|
||||||
|
setcookie(
|
||||||
|
session_name(),
|
||||||
|
'',
|
||||||
|
time() - 42000,
|
||||||
|
$params["path"],
|
||||||
|
$params["domain"],
|
||||||
|
$params["secure"],
|
||||||
|
$params["httponly"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// On renvoie vers la page de login (ou index si tu préfères)
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
// api/get-leaves.php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require __DIR__ . '/../includes/db.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->query("
|
||||||
|
SELECT
|
||||||
|
l.id,
|
||||||
|
l.person_id,
|
||||||
|
p.name AS person_name,
|
||||||
|
l.leave_type,
|
||||||
|
l.leave_date,
|
||||||
|
l.duration
|
||||||
|
FROM pf_leaves l
|
||||||
|
JOIN pf_people p ON p.id = l.person_id
|
||||||
|
ORDER BY l.leave_date, l.person_id
|
||||||
|
");
|
||||||
|
|
||||||
|
$leaves = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'leaves' => $leaves,
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require __DIR__ . '/../includes/db.php';
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!isset($input['action'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Action manquante.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $input['action'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($action === 'delete') {
|
||||||
|
// Suppression d'un seul événement
|
||||||
|
if (!isset($input['event_id'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID manquant pour la suppression.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$eventId = (int)$input['event_id'];
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM pf_events WHERE id = ?");
|
||||||
|
$stmt->execute([$eventId]);
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Événement supprimé.']);
|
||||||
|
|
||||||
|
} elseif ($action === 'update') {
|
||||||
|
// Mise à jour d'un seul événement
|
||||||
|
if (!isset($input['event_id'], $input['new_type'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'ID ou nouveau type manquant pour la mise à jour.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$eventId = (int)$input['event_id'];
|
||||||
|
$newType = $input['new_type'];
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("UPDATE pf_events SET event_type = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$newType, $eventId]);
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Événement mis à jour.']);
|
||||||
|
|
||||||
|
} elseif ($action === 'bulk_delete') {
|
||||||
|
// Suppression en masse
|
||||||
|
if (empty($input['event_ids']) || !is_array($input['event_ids'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Liste event_ids manquante pour bulk_delete.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$eventIds = array_map('intval', $input['event_ids']);
|
||||||
|
$eventIds = array_filter($eventIds, fn($id) => $id > 0);
|
||||||
|
|
||||||
|
if (empty($eventIds)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Aucun ID valide pour bulk_delete.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$placeholders = implode(',', array_fill(0, count($eventIds), '?'));
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM pf_events WHERE id IN ($placeholders)");
|
||||||
|
$stmt->execute($eventIds);
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Événements supprimés en masse.']);
|
||||||
|
|
||||||
|
} elseif ($action === 'bulk_update') {
|
||||||
|
// Mise à jour en masse
|
||||||
|
if (empty($input['event_ids']) || !is_array($input['event_ids']) || !isset($input['new_type'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'event_ids ou new_type manquant pour bulk_update.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$eventIds = array_map('intval', $input['event_ids']);
|
||||||
|
$eventIds = array_filter($eventIds, fn($id) => $id > 0);
|
||||||
|
$newType = $input['new_type'];
|
||||||
|
|
||||||
|
if (empty($eventIds)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Aucun ID valide pour bulk_update.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$placeholders = implode(',', array_fill(0, count($eventIds), '?'));
|
||||||
|
$params = array_merge([$newType], $eventIds);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("UPDATE pf_events SET event_type = ? WHERE id IN ($placeholders)");
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Événements mis à jour en masse.']);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Action non valide.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
// api/manage-leaf.php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require __DIR__ . '/../includes/db.php';
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!isset($input['action'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Action manquante.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $input['action'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($action === 'delete_day') {
|
||||||
|
if (empty($input['date'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Date manquante pour delete_day.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dateObj = date_create($input['date']);
|
||||||
|
if (!$dateObj) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Date invalide.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$leaveDate = $dateObj->format('Y-m-d');
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM pf_leaves WHERE leave_date = ?");
|
||||||
|
$stmt->execute([$leaveDate]);
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Congés supprimés pour ce jour.']);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Action non valide.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
// api/save-leaves.php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require __DIR__ . '/../includes/db.php';
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
if (!is_array($input) || empty($input)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Données invalides (attendu tableau JSON).']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exemple d'élément attendu :
|
||||||
|
// { date: '2025-09-01', person_id: 2, leave_type: 'CP', duration: 1.0 }
|
||||||
|
|
||||||
|
$validLeaveTypes = ['CP', 'JRA', 'JA'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$stmtInsert = $pdo->prepare("
|
||||||
|
INSERT INTO pf_leaves (person_id, leave_type, leave_date, duration)
|
||||||
|
VALUES (:person_id, :leave_type, :leave_date, :duration)
|
||||||
|
");
|
||||||
|
|
||||||
|
foreach ($input as $item) {
|
||||||
|
$date = $item['date'] ?? null;
|
||||||
|
$personId = $item['person_id'] ?? null;
|
||||||
|
$leaveType = $item['leave_type'] ?? null;
|
||||||
|
$duration = isset($item['duration']) ? (float)$item['duration'] : 1.0;
|
||||||
|
|
||||||
|
if (!$date || !$personId || !$leaveType) {
|
||||||
|
continue; // on ignore les lignes incomplètes
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($leaveType, $validLeaveTypes, true)) {
|
||||||
|
continue; // leave_type invalide
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalisation de la date
|
||||||
|
$dateObj = date_create($date);
|
||||||
|
if (!$dateObj) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$leaveDate = $dateObj->format('Y-m-d');
|
||||||
|
|
||||||
|
$stmtInsert->execute([
|
||||||
|
':person_id' => (int)$personId,
|
||||||
|
':leave_type' => $leaveType,
|
||||||
|
':leave_date' => $leaveDate,
|
||||||
|
':duration' => $duration,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Congés enregistrés.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -1,94 +1,6 @@
|
|||||||
/* === RESET SIMPLE === */
|
/* === TABLES SPÉCIFIQUES AU FAMILY CALENDAR === */
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
/* Tableau des vacances scolaires */
|
||||||
margin: 0;
|
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
||||||
sans-serif;
|
|
||||||
background: #f5f5f5;
|
|
||||||
color: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === LAYOUT GLOBAL === */
|
|
||||||
.pf-container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-header {
|
|
||||||
background: #243b53;
|
|
||||||
color: #fff;
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-header-content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-logo {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-nav {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-nav-link {
|
|
||||||
color: #d9e2ec;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-nav-link:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-nav-link--active {
|
|
||||||
background: #102a43;
|
|
||||||
color: #f0f4f8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-main {
|
|
||||||
padding: 24px 10px 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-section {
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-section--panel {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 16px 16px 20px;
|
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-footer {
|
|
||||||
border-top: 1px solid #d9e2ec;
|
|
||||||
padding: 8px 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #627d98;
|
|
||||||
background: #f0f4f8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === 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 {
|
#schoolHolidaysTable {
|
||||||
width: auto;
|
width: auto;
|
||||||
table-layout: auto;
|
table-layout: auto;
|
||||||
@@ -99,81 +11,39 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* En-têtes et cellules sans retour à la ligne */
|
/* En-têtes et cellules sans retour à la ligne */
|
||||||
#schoolHolidaysTable th,
|
#schoolHolidaysTable th,
|
||||||
#schoolHolidaysTable td {
|
#schoolHolidaysTable td {
|
||||||
white-space: nowrap; /* pas de retour à la ligne dans les cellules */
|
white-space: nowrap;
|
||||||
overflow: visible; /* pas de tronquage */
|
overflow: visible;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-flex {
|
/* Planning hebdomadaire principal */
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-flex--wrap {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-gap-lg {
|
|
||||||
gap: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-card {
|
|
||||||
background: #f8fafc;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #d9e2ec;
|
|
||||||
padding: 12px 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-card--small {
|
|
||||||
min-width: 240px;
|
|
||||||
max-width: 260px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-card-title {
|
|
||||||
margin: 0 0 8px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-card-body {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === TABLES === */
|
|
||||||
|
|
||||||
/* Appliquer un layout fixe pour harmoniser la largeur des colonnes */
|
|
||||||
#planningTable {
|
#planningTable {
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
width: 100%; /* le tableau prend toute la largeur disponible */
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Colonnes : texte centré et césures possibles */
|
|
||||||
#planningTable th,
|
#planningTable th,
|
||||||
#planningTable td {
|
#planningTable td {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap; /* évite les retours à la ligne sauvages */
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis; /* coupe avec "..." si le texte déborde */
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Colonnes "Mois" et "Semaine" un peu plus étroites */
|
/* Colonnes "Mois" et "Semaine" plus étroites */
|
||||||
#planningTable th:nth-child(1),
|
#planningTable th:nth-child(1),
|
||||||
#planningTable td:nth-child(1) {
|
#planningTable td:nth-child(1),
|
||||||
width: 2%; /* Mois */
|
|
||||||
}
|
|
||||||
|
|
||||||
#planningTable th:nth-child(2),
|
#planningTable th:nth-child(2),
|
||||||
#planningTable td:nth-child(2) {
|
#planningTable td:nth-child(2) {
|
||||||
width: 2%; /* Semaine */
|
width: 2%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Colonnes jours (Lundi à Vendredi) réparties sur une largeur raisonnable */
|
/* Colonnes jours (Lundi à Vendredi) */
|
||||||
#planningTable th:nth-child(3),
|
#planningTable th:nth-child(3),
|
||||||
#planningTable td:nth-child(3),
|
#planningTable td:nth-child(3),
|
||||||
#planningTable th:nth-child(4),
|
#planningTable th:nth-child(4),
|
||||||
@@ -184,10 +54,10 @@ label {
|
|||||||
#planningTable td:nth-child(6),
|
#planningTable td:nth-child(6),
|
||||||
#planningTable th:nth-child(7),
|
#planningTable th:nth-child(7),
|
||||||
#planningTable td:nth-child(7) {
|
#planningTable td:nth-child(7) {
|
||||||
width: 2%; /* ajustable selon ton rendu */
|
width: 2%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Colonnes de totaux (Off / Extra / Centre / Avis) un peu plus compactes */
|
/* Colonnes de totaux */
|
||||||
#planningTable th:nth-child(8),
|
#planningTable th:nth-child(8),
|
||||||
#planningTable td:nth-child(8),
|
#planningTable td:nth-child(8),
|
||||||
#planningTable th:nth-child(9),
|
#planningTable th:nth-child(9),
|
||||||
@@ -199,48 +69,8 @@ label {
|
|||||||
width: 4%;
|
width: 4%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-table-wrapper {
|
/* === COLOR CODING / LÉGENDE === */
|
||||||
max-height: 600px;
|
|
||||||
overflow: auto;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #d9e2ec;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-table th,
|
|
||||||
.pf-table td {
|
|
||||||
border: 1px solid #d9e2ec;
|
|
||||||
padding: 4px 6px;
|
|
||||||
text-align: center;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-table thead th {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
background: #f0f4f8;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-table--compact th,
|
|
||||||
.pf-table--compact td {
|
|
||||||
padding: 3px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === INPUTS === */
|
|
||||||
input[type="number"] {
|
|
||||||
width: 3.1rem;
|
|
||||||
font-size: 11px;
|
|
||||||
padding: 2px 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === COLOR CODING POUR LE FAMILY CALENDAR === */
|
|
||||||
.fc-row--carole-off {
|
.fc-row--carole-off {
|
||||||
background: #ffe0e0;
|
background: #ffe0e0;
|
||||||
}
|
}
|
||||||
@@ -254,30 +84,41 @@ input[type="number"] {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 12px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-holidays-table th,
|
.fc-holidays-table th,
|
||||||
.fc-holidays-table td {
|
.fc-holidays-table td {
|
||||||
border: 1px solid #d9e2ec;
|
border: none;
|
||||||
padding: 4px 6px;
|
padding: 4px 4px;
|
||||||
text-align: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-holidays-table th {
|
.fc-holidays-table thead th {
|
||||||
background-color: #f0f4f8;
|
background: none;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #243b53;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid #d9e2ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-holidays-table tbody tr:nth-child(odd) td {
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-holidays-table tbody tr:nth-child(even) td {
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cellule de jour sélectionnée dans le calendrier */
|
/* Cellule de jour sélectionnée dans le calendrier */
|
||||||
.fc-day--selected {
|
.fc-day--selected {
|
||||||
background-color: #bde4ff !important; /* Une couleur de sélection claire, !important pour forcer le style */
|
background-color: #bde4ff !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Le menu contextuel flottant */
|
/* Le menu contextuel flottant */
|
||||||
.fc-selection-menu {
|
.fc-selection-menu {
|
||||||
display: none; /* Caché par défaut */
|
display: none;
|
||||||
position: absolute; /* Permet de le positionner près de la souris */
|
position: absolute;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
@@ -290,6 +131,7 @@ input[type="number"] {
|
|||||||
.fc-selection-menu .fc-menu-section {
|
.fc-selection-menu .fc-menu-section {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-selection-menu .fc-menu-section:last-child {
|
.fc-selection-menu .fc-menu-section:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
@@ -334,25 +176,12 @@ input[type="number"] {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Picto pour Centre (toboggan/balançoire) */
|
/* Picto pour Centre */
|
||||||
.fc-day--has-guard.fc-day--centre {
|
.fc-day--has-guard.fc-day--centre {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.fc-day--has-guard.fc-day--centre::after {
|
.fc-day--has-guard.fc-day--centre::after {
|
||||||
content: "🎢";
|
content: "🏫";
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
right: 2px;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
/* Picto pour Avis (FC Barcelone) */
|
|
||||||
.fc-day--has-guard.fc-day--avis {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.fc-day--has-guard.fc-day--avis::after {
|
|
||||||
content: "⚽";
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
right: 2px;
|
right: 2px;
|
||||||
@@ -361,31 +190,131 @@ input[type="number"] {
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Combinaisons avec dégradés --- */
|
/* Combinaisons avec dégradés */
|
||||||
/* Combinaison Vacances + Off Carole */
|
|
||||||
.fc-day--school-holiday.fc-day--off-carole {
|
.fc-day--school-holiday.fc-day--off-carole {
|
||||||
background-image: linear-gradient(45deg, #e5d9f2 49%, #ffe9a7 51%);
|
background-image: linear-gradient(45deg, #e5d9f2 49%, #ffe9a7 51%);
|
||||||
}
|
}
|
||||||
/* Combinaison Vacances + Extra Off Carole */
|
|
||||||
.fc-day--school-holiday.fc-day--extra-off-carole {
|
.fc-day--school-holiday.fc-day--extra-off-carole {
|
||||||
background-image: linear-gradient(45deg, #e5d9f2 49%, #ffd59b 51%);
|
background-image: linear-gradient(45deg, #e5d9f2 49%, #ffd59b 51%);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Eléments pour la légende */
|
/* Légende */
|
||||||
.pf-legend-item {
|
.pf-legend-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-legend-color {
|
.pf-legend-color {
|
||||||
width: 20px;
|
width: 18px;
|
||||||
height: 20px;
|
height: 18px;
|
||||||
border: 1px solid #ccc;
|
border-radius: 4px;
|
||||||
margin-right: 10px;
|
border: 1px solid #d9e2ec;
|
||||||
flex-shrink: 0;
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-legend-item span {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #334e68;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-legend-school-holiday {
|
||||||
|
background-color: #e5d9f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-legend-public-holiday {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-legend-off-carole {
|
||||||
|
background-color: #ffe9a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-legend-extra-off-carole {
|
||||||
|
background-color: #ffd59b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-legend-centre {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-legend-centre::before {
|
||||||
|
content: "🏫";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Avis indicateur */
|
||||||
|
.fc-legend-avis {
|
||||||
|
border-radius: 2px;
|
||||||
|
width: 24px;
|
||||||
|
height: 16px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
#ffcc00 0%,
|
||||||
|
#ffcc00 11%,
|
||||||
|
#c8102e 11%,
|
||||||
|
#c8102e 22%,
|
||||||
|
#ffcc00 22%,
|
||||||
|
#ffcc00 33%,
|
||||||
|
#c8102e 33%,
|
||||||
|
#c8102e 44%,
|
||||||
|
#ffcc00 44%,
|
||||||
|
#ffcc00 55%,
|
||||||
|
#c8102e 55%,
|
||||||
|
#c8102e 66%,
|
||||||
|
#ffcc00 66%,
|
||||||
|
#ffcc00 77%,
|
||||||
|
#c8102e 77%,
|
||||||
|
#c8102e 88%,
|
||||||
|
#ffcc00 88%,
|
||||||
|
#ffcc00 100%
|
||||||
|
);
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === CALENDRIER MENSUEL === */
|
/* === CALENDRIER MENSUEL === */
|
||||||
|
|
||||||
|
.fc-day--has-guard.fc-day--avis {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-day--has-guard.fc-day--avis::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 2px;
|
||||||
|
width: 14px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
#ffcc00 0%,
|
||||||
|
#ffcc00 11%,
|
||||||
|
#c8102e 11%,
|
||||||
|
#c8102e 22%,
|
||||||
|
#ffcc00 22%,
|
||||||
|
#ffcc00 33%,
|
||||||
|
#c8102e 33%,
|
||||||
|
#c8102e 44%,
|
||||||
|
#ffcc00 44%,
|
||||||
|
#ffcc00 55%,
|
||||||
|
#c8102e 55%,
|
||||||
|
#c8102e 66%,
|
||||||
|
#ffcc00 66%,
|
||||||
|
#ffcc00 77%,
|
||||||
|
#c8102e 77%,
|
||||||
|
#c8102e 88%,
|
||||||
|
#ffcc00 88%,
|
||||||
|
#ffcc00 100%
|
||||||
|
);
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
.fc-month-calendar-wrapper {
|
.fc-month-calendar-wrapper {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -395,10 +324,7 @@ input[type="number"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fc-calendar-and-summary {
|
.fc-calendar-and-summary {
|
||||||
display: grid;
|
display: block;
|
||||||
grid-template-columns: 1fr auto;
|
|
||||||
gap: 24px;
|
|
||||||
align-items: start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-month-header {
|
.fc-month-header {
|
||||||
@@ -506,6 +432,36 @@ input[type="number"] {
|
|||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fc-month-summary-inline {
|
||||||
|
margin-top: 8px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #d9e2ec;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-month-summary-inline table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-month-summary-inline th,
|
||||||
|
.fc-month-summary-inline td {
|
||||||
|
padding: 4px 6px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-month-summary-inline th {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #243b53;
|
||||||
|
border-bottom: 1px solid #d9e2ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-month-summary-inline td:first-child {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
.fc-month-day {
|
.fc-month-day {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
@@ -515,9 +471,9 @@ input[type="number"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fc-day--other-month {
|
.fc-day--other-month {
|
||||||
background: #f8fafc;
|
background: #f0f4f8;
|
||||||
color: #9fb3c8;
|
color: #9fb3c8;
|
||||||
cursor: default;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-day--weekend {
|
.fc-day--weekend {
|
||||||
@@ -555,8 +511,9 @@ input[type="number"] {
|
|||||||
.fc-month-table .fc-day--has-guard.fc-day--centre {
|
.fc-month-table .fc-day--has-guard.fc-day--centre {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-month-table .fc-day--has-guard.fc-day--centre::after {
|
.fc-month-table .fc-day--has-guard.fc-day--centre::after {
|
||||||
content: "🎢";
|
content: "🏫";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
right: 2px;
|
right: 2px;
|
||||||
@@ -564,72 +521,96 @@ input[type="number"] {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-month-table .fc-day--has-guard.fc-day--avis {
|
.fc-month-table .fc-day--has-guard.fc-day--avis {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-month-table .fc-day--has-guard.fc-day--avis::after {
|
.fc-month-table .fc-day--has-guard.fc-day--avis::after {
|
||||||
content: "⚽";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
right: 2px;
|
right: 2px;
|
||||||
font-size: 12px;
|
width: 12px;
|
||||||
line-height: 1;
|
height: 7px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
#ffcc00 0%,
|
||||||
|
#ffcc00 11%,
|
||||||
|
#c8102e 11%,
|
||||||
|
#c8102e 22%,
|
||||||
|
#ffcc00 22%,
|
||||||
|
#ffcc00 33%,
|
||||||
|
#c8102e 33%,
|
||||||
|
#c8102e 44%,
|
||||||
|
#ffcc00 44%,
|
||||||
|
#ffcc00 55%,
|
||||||
|
#c8102e 55%,
|
||||||
|
#c8102e 66%,
|
||||||
|
#ffcc00 66%,
|
||||||
|
#ffcc00 77%,
|
||||||
|
#c8102e 77%,
|
||||||
|
#c8102e 88%,
|
||||||
|
#ffcc00 88%,
|
||||||
|
#ffcc00 100%
|
||||||
|
);
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Combinaisons avec dégradés pour le calendrier mensuel */
|
|
||||||
.fc-month-table .fc-day--school-holiday.fc-day--off-carole {
|
|
||||||
background-image: linear-gradient(45deg, #e5d9f2 49%, #ffe9a7 51%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-month-table .fc-day--school-holiday.fc-day--extra-off-carole {
|
|
||||||
background-image: linear-gradient(45deg, #e5d9f2 49%, #ffd59b 51%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fc-month-table .fc-day--selected {
|
.fc-month-table .fc-day--selected {
|
||||||
background-color: #bde4ff !important;
|
background-color: #bde4ff !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fc-leaves-label-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
left: 2px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-leaves-label {
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2933;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* === TABLEAU RÉCAPITULATIF === */
|
/* === TABLEAU RÉCAPITULATIF === */
|
||||||
|
|
||||||
.fc-month-summary {
|
.fc-month-summary {
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-summary-table-wrapper {
|
.fc-summary-table-wrapper {
|
||||||
background: #f8fafc;
|
background: #f8fafc;
|
||||||
border-radius: 6px;
|
border-radius: 10px;
|
||||||
padding: 12px;
|
padding: 10px 10px 12px;
|
||||||
border: 1px solid #d9e2ec;
|
border: 1px solid #d9e2ec;
|
||||||
|
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-summary-table {
|
.fc-summary-table {
|
||||||
width: 100%;
|
font-size: 12px;
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-summary-table thead th {
|
.fc-summary-table thead th {
|
||||||
background: #f0f4f8;
|
background: transparent;
|
||||||
padding: 8px 6px;
|
border-bottom: 1px solid #d9e2ec;
|
||||||
text-align: center;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #243b53;
|
|
||||||
border: 1px solid #d9e2ec;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-summary-table tbody td {
|
.fc-summary-table tbody td {
|
||||||
border: 1px solid #d9e2ec;
|
border: none;
|
||||||
padding: 6px 8px;
|
border-bottom: 1px solid #edf2f7;
|
||||||
text-align: center;
|
|
||||||
background: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-summary-table tbody td:first-child {
|
.fc-summary-table tbody tr:last-child td {
|
||||||
text-align: left;
|
border-bottom: none;
|
||||||
font-weight: 500;
|
|
||||||
color: #243b53;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-summary-total-row {
|
.fc-summary-total-row {
|
||||||
@@ -42,6 +42,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
this.fixedEvents = [];
|
this.fixedEvents = [];
|
||||||
this.events = [];
|
this.events = [];
|
||||||
this.menuJustOpened = false;
|
this.menuJustOpened = false;
|
||||||
|
this.leaves = [];
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
window.cal = this;
|
window.cal = this;
|
||||||
@@ -55,6 +56,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
this.dbEvents = this.loadDbEvents();
|
this.dbEvents = this.loadDbEvents();
|
||||||
this.fixedEvents = await this.fetchPublicAndSchoolHolidays();
|
this.fixedEvents = await this.fetchPublicAndSchoolHolidays();
|
||||||
this.events = [...this.dbEvents, ...this.fixedEvents];
|
this.events = [...this.dbEvents, ...this.fixedEvents];
|
||||||
|
this.leaves = await this.fetchLeaves();
|
||||||
|
|
||||||
this.reprocessAndRender();
|
this.reprocessAndRender();
|
||||||
this.renderMonthCalendar();
|
this.renderMonthCalendar();
|
||||||
@@ -73,6 +75,20 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchLeaves() {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/get-leaves.php");
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error("Erreur HTTP " + res.status);
|
||||||
|
}
|
||||||
|
const data = await res.json();
|
||||||
|
return data.leaves || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erreur lors du chargement des congés Alex/Laia :", err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fetchPublicAndSchoolHolidays() {
|
async fetchPublicAndSchoolHolidays() {
|
||||||
// 1. Jours fériés (fixes)
|
// 1. Jours fériés (fixes)
|
||||||
const publicHolidays = [
|
const publicHolidays = [
|
||||||
@@ -329,6 +345,36 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
if (gardeType === "AVIS") td.classList.add("fc-day--avis");
|
if (gardeType === "AVIS") td.classList.add("fc-day--avis");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Indicateur congés Alex/Laia
|
||||||
|
const isoDate = td.dataset.date;
|
||||||
|
const leavesOnDay = this.leaves.filter(
|
||||||
|
(lv) => lv.leave_date === isoDate
|
||||||
|
);
|
||||||
|
|
||||||
|
if (leavesOnDay.length > 0) {
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.className = "fc-leaves-label-container";
|
||||||
|
|
||||||
|
const hasAlex = leavesOnDay.some((lv) => lv.person_id === 2); // Alex
|
||||||
|
const hasLaia = leavesOnDay.some((lv) => lv.person_id === 3); // Laia
|
||||||
|
|
||||||
|
if (hasAlex) {
|
||||||
|
const alexSpan = document.createElement("span");
|
||||||
|
alexSpan.className = "fc-leaves-label";
|
||||||
|
alexSpan.textContent = "AF";
|
||||||
|
container.appendChild(alexSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasLaia) {
|
||||||
|
const laiaSpan = document.createElement("span");
|
||||||
|
laiaSpan.className = "fc-leaves-label";
|
||||||
|
laiaSpan.textContent = "LM";
|
||||||
|
container.appendChild(laiaSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
td.appendChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
tr.appendChild(td);
|
tr.appendChild(td);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -378,9 +424,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
);
|
);
|
||||||
document.addEventListener("mouseup", (e) => this.handleMonthMouseUp(e));
|
document.addEventListener("mouseup", (e) => this.handleMonthMouseUp(e));
|
||||||
if (this.monthSelectionMenu) {
|
if (this.monthSelectionMenu) {
|
||||||
|
// Gestion add / add-single / delete / update sur le calendrier mensuel
|
||||||
this.monthSelectionMenu.addEventListener("click", (e) =>
|
this.monthSelectionMenu.addEventListener("click", (e) =>
|
||||||
this.handleMonthMenuClick(e)
|
this.handleMonthMenuClick(e)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Gestion des actions bulk (bulk-update*, bulk-delete*) via handleMenuClick
|
||||||
|
this.monthSelectionMenu.addEventListener("click", (e) =>
|
||||||
|
this.handleMenuClick(e)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,49 +457,174 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMouseDown(e) {
|
handleMouseDown(e) {
|
||||||
const cell = e.target.closest("td[data-date]");
|
console.log("handleMouseDown raw target:", e.target);
|
||||||
if (!cell) return;
|
|
||||||
e.preventDefault();
|
|
||||||
this.clearSelection();
|
|
||||||
|
|
||||||
|
const cell = e.target.closest("#planningTable td[data-date]");
|
||||||
|
console.log("handleMouseDown cell:", cell);
|
||||||
|
|
||||||
|
if (!cell) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.clearSelection();
|
||||||
this.isSelecting = true;
|
this.isSelecting = true;
|
||||||
|
|
||||||
cell.classList.add("fc-day--selected");
|
cell.classList.add("fc-day--selected");
|
||||||
this.selectedCells.push(cell);
|
this.selectedCells = [cell];
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"handleMouseDown after select, selectedCells =",
|
||||||
|
this.selectedCells
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseMove(e) {
|
handleMouseMove(e) {
|
||||||
if (!this.isSelecting) return;
|
if (!this.isSelecting) return;
|
||||||
const cell = e.target.closest("td[data-date]");
|
|
||||||
if (cell && !this.selectedCells.includes(cell)) {
|
const cell = e.target.closest("#planningTable td[data-date]");
|
||||||
|
if (!cell) return;
|
||||||
|
|
||||||
|
if (!this.selectedCells.includes(cell)) {
|
||||||
cell.classList.add("fc-day--selected");
|
cell.classList.add("fc-day--selected");
|
||||||
this.selectedCells.push(cell);
|
this.selectedCells.push(cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp(e) {
|
handleMouseUp(e) {
|
||||||
if (!this.isSelecting) return;
|
console.log(
|
||||||
|
"handleMouseUp called, selectedCells.length =",
|
||||||
|
this.selectedCells.length
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!this.isSelecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.isSelecting = false;
|
this.isSelecting = false;
|
||||||
|
|
||||||
if (this.selectedCells.length === 0) return;
|
if (this.selectedCells.length === 0) return;
|
||||||
|
|
||||||
if (this.selectedCells.length === 1) {
|
if (this.selectedCells.length === 1) {
|
||||||
|
// Cas 1 jour : logique habituelle
|
||||||
const date = this.selectedCells[0].dataset.date;
|
const date = this.selectedCells[0].dataset.date;
|
||||||
const eventsOnDay = this.events.filter(
|
const eventsOnDay = this.events.filter(
|
||||||
(evt) => evt.date === date && MODIFIABLE_TYPES.includes(evt.type)
|
(evt) => evt.date === date && MODIFIABLE_TYPES.includes(evt.type)
|
||||||
);
|
);
|
||||||
const conge = eventsOnDay.find((e) => CONGE_TYPES.includes(e.type));
|
const conge = eventsOnDay.find((ev) => CONGE_TYPES.includes(ev.type));
|
||||||
const garde = eventsOnDay.find((e) => GUARDE_TYPES.includes(e.type));
|
const garde = eventsOnDay.find((ev) => GUARDE_TYPES.includes(ev.type));
|
||||||
|
|
||||||
if (!conge && !garde) {
|
if (!conge && !garde) {
|
||||||
this.showAddMenu(e);
|
this.showAddMenu(e);
|
||||||
} else {
|
} else {
|
||||||
this.showEditMenuForDay(e, { conge, garde, date });
|
this.showEditMenuForDay(e, { conge, garde, date });
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Multi-jours =====
|
||||||
|
const selectedDates = this.selectedCells.map((c) => c.dataset.date);
|
||||||
|
const eventsOnDates = this.events.filter(
|
||||||
|
(evt) =>
|
||||||
|
selectedDates.includes(evt.date) &&
|
||||||
|
MODIFIABLE_TYPES.includes(evt.type)
|
||||||
|
);
|
||||||
|
|
||||||
|
const conges = eventsOnDates.filter((ev) =>
|
||||||
|
CONGE_TYPES.includes(ev.type)
|
||||||
|
);
|
||||||
|
const gardes = eventsOnDates.filter((ev) =>
|
||||||
|
GUARDE_TYPES.includes(ev.type)
|
||||||
|
);
|
||||||
|
|
||||||
|
const uniqueCongeTypes = new Set(conges.map((c) => c.type));
|
||||||
|
const uniqueGardeTypes = new Set(gardes.map((g) => g.type));
|
||||||
|
|
||||||
|
const allDatesHaveConge =
|
||||||
|
conges.length === selectedDates.length && uniqueCongeTypes.size === 1;
|
||||||
|
const allDatesHaveGarde =
|
||||||
|
gardes.length === selectedDates.length && uniqueGardeTypes.size === 1;
|
||||||
|
|
||||||
|
console.log("Multi-jours:");
|
||||||
|
console.log("selectedDates:", selectedDates);
|
||||||
|
console.log("eventsOnDates:", eventsOnDates);
|
||||||
|
console.log("conges:", conges);
|
||||||
|
console.log("gardes:", gardes);
|
||||||
|
console.log("uniqueCongeTypes:", Array.from(uniqueCongeTypes));
|
||||||
|
console.log("uniqueGardeTypes:", Array.from(uniqueGardeTypes));
|
||||||
|
console.log("allDatesHaveConge:", allDatesHaveConge);
|
||||||
|
console.log("allDatesHaveGarde:", allDatesHaveGarde);
|
||||||
|
|
||||||
|
if (allDatesHaveConge || allDatesHaveGarde) {
|
||||||
|
const bulkInfo = {
|
||||||
|
selectedDates,
|
||||||
|
conges,
|
||||||
|
gardes,
|
||||||
|
congeType: allDatesHaveConge ? conges[0].type : null,
|
||||||
|
gardeType: allDatesHaveGarde ? gardes[0].type : null,
|
||||||
|
};
|
||||||
|
this.showBulkMenu(e, bulkInfo);
|
||||||
} else {
|
} else {
|
||||||
// Multi-jours : on reste en mode ajout, avec contrôles plus tard
|
|
||||||
this.showAddMenu(e);
|
this.showAddMenu(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showBulkMenu(e, bulkInfo) {
|
||||||
|
const { selectedDates, conges, gardes, congeType, gardeType } = bulkInfo;
|
||||||
|
const nbDays = selectedDates.length;
|
||||||
|
|
||||||
|
let html =
|
||||||
|
'<div class="fc-menu-section"><strong>Actions multi-jours</strong></div>';
|
||||||
|
|
||||||
|
if (congeType) {
|
||||||
|
const oppositeConge =
|
||||||
|
congeType === "OFF_CAROLE" ? "EXTRA_OFF_CAROLE" : "OFF_CAROLE";
|
||||||
|
html += `
|
||||||
|
<div class="fc-menu-section"><strong>Congés Carole (${nbDays} jours)</strong></div>
|
||||||
|
<button
|
||||||
|
data-action="bulk-update-conge"
|
||||||
|
data-new-type="${oppositeConge}"
|
||||||
|
data-target="conge"
|
||||||
|
>
|
||||||
|
Remplacer ${congeType.replace(/_/g, " ")} par ${oppositeConge.replace(
|
||||||
|
/_/g,
|
||||||
|
" "
|
||||||
|
)} sur ${nbDays} jours
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-action="bulk-delete-conge"
|
||||||
|
data-target="conge"
|
||||||
|
>
|
||||||
|
Supprimer le congé sur ${nbDays} jours
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gardeType) {
|
||||||
|
const oppositeGarde = gardeType === "CENTRE" ? "AVIS" : "CENTRE";
|
||||||
|
html += `
|
||||||
|
<div class="fc-menu-section"><strong>Mode de garde (${nbDays} jours)</strong></div>
|
||||||
|
<button
|
||||||
|
data-action="bulk-update-garde"
|
||||||
|
data-new-type="${oppositeGarde}"
|
||||||
|
data-target="garde"
|
||||||
|
>
|
||||||
|
Remplacer ${gardeType} par ${oppositeGarde} sur ${nbDays} jours
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-action="bulk-delete-garde"
|
||||||
|
data-target="garde"
|
||||||
|
>
|
||||||
|
Supprimer le mode de garde sur ${nbDays} jours
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectionMenu.innerHTML = html;
|
||||||
|
// On stocke bulkInfo pour réutilisation dans handleMenuClick
|
||||||
|
this._currentBulkInfo = bulkInfo;
|
||||||
|
this.positionAndShowMenu(e);
|
||||||
|
}
|
||||||
|
|
||||||
handleClickOutsideMenu(e) {
|
handleClickOutsideMenu(e) {
|
||||||
if (this.menuJustOpened) {
|
if (this.menuJustOpened) {
|
||||||
this.menuJustOpened = false;
|
this.menuJustOpened = false;
|
||||||
@@ -498,6 +675,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
"garde =",
|
"garde =",
|
||||||
garde
|
garde
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// --- Section congé Carole ---
|
||||||
let congeSection = "";
|
let congeSection = "";
|
||||||
if (conge) {
|
if (conge) {
|
||||||
const oppositeConge =
|
const oppositeConge =
|
||||||
@@ -525,6 +704,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Section mode de garde ---
|
||||||
let gardeSection = "";
|
let gardeSection = "";
|
||||||
if (garde) {
|
if (garde) {
|
||||||
const oppositeGarde = garde.type === "CENTRE" ? "AVIS" : "CENTRE";
|
const oppositeGarde = garde.type === "CENTRE" ? "AVIS" : "CENTRE";
|
||||||
@@ -549,7 +729,51 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectionMenu.innerHTML = congeSection + gardeSection;
|
// --- Section congés Alex / Laia ---
|
||||||
|
console.log(
|
||||||
|
"[EDIT MENU] this.leaves length =",
|
||||||
|
(this.leaves || []).length
|
||||||
|
);
|
||||||
|
|
||||||
|
const leavesOnDay = (this.leaves || []).filter(
|
||||||
|
(lv) => lv.leave_date === date
|
||||||
|
);
|
||||||
|
console.log("[EDIT MENU] leavesOnDay =", leavesOnDay);
|
||||||
|
|
||||||
|
let leavesSection = `
|
||||||
|
<div class="fc-menu-section"><strong>Congés Alex / Laia</strong></div>
|
||||||
|
<button data-action="add-leave" data-date="${date}" data-person-id="2" data-leave-type="CP">
|
||||||
|
Alex - CP
|
||||||
|
</button>
|
||||||
|
<button data-action="add-leave" data-date="${date}" data-person-id="2" data-leave-type="JRA">
|
||||||
|
Alex - JRA
|
||||||
|
</button>
|
||||||
|
<button data-action="add-leave" data-date="${date}" data-person-id="2" data-leave-type="JA">
|
||||||
|
Alex - JA
|
||||||
|
</button>
|
||||||
|
<button data-action="add-leave" data-date="${date}" data-person-id="3" data-leave-type="CP">
|
||||||
|
Laia - CP
|
||||||
|
</button>
|
||||||
|
<button data-action="add-leave" data-date="${date}" data-person-id="3" data-leave-type="JRA">
|
||||||
|
Laia - JRA
|
||||||
|
</button>
|
||||||
|
<button data-action="add-leave" data-date="${date}" data-person-id="3" data-leave-type="JA">
|
||||||
|
Laia - JA
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (leavesOnDay.length > 0) {
|
||||||
|
leavesSection += `
|
||||||
|
<button data-action="delete-leaves-day" data-date="${date}">
|
||||||
|
Supprimer les congés Alex/Laia ce jour-là
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullHtml = congeSection + gardeSection + leavesSection;
|
||||||
|
console.log("[EDIT MENU] full menu HTML length =", fullHtml.length);
|
||||||
|
|
||||||
|
this.selectionMenu.innerHTML = fullHtml;
|
||||||
this.positionAndShowMenu(e);
|
this.positionAndShowMenu(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,7 +790,160 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
const { action, eventId, newType, type, person, date } = button.dataset;
|
const { action, eventId, newType, type, person, date } = button.dataset;
|
||||||
|
|
||||||
// === AJOUT MULTI-JOURS (sélection de plusieurs cellules) ===
|
// === BULK ACTIONS (multi-jours) ============================================
|
||||||
|
if (
|
||||||
|
action === "bulk-update-conge" ||
|
||||||
|
action === "bulk-delete-conge" ||
|
||||||
|
action === "bulk-update-garde" ||
|
||||||
|
action === "bulk-delete-garde"
|
||||||
|
) {
|
||||||
|
if (!this._currentBulkInfo) {
|
||||||
|
this.clearSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bulkInfo = this._currentBulkInfo;
|
||||||
|
const target = button.dataset.target; // "conge" ou "garde"
|
||||||
|
|
||||||
|
let events = [];
|
||||||
|
if (target === "conge") {
|
||||||
|
events = bulkInfo.conges;
|
||||||
|
} else if (target === "garde") {
|
||||||
|
events = bulkInfo.gardes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventIds = events.map((ev) => ev.id);
|
||||||
|
this.clearSelection();
|
||||||
|
this._currentBulkInfo = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let payload;
|
||||||
|
if (
|
||||||
|
action === "bulk-delete-conge" ||
|
||||||
|
action === "bulk-delete-garde"
|
||||||
|
) {
|
||||||
|
payload = {
|
||||||
|
action: "bulk_delete",
|
||||||
|
event_ids: eventIds,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const bulkNewType = button.dataset.newType;
|
||||||
|
payload = {
|
||||||
|
action: "bulk_update",
|
||||||
|
event_ids: eventIds,
|
||||||
|
new_type: bulkNewType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch("/api/manage-event.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const errData = await res.json().catch(() => ({}));
|
||||||
|
throw new Error(errData.message || "Erreur HTTP " + res.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resEvents = await fetch("/api/get-events.php");
|
||||||
|
if (!resEvents.ok) {
|
||||||
|
throw new Error("Erreur lors du rechargement des événements.");
|
||||||
|
}
|
||||||
|
const dataEvents = await resEvents.json();
|
||||||
|
|
||||||
|
this.dbEvents = dataEvents.events || [];
|
||||||
|
this.events = [...this.dbEvents, ...this.fixedEvents];
|
||||||
|
|
||||||
|
this.reprocessAndRender();
|
||||||
|
this.renderMonthCalendar();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erreur bulk:", err);
|
||||||
|
alert("Erreur lors de l'action multi-jours: " + err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === AJOUT D'UN CONGÉ ALEX/LAIA SUR UNE SEULE DATE ==========================
|
||||||
|
if (action === "add-leave") {
|
||||||
|
const leaveDate = date;
|
||||||
|
const personId = parseInt(button.dataset.personId, 10);
|
||||||
|
const leaveType = button.dataset.leaveType;
|
||||||
|
|
||||||
|
if (!leaveDate || !personId || !leaveType) {
|
||||||
|
this.clearSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newLeave = {
|
||||||
|
date: leaveDate,
|
||||||
|
person_id: personId,
|
||||||
|
leave_type: leaveType,
|
||||||
|
duration: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.clearSelection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/save-leaves.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify([newLeave]),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Erreur HTTP " + response.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recharger les congés
|
||||||
|
this.leaves = await this.fetchLeaves();
|
||||||
|
this.reprocessAndRender();
|
||||||
|
this.renderMonthCalendar();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert("Erreur lors de l'ajout de congé Alex/Laia.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === SUPPRIMER TOUS LES CONGÉS ALEX/LAIA D'UN JOUR ==========================
|
||||||
|
if (action === "delete-leaves-day") {
|
||||||
|
const leaveDate = date;
|
||||||
|
if (!leaveDate) {
|
||||||
|
this.clearSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clearSelection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/manage-leaf.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ action: "delete_day", date: leaveDate }),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const errData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(
|
||||||
|
errData.message || "Erreur HTTP " + response.status
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.leaves = await this.fetchLeaves();
|
||||||
|
this.reprocessAndRender();
|
||||||
|
this.renderMonthCalendar();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert(
|
||||||
|
"Erreur lors de la suppression des congés Alex/Laia pour ce jour: " +
|
||||||
|
err.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === AJOUT MULTI-JOURS (sélection de plusieurs cellules) ===================
|
||||||
if (action === "add") {
|
if (action === "add") {
|
||||||
const selectedDates = this.selectedCells.map((c) => c.dataset.date);
|
const selectedDates = this.selectedCells.map((c) => c.dataset.date);
|
||||||
const existingOnDates = this.dbEvents.filter((evt) =>
|
const existingOnDates = this.dbEvents.filter((evt) =>
|
||||||
@@ -636,7 +1013,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === AJOUT SUR UNE SEULE DATE (menu combiné d'un jour) ===
|
// === AJOUT SUR UNE SEULE DATE (menu combiné d'un jour) =====================
|
||||||
if (action === "add-single") {
|
if (action === "add-single") {
|
||||||
const targetDate = date;
|
const targetDate = date;
|
||||||
const existingOnDate = this.dbEvents.filter(
|
const existingOnDate = this.dbEvents.filter(
|
||||||
@@ -682,7 +1059,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
throw new Error("Erreur HTTP " + response.status);
|
throw new Error("Erreur HTTP " + response.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resync DB events
|
|
||||||
const resEvents = await fetch("/api/get-events.php");
|
const resEvents = await fetch("/api/get-events.php");
|
||||||
if (!resEvents.ok) {
|
if (!resEvents.ok) {
|
||||||
throw new Error("Erreur lors du rechargement des événements.");
|
throw new Error("Erreur lors du rechargement des événements.");
|
||||||
@@ -701,7 +1077,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === SUPPRESSION ===
|
// === SUPPRESSION SIMPLE ====================================================
|
||||||
if (action === "delete") {
|
if (action === "delete") {
|
||||||
if (!eventId) {
|
if (!eventId) {
|
||||||
console.warn("Bouton delete sans eventId, action ignorée.");
|
console.warn("Bouton delete sans eventId, action ignorée.");
|
||||||
@@ -724,7 +1100,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resync DB events
|
|
||||||
const resEvents = await fetch("/api/get-events.php");
|
const resEvents = await fetch("/api/get-events.php");
|
||||||
if (!resEvents.ok) {
|
if (!resEvents.ok) {
|
||||||
throw new Error("Erreur lors du rechargement des événements.");
|
throw new Error("Erreur lors du rechargement des événements.");
|
||||||
@@ -743,7 +1118,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === MODIFICATION (change OFF <-> EXTRA ou CENTRE <-> AVIS) ===
|
// === MODIFICATION SIMPLE ===================================================
|
||||||
if (action === "update") {
|
if (action === "update") {
|
||||||
console.log("[UPDATE] click sur bouton update", { eventId, newType });
|
console.log("[UPDATE] click sur bouton update", { eventId, newType });
|
||||||
|
|
||||||
@@ -772,7 +1147,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resync DB events
|
|
||||||
const resEvents = await fetch("/api/get-events.php");
|
const resEvents = await fetch("/api/get-events.php");
|
||||||
if (!resEvents.ok) {
|
if (!resEvents.ok) {
|
||||||
throw new Error("Erreur lors du rechargement des événements.");
|
throw new Error("Erreur lors du rechargement des événements.");
|
||||||
@@ -836,36 +1210,20 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
navigateMonth(direction) {
|
navigateMonth(direction) {
|
||||||
if (this.viewMode === "year") {
|
if (this.viewMode === "year") {
|
||||||
// Navigation par année scolaire (septembre à août)
|
// Navigation par année scolaire : on avance/recul d'un an,
|
||||||
this.currentMonth.setFullYear(
|
// en gardant currentMonth fixé sur septembre
|
||||||
this.currentMonth.getFullYear() + direction
|
const year = this.currentMonth.getFullYear() + direction;
|
||||||
);
|
this.currentMonth = new Date(year, 8, 1); // 8 = septembre
|
||||||
} else if (this.viewMode === "2months") {
|
|
||||||
this.currentMonth.setMonth(this.currentMonth.getMonth() + direction);
|
|
||||||
// S'assurer qu'on reste dans une année scolaire valide
|
|
||||||
this.adjustToSchoolYear();
|
|
||||||
} else {
|
} else {
|
||||||
this.currentMonth.setMonth(this.currentMonth.getMonth() + direction);
|
// Navigation simple par mois (1 ou 2 mois)
|
||||||
// S'assurer qu'on reste dans une année scolaire valide
|
const year = this.currentMonth.getFullYear();
|
||||||
this.adjustToSchoolYear();
|
const month = this.currentMonth.getMonth() + direction;
|
||||||
}
|
|
||||||
this.renderMonthCalendar();
|
// new Date gère automatiquement le dépassement (ex: 2025, 12 => jan 2026)
|
||||||
|
this.currentMonth = new Date(year, month, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustToSchoolYear() {
|
this.renderMonthCalendar();
|
||||||
const month = this.currentMonth.getMonth();
|
|
||||||
const year = this.currentMonth.getFullYear();
|
|
||||||
// Si on dépasse août (mois 7), on passe à septembre de l'année suivante
|
|
||||||
if (month > 7) {
|
|
||||||
this.currentMonth.setFullYear(year + 1);
|
|
||||||
this.currentMonth.setMonth(8); // Septembre
|
|
||||||
}
|
|
||||||
// Si on est avant septembre (mois 8), on reste dans l'année scolaire
|
|
||||||
// mais on pourrait vouloir revenir à septembre de l'année en cours
|
|
||||||
if (month < 8) {
|
|
||||||
// On garde l'année mais on pourrait vouloir ajuster
|
|
||||||
// Pour l'instant, on laisse comme ça car ça peut être août de l'année scolaire
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMonthCalendar() {
|
renderMonthCalendar() {
|
||||||
@@ -875,30 +1233,27 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const monthTitle = document.getElementById("fc-current-month-year");
|
const monthTitle = document.getElementById("fc-current-month-year");
|
||||||
if (monthTitle) {
|
if (monthTitle) {
|
||||||
const year = this.currentMonth.getFullYear();
|
const year = this.currentMonth.getFullYear();
|
||||||
|
const month = this.currentMonth.getMonth();
|
||||||
|
|
||||||
|
// Calcul de l'année scolaire à partir du mois courant
|
||||||
|
const schoolYearStart = month >= 8 ? year : year - 1;
|
||||||
|
const schoolYearEnd = schoolYearStart + 1;
|
||||||
|
|
||||||
if (this.viewMode === "year") {
|
if (this.viewMode === "year") {
|
||||||
// Afficher l'année scolaire (ex: 2025-2026)
|
// Année scolaire complète
|
||||||
monthTitle.textContent = `${year}-${year + 1}`;
|
monthTitle.textContent = `Année scolaire ${schoolYearStart}–${schoolYearEnd}`;
|
||||||
} else if (this.viewMode === "2months") {
|
} else if (this.viewMode === "2months") {
|
||||||
const month = this.currentMonth.getMonth();
|
|
||||||
const monthName = getMonthNameFr(month);
|
const monthName = getMonthNameFr(month);
|
||||||
const nextMonth = new Date(year, month + 1, 1);
|
const next = new Date(year, month + 1, 1);
|
||||||
const nextMonthName = getMonthNameFr(nextMonth.getMonth());
|
const nextMonthIndex = next.getMonth();
|
||||||
// Ajuster l'année si on passe de décembre à janvier
|
const nextYear = next.getFullYear();
|
||||||
const displayYear = month >= 8 ? year : year + 1;
|
const nextMonthName = getMonthNameFr(nextMonthIndex);
|
||||||
monthTitle.textContent = `${monthName} - ${nextMonthName} ${displayYear}`;
|
|
||||||
|
monthTitle.textContent = `${monthName} ${year} – ${nextMonthName} ${nextYear} (${schoolYearStart}–${schoolYearEnd})`;
|
||||||
} else {
|
} else {
|
||||||
const month = this.currentMonth.getMonth();
|
// Vue 1 mois
|
||||||
const monthName = getMonthNameFr(month);
|
const monthName = getMonthNameFr(month);
|
||||||
// Afficher l'année scolaire si on est après août
|
monthTitle.textContent = `${monthName} ${year} (${schoolYearStart}–${schoolYearEnd})`;
|
||||||
if (month >= 8) {
|
|
||||||
monthTitle.textContent = `${monthName} ${year} (${year}-${
|
|
||||||
year + 1
|
|
||||||
})`;
|
|
||||||
} else {
|
|
||||||
monthTitle.textContent = `${monthName} ${year} (${
|
|
||||||
year - 1
|
|
||||||
}-${year})`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -910,15 +1265,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
} else {
|
} else {
|
||||||
this.renderSingleMonthView();
|
this.renderSingleMonthView();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Afficher le tableau récapitulatif
|
|
||||||
this.renderMonthSummary();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSingleMonthView() {
|
renderSingleMonthView() {
|
||||||
const year = this.currentMonth.getFullYear();
|
const year = this.currentMonth.getFullYear();
|
||||||
const month = this.currentMonth.getMonth();
|
const month = this.currentMonth.getMonth();
|
||||||
this.monthCalendar.innerHTML = this.generateMonthHTML(year, month);
|
const calendarHTML = this.generateMonthHTML(year, month);
|
||||||
|
const summaryHTML = this.generateMonthSummaryHTML(year, month);
|
||||||
|
this.monthCalendar.innerHTML = calendarHTML + summaryHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateMonthTotals(year, month) {
|
calculateMonthTotals(year, month) {
|
||||||
@@ -971,6 +1325,39 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
return totals;
|
return totals;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateMonthSummaryHTML(year, month) {
|
||||||
|
const totals = this.calculateMonthTotals(year, month);
|
||||||
|
const formatTotal = (total) =>
|
||||||
|
total > 0 ? (Number.isInteger(total) ? total : total.toFixed(1)) : "";
|
||||||
|
|
||||||
|
const monthLabel = `${getMonthNameFr(month)} ${year}`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="fc-month-summary-inline">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Mois</th>
|
||||||
|
<th># Off Carole</th>
|
||||||
|
<th># Extra off Carole</th>
|
||||||
|
<th># Centre</th>
|
||||||
|
<th># Avis</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>${monthLabel}</td>
|
||||||
|
<td>${formatTotal(totals.offCarole)}</td>
|
||||||
|
<td>${formatTotal(totals.extraOffCarole)}</td>
|
||||||
|
<td>${formatTotal(totals.centre)}</td>
|
||||||
|
<td>${formatTotal(totals.avis)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
renderMonthSummary() {
|
renderMonthSummary() {
|
||||||
const summaryDiv = document.getElementById("fc-month-summary");
|
const summaryDiv = document.getElementById("fc-month-summary");
|
||||||
if (!summaryDiv) return;
|
if (!summaryDiv) return;
|
||||||
@@ -1114,18 +1501,25 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const nextMonthIndex = nextMonth > 11 ? 0 : nextMonth;
|
const nextMonthIndex = nextMonth > 11 ? 0 : nextMonth;
|
||||||
|
|
||||||
let html = '<div class="fc-two-months-container">';
|
let html = '<div class="fc-two-months-container">';
|
||||||
|
|
||||||
|
// Premier mois
|
||||||
html += `<div class="fc-month-container">`;
|
html += `<div class="fc-month-container">`;
|
||||||
html += `<div class="fc-month-title">${getMonthNameFr(month)} ${
|
html += `<div class="fc-month-title">${getMonthNameFr(month)} ${
|
||||||
month >= 8 ? year : year + 1
|
month >= 8 ? year : year + 1
|
||||||
}</div>`;
|
}</div>`;
|
||||||
html += this.generateMonthHTML(year, month);
|
html += this.generateMonthHTML(year, month);
|
||||||
|
html += this.generateMonthSummaryHTML(year, month);
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
|
|
||||||
|
// Deuxième mois
|
||||||
html += `<div class="fc-month-container">`;
|
html += `<div class="fc-month-container">`;
|
||||||
html += `<div class="fc-month-title">${getMonthNameFr(nextMonthIndex)} ${
|
html += `<div class="fc-month-title">${getMonthNameFr(nextMonthIndex)} ${
|
||||||
nextMonthIndex >= 8 ? nextYear : nextYear + 1
|
nextMonthIndex >= 8 ? nextYear : nextYear + 1
|
||||||
}</div>`;
|
}</div>`;
|
||||||
html += this.generateMonthHTML(nextYear, nextMonthIndex);
|
html += this.generateMonthHTML(nextYear, nextMonthIndex);
|
||||||
|
html += this.generateMonthSummaryHTML(nextYear, nextMonthIndex);
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
|
|
||||||
html += "</div>";
|
html += "</div>";
|
||||||
this.monthCalendar.innerHTML = html;
|
this.monthCalendar.innerHTML = html;
|
||||||
}
|
}
|
||||||
@@ -1133,13 +1527,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
renderYearView() {
|
renderYearView() {
|
||||||
const year = this.currentMonth.getFullYear();
|
const year = this.currentMonth.getFullYear();
|
||||||
let html = '<div class="fc-year-container">';
|
let html = '<div class="fc-year-container">';
|
||||||
// Année scolaire : septembre (8) à août (7) de l'année suivante
|
|
||||||
const schoolYearMonths = [];
|
const schoolYearMonths = [];
|
||||||
// Septembre à décembre de l'année en cours
|
|
||||||
for (let month = 8; month < 12; month++) {
|
for (let month = 8; month < 12; month++) {
|
||||||
schoolYearMonths.push({ year, month });
|
schoolYearMonths.push({ year, month });
|
||||||
}
|
}
|
||||||
// Janvier à août de l'année suivante
|
|
||||||
for (let month = 0; month < 8; month++) {
|
for (let month = 0; month < 8; month++) {
|
||||||
schoolYearMonths.push({ year: year + 1, month });
|
schoolYearMonths.push({ year: year + 1, month });
|
||||||
}
|
}
|
||||||
@@ -1150,6 +1541,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
month
|
month
|
||||||
)} ${monthYear}</div>`;
|
)} ${monthYear}</div>`;
|
||||||
html += this.generateMonthHTML(monthYear, month);
|
html += this.generateMonthHTML(monthYear, month);
|
||||||
|
html += this.generateMonthSummaryHTML(monthYear, month);
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
});
|
});
|
||||||
html += "</div>";
|
html += "</div>";
|
||||||
@@ -1235,7 +1627,27 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
if (gardeType === "AVIS") classes += " fc-day--avis";
|
if (gardeType === "AVIS") classes += " fc-day--avis";
|
||||||
}
|
}
|
||||||
|
|
||||||
html += `<td class="${classes}" data-date="${isoDate}">${day}</td>`;
|
// Construire la cellule avec indicateur congés Alex/Laia
|
||||||
|
// (on génère un TD "manuel" pour pouvoir inclure le conteneur)
|
||||||
|
const leavesOnDay = this.leaves.filter(
|
||||||
|
(lv) => lv.leave_date === isoDate
|
||||||
|
);
|
||||||
|
|
||||||
|
let cellInnerHTML = `${day}`;
|
||||||
|
|
||||||
|
if (leavesOnDay.length > 0) {
|
||||||
|
const hasAlex = leavesOnDay.some((lv) => lv.person_id === 2); // Alex
|
||||||
|
const hasLaia = leavesOnDay.some((lv) => lv.person_id === 3); // Laia
|
||||||
|
|
||||||
|
let leavesHtml = `<div class="fc-leaves-label-container">`;
|
||||||
|
if (hasAlex) leavesHtml += `<span class="fc-leaves-label">AF</span>`;
|
||||||
|
if (hasLaia) leavesHtml += `<span class="fc-leaves-label">LM</span>`;
|
||||||
|
leavesHtml += `</div>`;
|
||||||
|
|
||||||
|
cellInnerHTML += leavesHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `<td class="${classes}" data-date="${isoDate}">${cellInnerHTML}</td>`;
|
||||||
dayCount++;
|
dayCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1277,6 +1689,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
if (this.monthSelectedCells.length === 0) return;
|
if (this.monthSelectedCells.length === 0) return;
|
||||||
|
|
||||||
if (this.monthSelectedCells.length === 1) {
|
if (this.monthSelectedCells.length === 1) {
|
||||||
|
// Cas 1 jour : logique habituelle
|
||||||
const date = this.monthSelectedCells[0].dataset.date;
|
const date = this.monthSelectedCells[0].dataset.date;
|
||||||
const eventsOnDay = this.events.filter(
|
const eventsOnDay = this.events.filter(
|
||||||
(evt) => evt.date === date && MODIFIABLE_TYPES.includes(evt.type)
|
(evt) => evt.date === date && MODIFIABLE_TYPES.includes(evt.type)
|
||||||
@@ -1289,6 +1702,41 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
} else {
|
} else {
|
||||||
this.showMonthEditMenuForDay(e, { conge, garde, date });
|
this.showMonthEditMenuForDay(e, { conge, garde, date });
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Multi-jours : tentative de bulk edit =====
|
||||||
|
const selectedDates = this.monthSelectedCells.map((c) => c.dataset.date);
|
||||||
|
const eventsOnDates = this.events.filter(
|
||||||
|
(evt) =>
|
||||||
|
selectedDates.includes(evt.date) &&
|
||||||
|
MODIFIABLE_TYPES.includes(evt.type)
|
||||||
|
);
|
||||||
|
|
||||||
|
const conges = eventsOnDates.filter((ev) =>
|
||||||
|
CONGE_TYPES.includes(ev.type)
|
||||||
|
);
|
||||||
|
const gardes = eventsOnDates.filter((ev) =>
|
||||||
|
GUARDE_TYPES.includes(ev.type)
|
||||||
|
);
|
||||||
|
|
||||||
|
const uniqueCongeTypes = new Set(conges.map((c) => c.type));
|
||||||
|
const uniqueGardeTypes = new Set(gardes.map((g) => g.type));
|
||||||
|
|
||||||
|
const allDatesHaveConge =
|
||||||
|
conges.length === selectedDates.length && uniqueCongeTypes.size === 1;
|
||||||
|
const allDatesHaveGarde =
|
||||||
|
gardes.length === selectedDates.length && uniqueGardeTypes.size === 1;
|
||||||
|
|
||||||
|
if (allDatesHaveConge || allDatesHaveGarde) {
|
||||||
|
const bulkInfo = {
|
||||||
|
selectedDates,
|
||||||
|
conges,
|
||||||
|
gardes,
|
||||||
|
congeType: allDatesHaveConge ? conges[0].type : null,
|
||||||
|
gardeType: allDatesHaveGarde ? gardes[0].type : null,
|
||||||
|
};
|
||||||
|
this.showMonthBulkMenu(e, bulkInfo);
|
||||||
} else {
|
} else {
|
||||||
this.showMonthAddMenu(e);
|
this.showMonthAddMenu(e);
|
||||||
}
|
}
|
||||||
@@ -1319,6 +1767,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
showMonthEditMenuForDay(e, { conge, garde, date }) {
|
showMonthEditMenuForDay(e, { conge, garde, date }) {
|
||||||
if (!this.monthSelectionMenu) return;
|
if (!this.monthSelectionMenu) return;
|
||||||
|
|
||||||
|
// --- Section congé Carole ---
|
||||||
let congeSection = "";
|
let congeSection = "";
|
||||||
if (conge) {
|
if (conge) {
|
||||||
const oppositeConge =
|
const oppositeConge =
|
||||||
@@ -1346,6 +1796,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Section mode de garde ---
|
||||||
let gardeSection = "";
|
let gardeSection = "";
|
||||||
if (garde) {
|
if (garde) {
|
||||||
const oppositeGarde = garde.type === "CENTRE" ? "AVIS" : "CENTRE";
|
const oppositeGarde = garde.type === "CENTRE" ? "AVIS" : "CENTRE";
|
||||||
@@ -1370,7 +1821,103 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.monthSelectionMenu.innerHTML = congeSection + gardeSection;
|
// --- Section congés Alex / Laia ---
|
||||||
|
const leavesOnDay = (this.leaves || []).filter(
|
||||||
|
(lv) => lv.leave_date === date
|
||||||
|
);
|
||||||
|
|
||||||
|
let leavesSection = `
|
||||||
|
<div class="fc-menu-section"><strong>Congés Alex / Laia</strong></div>
|
||||||
|
<button data-action="add-leave" data-date="${date}" data-person-id="2" data-leave-type="CP">
|
||||||
|
Alex - CP
|
||||||
|
</button>
|
||||||
|
<button data-action="add-leave" data-date="${date}" data-person-id="2" data-leave-type="JRA">
|
||||||
|
Alex - JRA
|
||||||
|
</button>
|
||||||
|
<button data-action="add-leave" data-date="${date}" data-person-id="2" data-leave-type="JA">
|
||||||
|
Alex - JA
|
||||||
|
</button>
|
||||||
|
<button data-action="add-leave" data-date="${date}" data-person-id="3" data-leave-type="CP">
|
||||||
|
Laia - CP
|
||||||
|
</button>
|
||||||
|
<button data-action="add-leave" data-date="${date}" data-person-id="3" data-leave-type="JRA">
|
||||||
|
Laia - JRA
|
||||||
|
</button>
|
||||||
|
<button data-action="add-leave" data-date="${date}" data-person-id="3" data-leave-type="JA">
|
||||||
|
Laia - JA
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (leavesOnDay.length > 0) {
|
||||||
|
leavesSection += `
|
||||||
|
<button data-action="delete-leaves-day" data-date="${date}">
|
||||||
|
Supprimer les congés Alex/Laia ce jour-là
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Assemblage du menu ---
|
||||||
|
this.monthSelectionMenu.innerHTML =
|
||||||
|
congeSection + gardeSection + leavesSection;
|
||||||
|
this.positionAndShowMonthMenu(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
showMonthBulkMenu(e, bulkInfo) {
|
||||||
|
if (!this.monthSelectionMenu) return;
|
||||||
|
|
||||||
|
const { selectedDates, conges, gardes, congeType, gardeType } = bulkInfo;
|
||||||
|
const nbDays = selectedDates.length;
|
||||||
|
|
||||||
|
let html =
|
||||||
|
'<div class="fc-menu-section"><strong>Actions multi-jours</strong></div>';
|
||||||
|
|
||||||
|
if (congeType) {
|
||||||
|
const oppositeConge =
|
||||||
|
congeType === "OFF_CAROLE" ? "EXTRA_OFF_CAROLE" : "OFF_CAROLE";
|
||||||
|
html += `
|
||||||
|
<div class="fc-menu-section"><strong>Congés Carole (${nbDays} jours)</strong></div>
|
||||||
|
<button
|
||||||
|
data-action="bulk-update-conge"
|
||||||
|
data-new-type="${oppositeConge}"
|
||||||
|
data-target="conge"
|
||||||
|
>
|
||||||
|
Remplacer ${congeType.replace(/_/g, " ")} par ${oppositeConge.replace(
|
||||||
|
/_/g,
|
||||||
|
" "
|
||||||
|
)} sur ${nbDays} jours
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-action="bulk-delete-conge"
|
||||||
|
data-target="conge"
|
||||||
|
>
|
||||||
|
Supprimer le congé sur ${nbDays} jours
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gardeType) {
|
||||||
|
const oppositeGarde = gardeType === "CENTRE" ? "AVIS" : "CENTRE";
|
||||||
|
html += `
|
||||||
|
<div class="fc-menu-section"><strong>Mode de garde (${nbDays} jours)</strong></div>
|
||||||
|
<button
|
||||||
|
data-action="bulk-update-garde"
|
||||||
|
data-new-type="${oppositeGarde}"
|
||||||
|
data-target="garde"
|
||||||
|
>
|
||||||
|
Remplacer ${gardeType} par ${oppositeGarde} sur ${nbDays} jours
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-action="bulk-delete-garde"
|
||||||
|
data-target="garde"
|
||||||
|
>
|
||||||
|
Supprimer le mode de garde sur ${nbDays} jours
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.monthSelectionMenu.innerHTML = html;
|
||||||
|
// On réutilise la même propriété que pour le planning hebdo :
|
||||||
|
this._currentBulkInfo = bulkInfo;
|
||||||
this.positionAndShowMonthMenu(e);
|
this.positionAndShowMonthMenu(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 535 KiB |
@@ -0,0 +1,117 @@
|
|||||||
|
/* === HOME : fond et overlay === */
|
||||||
|
|
||||||
|
/* Fond image + overlay uniquement pour la home */
|
||||||
|
body.pf-home {
|
||||||
|
position: relative;
|
||||||
|
background-image: url("/modules/home/assets/img/background.jpg");
|
||||||
|
background-size: cover;
|
||||||
|
background-position: top center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header translucide par-dessus l'image sur la home */
|
||||||
|
.pf-home .pf-header {
|
||||||
|
background: rgba(36, 59, 83, 0.85);
|
||||||
|
backdrop-filter: blur(4px); /* optionnel, si supporté */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Voile sombre sur toute la page home */
|
||||||
|
body.pf-home::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(15, 23, 42, 0.35);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* S'assurer que le contenu passe au-dessus du voile */
|
||||||
|
body.pf-home > * {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === HOME / MODULES CARDS === */
|
||||||
|
|
||||||
|
/* Container des cartes de modules */
|
||||||
|
.pf-modules-cards {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card module : réutilise .pf-card mais plus "clicable" */
|
||||||
|
.pf-card--module {
|
||||||
|
min-width: 240px;
|
||||||
|
max-width: 320px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
background: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.15s ease, box-shadow 0.15s ease,
|
||||||
|
border-color 0.15s ease, background-color 0.15s ease;
|
||||||
|
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-card--module:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.12);
|
||||||
|
border-color: #bcccdc;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Désactivation / "à venir" */
|
||||||
|
.pf-card--disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-card--disabled:hover {
|
||||||
|
transform: none;
|
||||||
|
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.06);
|
||||||
|
border-color: #d9e2ec;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icône dans la card */
|
||||||
|
.pf-card-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CTA (texte en bas de card) */
|
||||||
|
.pf-card-cta {
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #243b53;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-card-cta::after {
|
||||||
|
content: "→";
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assure que le CTA reste en bas quand le texte est plus long */
|
||||||
|
.pf-card--module .pf-card-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bloc user info */
|
||||||
|
.pf-user-info {
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-user-info a {
|
||||||
|
color: #c81e1e;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-user-info a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user