MIGRATION VERS GITEA
This commit is contained in:
commit
5f9d991c15
333 files changed
+108514
No files matched your search
@@ -0,0 +1,305 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import NavPanel from '../../components/nav-panel/NavPanel';
|
||||
import { createTravaux, getHorairesSubstitution } from '../../api/travaux';
|
||||
import { getAllLignes } from '../../api/lignes';
|
||||
import { canPerform, getCurrentUser, getScopedRegionId } from '../../utils/userUtils';
|
||||
import './Travaux.css';
|
||||
|
||||
import RichTextEditor from '../gares/RichTextEditor';
|
||||
|
||||
const JOURS_SEMAINE = [
|
||||
{ value: 'lundi', label: 'Lundi' },
|
||||
{ value: 'mardi', label: 'Mardi' },
|
||||
{ value: 'mercredi', label: 'Mercredi' },
|
||||
{ value: 'jeudi', label: 'Jeudi' },
|
||||
{ value: 'vendredi', label: 'Vendredi' },
|
||||
{ value: 'samedi', label: 'Samedi' },
|
||||
{ value: 'dimanche', label: 'Dimanche' }
|
||||
];
|
||||
|
||||
export default function CreerTravaux() {
|
||||
const currentUser = getCurrentUser();
|
||||
const canCreateTravaux = canPerform(currentUser, 'travaux', 'create');
|
||||
|
||||
const [titre, setTitre] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [dateDebut, setDateDebut] = useState('');
|
||||
const [dateFin, setDateFin] = useState('');
|
||||
const [joursTravaux, setJoursTravaux] = useState([]);
|
||||
const [heureDebut, setHeureDebut] = useState('');
|
||||
const [heureFin, setHeureFin] = useState('');
|
||||
const [actif, setActif] = useState(true);
|
||||
|
||||
// Substitutions
|
||||
const [substitutionsActive, setSubstitutionsActive] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [horairesDisponibles, setHorairesDisponibles] = useState([]);
|
||||
const [horairesSelectionnes, setHorairesSelectionnes] = useState([]);
|
||||
const [loadingHoraires, setLoadingHoraires] = useState(false);
|
||||
const [showResults, setShowResults] = useState(false);
|
||||
|
||||
const [saving, setSaving] = useState(false);
|
||||
const scopedRegionId = getScopedRegionId(getCurrentUser());
|
||||
|
||||
const [lignes, setLignes] = useState([]);
|
||||
const [selectedLigneId, setSelectedLigneId] = useState('');
|
||||
const [loadingLignes, setLoadingLignes] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLignes = async () => {
|
||||
setLoadingLignes(true);
|
||||
try {
|
||||
const { data, error } = await getAllLignes(scopedRegionId);
|
||||
if (!error && data) {
|
||||
setLignes(data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Erreur lors du chargement des lignes:', err);
|
||||
} finally {
|
||||
setLoadingLignes(false);
|
||||
}
|
||||
};
|
||||
fetchLignes();
|
||||
}, [scopedRegionId]);
|
||||
|
||||
const handleSearchHoraires = useCallback(async () => {
|
||||
setLoadingHoraires(true);
|
||||
setShowResults(true);
|
||||
try {
|
||||
const { data, error } = await getHorairesSubstitution(searchQuery, scopedRegionId);
|
||||
if (!error && data) {
|
||||
setHorairesDisponibles(data);
|
||||
} else {
|
||||
setHorairesDisponibles([]);
|
||||
}
|
||||
} catch (err) {
|
||||
setHorairesDisponibles([]);
|
||||
} finally {
|
||||
setLoadingHoraires(false);
|
||||
}
|
||||
}, [searchQuery, scopedRegionId]);
|
||||
|
||||
const toggleHoraireSelection = (horaire) => {
|
||||
const existe = horairesSelectionnes.find(h => h.id === horaire.id);
|
||||
if (existe) {
|
||||
setHorairesSelectionnes(horairesSelectionnes.filter(h => h.id !== horaire.id));
|
||||
} else {
|
||||
setHorairesSelectionnes([...horairesSelectionnes, horaire]);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleJour = (jour) => {
|
||||
if (joursTravaux.includes(jour)) {
|
||||
setJoursTravaux(joursTravaux.filter(j => j !== jour));
|
||||
} else {
|
||||
setJoursTravaux([...joursTravaux, jour]);
|
||||
}
|
||||
};
|
||||
|
||||
const formatHeure = (heure) => heure ? heure.substring(0, 5) : '—';
|
||||
|
||||
const navigate = (path) => { window.location.href = path; };
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!canCreateTravaux) {
|
||||
alert('Vous n\'avez pas les droits pour créer des travaux.');
|
||||
return;
|
||||
}
|
||||
if (!titre.trim() || !dateDebut || !dateFin || joursTravaux.length === 0 || !heureDebut || !heureFin) {
|
||||
alert('Veuillez remplir tous les champs obligatoires');
|
||||
return;
|
||||
}
|
||||
if (new Date(dateFin) < new Date(dateDebut)) {
|
||||
alert('La date de fin doit être postérieure à la date de début');
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
const payload = {
|
||||
titre,
|
||||
description: description || null,
|
||||
date_debut: dateDebut,
|
||||
date_fin: dateFin,
|
||||
jours_travaux: joursTravaux,
|
||||
heure_debut: heureDebut,
|
||||
heure_fin: heureFin,
|
||||
actif,
|
||||
region_id: scopedRegionId || null,
|
||||
ligne_id: selectedLigneId || null,
|
||||
ligne_nom: selectedLigneId ? (lignes.find(l => l.id === selectedLigneId)?.nom || null) : null
|
||||
};
|
||||
|
||||
const substitutionsIds = substitutionsActive ? horairesSelectionnes.map(h => h.id) : [];
|
||||
const { error } = await createTravaux(payload, substitutionsIds);
|
||||
|
||||
if (error) {
|
||||
alert('Erreur lors de la création des travaux');
|
||||
} else {
|
||||
navigate('/travaux');
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Une erreur inattendue s\'est produite');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sncf-groupe panel-layout">
|
||||
<div className="panel-nav"><NavPanel /></div>
|
||||
<div className="panel-content travaux-form-page-content">
|
||||
|
||||
<header className="travaux-page-header" style={{ borderBottom: 'none', padding: '0 0 30px 0', background: 'transparent' }}>
|
||||
<div>
|
||||
<p className="travaux-page-kicker">Création</p>
|
||||
<h1>Créer des travaux</h1>
|
||||
<p className="travaux-page-description">Planifiez de nouveaux travaux et leurs substitutions.</p>
|
||||
</div>
|
||||
<wcs-button className="wcs-secondary" onClick={() => navigate('/travaux')}>
|
||||
<wcs-mat-icon icon="close"></wcs-mat-icon> Annuler
|
||||
</wcs-button>
|
||||
</header>
|
||||
|
||||
<div className="travaux-glass-card">
|
||||
<h2 className="travaux-section-title"><wcs-mat-icon icon="info"></wcs-mat-icon> Informations Générales</h2>
|
||||
<p className="travaux-section-subtitle">Saisissez un titre clair et une description détaillée pour ces travaux.</p>
|
||||
|
||||
<div className="premium-input-group">
|
||||
<label className="premium-label">Titre des travaux *</label>
|
||||
<input type="text" className="premium-input" placeholder="Ex: Maintenance des voies Paris-Lyon" value={titre} onChange={(e) => setTitre(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className="premium-input-group">
|
||||
<label className="premium-label">Description détaillée</label>
|
||||
<RichTextEditor value={description} onChange={setDescription} placeholder="Contexte opérationnel, impacts prévus..." />
|
||||
</div>
|
||||
|
||||
<div className="premium-input-group">
|
||||
<label className="premium-label">Ligne ferroviaire associée</label>
|
||||
<select
|
||||
className="premium-input"
|
||||
value={selectedLigneId}
|
||||
onChange={(e) => setSelectedLigneId(e.target.value)}
|
||||
disabled={loadingLignes}
|
||||
>
|
||||
<option value="">Sélectionner une ligne (optionnel)</option>
|
||||
{lignes.map(l => (
|
||||
<option key={l.id} value={l.id}>{l.nom}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="travaux-glass-card">
|
||||
<h2 className="travaux-section-title"><wcs-mat-icon icon="event"></wcs-mat-icon> Planification</h2>
|
||||
<p className="travaux-section-subtitle">Définissez la période, les jours concernés et la plage horaire d'intervention.</p>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '20px' }}>
|
||||
<div className="premium-input-group">
|
||||
<label className="premium-label">Date de début *</label>
|
||||
<input type="date" className="premium-input" value={dateDebut} onChange={(e) => setDateDebut(e.target.value)} />
|
||||
</div>
|
||||
<div className="premium-input-group">
|
||||
<label className="premium-label">Date de fin *</label>
|
||||
<input type="date" className="premium-input" value={dateFin} onChange={(e) => setDateFin(e.target.value)} />
|
||||
</div>
|
||||
<div className="premium-input-group">
|
||||
<label className="premium-label">Heure de début *</label>
|
||||
<input type="time" className="premium-input" value={heureDebut} onChange={(e) => setHeureDebut(e.target.value)} />
|
||||
</div>
|
||||
<div className="premium-input-group">
|
||||
<label className="premium-label">Heure de fin *</label>
|
||||
<input type="time" className="premium-input" value={heureFin} onChange={(e) => setHeureFin(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="premium-input-group" style={{ marginTop: '10px' }}>
|
||||
<label className="premium-label">Jours de travaux *</label>
|
||||
<div className="premium-checkbox-group">
|
||||
{JOURS_SEMAINE.map((jour) => (
|
||||
<label key={jour.value} className={`premium-checkbox-label ${joursTravaux.includes(jour.value) ? 'selected' : ''}`}>
|
||||
<input type="checkbox" checked={joursTravaux.includes(jour.value)} onChange={() => toggleJour(jour.value)} />
|
||||
{jour.label}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="travaux-glass-card">
|
||||
<h2 className="travaux-section-title"><wcs-mat-icon icon="swap_horiz"></wcs-mat-icon> Substitutions (Optionnel)</h2>
|
||||
<p className="travaux-section-subtitle">Gérez les trains ou cars de substitution associés à ces travaux.</p>
|
||||
|
||||
<label className={`premium-checkbox-label ${substitutionsActive ? 'selected' : ''}`} style={{ display: 'inline-flex', marginBottom: '20px' }}>
|
||||
<input type="checkbox" checked={substitutionsActive} onChange={(e) => setSubstitutionsActive(e.target.checked)} />
|
||||
Activer la recherche de substitutions
|
||||
</label>
|
||||
|
||||
{substitutionsActive && (
|
||||
<div>
|
||||
<div className="premium-input-group" style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<input type="text" className="premium-input" placeholder="Numéro de train, gare..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} onKeyPress={(e) => { if (e.key === 'Enter') handleSearchHoraires(); }} />
|
||||
<wcs-button className="wcs-primary" onClick={handleSearchHoraires} style={{ flexShrink: 0, height: '46px' }}>Rechercher</wcs-button>
|
||||
</div>
|
||||
|
||||
{horairesSelectionnes.length > 0 && (
|
||||
<div style={{ marginTop: '20px', background: '#eff6ff', padding: '15px', borderRadius: '10px', border: '1px solid #bfdbfe' }}>
|
||||
<h4 style={{ margin: '0 0 10px 0', color: '#1d4ed8' }}>{horairesSelectionnes.length} Substitution(s) Sélectionnée(s)</h4>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
{horairesSelectionnes.map(h => (
|
||||
<div key={h.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: '#fff', padding: '10px', borderRadius: '8px', border: '1px solid #dbeafe' }}>
|
||||
<div>
|
||||
<strong style={{ color: '#1e293b' }}>{h.numero_train}</strong> <span style={{ color: '#64748b', fontSize: '0.9rem' }}>— {h.gare_depart_nom} → {h.gare_arrivee_nom}</span>
|
||||
</div>
|
||||
<wcs-button className="wcs-danger wcs-small" onClick={() => toggleHoraireSelection(h)}><wcs-mat-icon icon="close"></wcs-mat-icon></wcs-button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showResults && (
|
||||
<div style={{ marginTop: '20px' }}>
|
||||
<h4 style={{ marginBottom: '10px', color: '#334155' }}>Résultats ({horairesDisponibles.length})</h4>
|
||||
{loadingHoraires ? (
|
||||
<div style={{ padding: '20px', textAlign: 'center' }}><wcs-spinner></wcs-spinner></div>
|
||||
) : (
|
||||
<div style={{ maxHeight: '300px', overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
{horairesDisponibles.map(h => {
|
||||
const isSelected = horairesSelectionnes.some(hs => hs.id === h.id);
|
||||
return (
|
||||
<div key={h.id} onClick={() => toggleHoraireSelection(h)} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '12px', background: isSelected ? '#eff6ff' : '#f8fafc', border: `1px solid ${isSelected ? '#3b82f6' : '#e2e8f0'}`, borderRadius: '8px', cursor: 'pointer' }}>
|
||||
<div>
|
||||
<strong style={{ color: '#1e293b', marginRight: '8px' }}>{h.numero_train}</strong>
|
||||
{h.type_train && <span style={{ fontSize: '0.8rem', background: '#e2e8f0', padding: '2px 6px', borderRadius: '4px', marginRight: '8px' }}>{h.type_train}</span>}
|
||||
<span style={{ color: '#64748b', fontSize: '0.9rem' }}>{h.gare_depart_nom} ({formatHeure(h.heure_depart)}) → {h.gare_arrivee_nom} ({formatHeure(h.heure_arrivee)})</span>
|
||||
</div>
|
||||
{isSelected && <wcs-mat-icon icon="check_circle" style={{ color: '#3b82f6' }}></wcs-mat-icon>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="premium-actions">
|
||||
<label className={`premium-checkbox-label ${actif ? 'selected' : ''}`} style={{ marginRight: 'auto' }}>
|
||||
<input type="checkbox" checked={actif} onChange={(e) => setActif(e.target.checked)} />
|
||||
{actif ? '✔ Travaux Actifs' : 'Travaux Inactifs'}
|
||||
</label>
|
||||
|
||||
<wcs-button className="wcs-secondary" onClick={() => navigate('/travaux')}>Annuler</wcs-button>
|
||||
<wcs-button className="wcs-primary" onClick={handleCreate} disabled={saving || !canCreateTravaux}>
|
||||
{saving ? <wcs-spinner></wcs-spinner> : 'Enregistrer les travaux'}
|
||||
</wcs-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user