V2.0.1 : VOICI LES MODIFICATIONS MAJEURS APPRTÉES DANS CETTE VERSION :

-> REFONTE DU DESIGN DES AFFICHEURS CLASSIQUES (GÉNÉRAL + QUAIS)
-> IMPLÉMENTATION DU DESIGN AFL : GÉNÉRAL ET QUAIS

-> CHOIX DE LA SÉLECTION DES TYPES D'AFFICHEURS (GÉNÉRAL / QUAIS) UNE FOIS LE TYPE D'AFFICHEUR SÉLECTIONNÉS
This commit is contained in:
Ferrovia committed 2026-07-10 17:55:13 +02:00
1 parent 71822823f2
commit 19868a451f
16 files changed
+156 -48

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
require('dotenv').config();
const { createClient } = require('@supabase/supabase-js');
const supabase = createClient(process.env.REACT_APP_SUPABASE_URL, process.env.REACT_APP_SUPABASE_ANON_KEY);
async function run() {
const { data, error } = await supabase.rpc('get_tables');
// If rpc fails, we can query information_schema or just list common table names
}
run();
+9
View File
@@ -0,0 +1,9 @@
require('dotenv').config();
const { createClient } = require('@supabase/supabase-js');
const supabase = createClient(process.env.REACT_APP_SUPABASE_URL, process.env.REACT_APP_SUPABASE_ANON_KEY);
async function run() {
const { data, error } = await supabase.from('annonces_diffusions').select('*').order('created_at', { ascending: false }).limit(1);
if (error) console.error(error);
else console.log(JSON.stringify(data[0], null, 2));
}
run();
+9
View File
@@ -0,0 +1,9 @@
require('dotenv').config();
const { createClient } = require('@supabase/supabase-js');
const supabase = createClient(process.env.REACT_APP_SUPABASE_URL, process.env.REACT_APP_SUPABASE_ANON_KEY);
async function run() {
const { data, error } = await supabase.from('annonces').select('*').limit(2);
if (error) console.error(error);
else console.log(JSON.stringify(data, null, 2));
}
run();
+9
View File
@@ -0,0 +1,9 @@
require('dotenv').config();
const { createClient } = require('@supabase/supabase-js');
const supabase = createClient(process.env.REACT_APP_SUPABASE_URL, process.env.REACT_APP_SUPABASE_ANON_KEY);
async function run() {
const { data, error } = await supabase.from('annonces_diffusions').select('*').limit(1);
if (error) console.error(error);
else console.log(Object.keys(data[0]));
}
run();
+12
View File
@@ -0,0 +1,12 @@
require('dotenv').config();
const { createClient } = require('@supabase/supabase-js');
const supabase = createClient(process.env.REACT_APP_SUPABASE_URL, process.env.REACT_APP_SUPABASE_ANON_KEY);
async function run() {
const { data, error } = await supabase.from('annonces_diffusions').select(`
*,
annonce:annonces_enregistrees(variables_values)
`).order('created_at', { ascending: false }).limit(1);
if (error) console.error(error);
else console.log(JSON.stringify(data[0], null, 2));
}
run();
+47
View File
@@ -0,0 +1,47 @@
/**
* Récupère TOUTES les lignes d'une table Supabase en paginant automatiquement.
*
* PostgREST (Supabase) limite par défaut à 1000 lignes par requête.
* Cette fonction envoie plusieurs requêtes avec `.range()` pour obtenir
* l'intégralité des résultats, puis les concatène.
*
* Usage :
* fetchAllRows(supabase.from('horaires').select('*'))
*
* En interne, `.range()` modifie le header Range de la requête et chaque
* `await` sur un PostgrestBuilder déclenche un nouveau fetch HTTP, ce qui
* permet de paginer sur le même builder.
*
* @param {object} query - Query Supabase (ex: supabase.from('horaires').select('*'))
* @param {number} [pageSize=500] - Nombre de lignes par page (≤ 1000)
* @returns {Promise<{data: Array, error: object|null}>}
*/
export async function fetchAllRows(query, pageSize = 500) {
let allData = [];
let from = 0;
let hasMore = true;
while (hasMore) {
const to = from + pageSize - 1;
const { data, error } = await query.range(from, to);
if (error) {
return { data: null, error };
}
if (!data || data.length === 0) {
hasMore = false;
} else {
allData = allData.concat(data);
if (data.length < pageSize) {
hasMore = false;
} else {
from += pageSize;
}
}
}
return { data: allData, error: null };
}
export default fetchAllRows;
+13 -1
View File
@@ -19,7 +19,19 @@ if (!supabaseUrl || !supabaseAnonKey) {
'Vérifiez REACT_APP_SUPABASE_URL et REACT_APP_SUPABASE_ANON_KEY dans .env et redémarrez le serveur.'
);
} else {
supabase = createClient(supabaseUrl, supabaseAnonKey);
supabase = createClient(supabaseUrl, supabaseAnonKey, {
realtime: {
// Désactivé : le reverse proxy ne supporte pas le WebSocket vers le port 3001.
// L'appli utilise un fallback par polling (useAnnonceSonoreRealtime).
params: { eventsPerSecond: 0 },
},
// Empêcher la connexion WebSocket automatique
global: {
headers: {},
},
});
// Désactiver complètement le canal Realtime pour éviter les erreurs wss:// dans la console
try { supabase.realtime.disconnect(); } catch (e) { /* ignore */ }
}
export { supabase };
+5 -5
View File
@@ -27,7 +27,7 @@ export default function SelectAfficheur({ onBack, navigate }) {
}
try {
setLoading(true);
const { data, error } = await supabase.from('horaires').select('stops, quai_reel, voie, quai, gare_depart_nom, gare_arrivee_nom');
const { data, error } = await supabase.from('horaires').select('quai_reel, voie, quai, gare_depart_nom, gare_arrivee_nom, gares_desservies');
if (error) throw error;
if (!mounted) return;
@@ -36,10 +36,10 @@ export default function SelectAfficheur({ onBack, navigate }) {
(data || []).forEach(h => {
let stopsList = [];
if (Array.isArray(h.stops)) {
stopsList = h.stops;
} else if (typeof h.stops === 'string') {
try { stopsList = JSON.parse(h.stops); } catch (e) {}
if (Array.isArray(h.gares_desservies)) {
stopsList = h.gares_desservies;
} else if (typeof h.gares_desservies === 'string') {
try { stopsList = JSON.parse(h.gares_desservies); } catch (e) {}
}
if (!Array.isArray(stopsList)) stopsList = [];
+7 -6
View File
@@ -1,6 +1,7 @@
import React, { useEffect, useState, useCallback } from 'react';
import './AFLArrivees.css';
import supabase from '../../../lib/supabase';
import { fetchAllRows } from '../../../lib/fetchAllRows';
import MarqueeText from '../../../components/MarqueeText';
import useAnnonceSonoreRealtime from '../../../hooks/useAnnonceSonoreRealtime';
import useAutoAnnonce from '../../../hooks/useAutoAnnonce';
@@ -586,7 +587,7 @@ export default function AFLArrivees(){
setError(null);
try {
const [{ data: horairesData, error: hError }, { data: perturbData, error: pError }, { data: travauxData, error: tError }, { data: travauxSubstData }] = await Promise.all([
supabase.from('horaires').select('id, *'),
fetchAllRows(supabase.from('horaires').select('*')),
supabase.from('perturbations').select('*'),
supabase.from('travaux').select('*'),
supabase.from('travaux_substitutions').select('*')
@@ -609,14 +610,14 @@ export default function AFLArrivees(){
// conserver la liste complète telle que renvoyée par la BDD (avant filtrage par gare)
let parsedHoraires = (horairesData || []).map(h => h || {});
// Certains champs (comme stops) peuvent arriver en tant que chaîne JSON depuis la BDD;
// essayer de parser pour garantir que stops est un tableau utilisable.
// Certains champs (comme gares_desservies) peuvent arriver en tant que chaîne JSON depuis la BDD;
// essayer de parser pour garantir que gares_desservies est un tableau utilisable.
parsedHoraires = parsedHoraires.map(h => {
try {
if (h && typeof h.stops === 'string') {
const parsed = JSON.parse(h.stops);
if (h && typeof h.gares_desservies === 'string') {
const parsed = JSON.parse(h.gares_desservies);
if (Array.isArray(parsed)) {
return { ...h, stops: parsed };
return { ...h, gares_desservies: parsed };
}
}
} catch (e) {
+5 -4
View File
@@ -1,5 +1,6 @@
import React, { useState, useEffect, useCallback } from 'react';
import supabase from '../../../lib/supabase';
import { fetchAllRows } from '../../../lib/fetchAllRows';
import './AFLArriveesQuais.css';
import useAnnonceSonoreRealtime from '../../../hooks/useAnnonceSonoreRealtime';
@@ -419,7 +420,7 @@ const AFLArriveesQuais = () => {
const fetchData = useCallback(async () => {
try {
const [{ data: horairesData }, { data: subData }, { data: perturbData }, { data: travauxData }] = await Promise.all([
supabase.from('horaires').select('*'),
fetchAllRows(supabase.from('horaires').select('*')),
supabase.from('travaux_substitutions').select('*'),
supabase.from('perturbations').select('*'),
supabase.from('travaux').select('*')
@@ -427,9 +428,9 @@ const AFLArriveesQuais = () => {
let parsedHoraires = (horairesData || []).map(h => {
try {
if (h && typeof h.stops === 'string') {
const parsed = JSON.parse(h.stops);
if (Array.isArray(parsed)) return { ...h, stops: parsed };
if (h && typeof h.gares_desservies === 'string') {
const parsed = JSON.parse(h.gares_desservies);
if (Array.isArray(parsed)) return { ...h, gares_desservies: parsed };
}
} catch (e) {}
return h || {};
+7 -6
View File
@@ -1,6 +1,7 @@
import React, { useEffect, useState, useCallback } from 'react';
import './AFLDeparts.css';
import supabase from '../../../lib/supabase';
import { fetchAllRows } from '../../../lib/fetchAllRows';
import MarqueeText from '../../../components/MarqueeText';
import useAnnonceSonoreRealtime from '../../../hooks/useAnnonceSonoreRealtime';
import useAutoAnnonce from '../../../hooks/useAutoAnnonce';
@@ -586,7 +587,7 @@ export default function AFLDeparts(){
setError(null);
try {
const [{ data: horairesData, error: hError }, { data: perturbData, error: pError }, { data: travauxData, error: tError }, { data: travauxSubstData }] = await Promise.all([
supabase.from('horaires').select('id, *'),
fetchAllRows(supabase.from('horaires').select('*')),
supabase.from('perturbations').select('*'),
supabase.from('travaux').select('*'),
supabase.from('travaux_substitutions').select('*')
@@ -609,14 +610,14 @@ export default function AFLDeparts(){
// conserver la liste complète telle que renvoyée par la BDD (avant filtrage par gare)
let parsedHoraires = (horairesData || []).map(h => h || {});
// Certains champs (comme stops) peuvent arriver en tant que chaîne JSON depuis la BDD;
// essayer de parser pour garantir que stops est un tableau utilisable.
// Certains champs (comme gares_desservies) peuvent arriver en tant que chaîne JSON depuis la BDD;
// essayer de parser pour garantir que gares_desservies est un tableau utilisable.
parsedHoraires = parsedHoraires.map(h => {
try {
if (h && typeof h.stops === 'string') {
const parsed = JSON.parse(h.stops);
if (h && typeof h.gares_desservies === 'string') {
const parsed = JSON.parse(h.gares_desservies);
if (Array.isArray(parsed)) {
return { ...h, stops: parsed };
return { ...h, gares_desservies: parsed };
}
}
} catch (e) {
+5 -4
View File
@@ -1,5 +1,6 @@
import React, { useState, useEffect, useCallback } from 'react';
import supabase from '../../../lib/supabase';
import { fetchAllRows } from '../../../lib/fetchAllRows';
import './AFLDepartsQuais.css';
import useAnnonceSonoreRealtime from '../../../hooks/useAnnonceSonoreRealtime';
@@ -420,7 +421,7 @@ const AFLDepartsQuais = () => {
const fetchData = useCallback(async () => {
try {
const [{ data: horairesData }, { data: subData }, { data: perturbData }, { data: travauxData }] = await Promise.all([
supabase.from('horaires').select('*'),
fetchAllRows(supabase.from('horaires').select('*')),
supabase.from('travaux_substitutions').select('*'),
supabase.from('perturbations').select('*'),
supabase.from('travaux').select('*')
@@ -428,9 +429,9 @@ const AFLDepartsQuais = () => {
let parsedHoraires = (horairesData || []).map(h => {
try {
if (h && typeof h.stops === 'string') {
const parsed = JSON.parse(h.stops);
if (Array.isArray(parsed)) return { ...h, stops: parsed };
if (h && typeof h.gares_desservies === 'string') {
const parsed = JSON.parse(h.gares_desservies);
if (Array.isArray(parsed)) return { ...h, gares_desservies: parsed };
}
} catch (e) {}
return h || {};
@@ -5,6 +5,7 @@ import Logo from '../../../components/Logo';
import MarqueeText from '../../../components/MarqueeText';
import useAnnonceSonoreRealtime from '../../../hooks/useAnnonceSonoreRealtime';
import useAutoAnnonce from '../../../hooks/useAutoAnnonce';
import { fetchAllRows } from '../../../lib/fetchAllRows';
// Helper utilitaire : format HHhMM sans secondes (ex: 06h44)
const formatTimeNoSeconds = (v) => {
@@ -625,9 +626,7 @@ export default function ClassiquesArrivees(){
setError(null);
try {
const [{ data: horairesData, error: hError }, { data: perturbData, error: pError }, { data: travauxData, error: tError }] = await Promise.all([
// DEBUG: récupérer explicitement l'id et toutes les colonnes pour s'assurer que l'identifiant
// `id` est bien présent et peut être mis en correspondance avec `horaire_id` dans perturbations
supabase.from('horaires').select('id, *'),
fetchAllRows(supabase.from('horaires').select('*')),
supabase.from('perturbations').select('*'),
supabase.from('travaux').select('*')
]);
@@ -649,14 +648,13 @@ export default function ClassiquesArrivees(){
// conserver la liste complète telle que renvoyée par la BDD (avant filtrage par gare)
let parsedHoraires = (horairesData || []).map(h => h || {});
// Certains champs (comme stops) peuvent arriver en tant que chaîne JSON depuis la BDD;
// essayer de parser pour garantir que stops est un tableau utilisable.
// Parser gares_desservies si c'est une chaîne JSON
parsedHoraires = parsedHoraires.map(h => {
try {
if (h && typeof h.stops === 'string') {
const parsed = JSON.parse(h.stops);
if (h && typeof h.gares_desservies === 'string') {
const parsed = JSON.parse(h.gares_desservies);
if (Array.isArray(parsed)) {
return { ...h, stops: parsed };
return { ...h, gares_desservies: parsed };
}
}
} catch (e) {
@@ -5,6 +5,7 @@ import Logo from '../../../components/Logo';
import MarqueeText from '../../../components/MarqueeText';
import useAnnonceSonoreRealtime from '../../../hooks/useAnnonceSonoreRealtime';
import useAutoAnnonce from '../../../hooks/useAutoAnnonce';
import { fetchAllRows } from '../../../lib/fetchAllRows';
// Helper utilitaire : format HHhMM sans secondes (ex: 06h44)
const formatTimeNoSeconds = (v) => {
@@ -625,9 +626,7 @@ export default function ClassiquesDeparts(){
setError(null);
try {
const [{ data: horairesData, error: hError }, { data: perturbData, error: pError }, { data: travauxData, error: tError }] = await Promise.all([
// DEBUG: récupérer explicitement l'id et toutes les colonnes pour s'assurer que l'identifiant
// `id` est bien présent et peut être mis en correspondance avec `horaire_id` dans perturbations
supabase.from('horaires').select('id, *'),
fetchAllRows(supabase.from('horaires').select('*')),
supabase.from('perturbations').select('*'),
supabase.from('travaux').select('*')
]);
@@ -649,14 +648,13 @@ export default function ClassiquesDeparts(){
// conserver la liste complète telle que renvoyée par la BDD (avant filtrage par gare)
let parsedHoraires = (horairesData || []).map(h => h || {});
// Certains champs (comme stops) peuvent arriver en tant que chaîne JSON depuis la BDD;
// essayer de parser pour garantir que stops est un tableau utilisable.
// Parser gares_desservies si c'est une chaîne JSON
parsedHoraires = parsedHoraires.map(h => {
try {
if (h && typeof h.stops === 'string') {
const parsed = JSON.parse(h.stops);
if (h && typeof h.gares_desservies === 'string') {
const parsed = JSON.parse(h.gares_desservies);
if (Array.isArray(parsed)) {
return { ...h, stops: parsed };
return { ...h, gares_desservies: parsed };
}
}
} catch (e) {
@@ -1,6 +1,7 @@
import React, { useEffect, useState, useCallback } from 'react';
import './ClassiquesArriveesQuais.css';
import supabase from '../../../../lib/supabase';
import { fetchAllRows } from '../../../../lib/fetchAllRows';
import useAnnonceSonoreRealtime from '../../../../hooks/useAnnonceSonoreRealtime';
import useAutoAnnonce from '../../../../hooks/useAutoAnnonce';
function Clock() {
@@ -490,7 +491,7 @@ export default function ClassiquesArriveesQuais() {
}
} catch (e) {}
const { data: all, error } = await supabase.from('horaires').select('id, *');
const { data: all, error } = await fetchAllRows(supabase.from('horaires').select('*'));
if (error) {
if (mounted) setHoraireError(error.message || String(error));
setLoadingHoraire(false);
@@ -784,8 +785,8 @@ export default function ClassiquesArriveesQuais() {
const horaire = selectedHoraire && selectedHoraire.horaire ? selectedHoraire.horaire : null;
let stops = horaire ? getStops(horaire) : [];
if (!Array.isArray(stops) || stops.length === 0) {
if (horaire && typeof horaire.stops === 'string') {
try { const p = JSON.parse(horaire.stops); if (Array.isArray(p)) stops = p; } catch (e) { /* ignore */ }
if (horaire && typeof horaire.gares_desservies === 'string') {
try { const p = JSON.parse(horaire.gares_desservies); if (Array.isArray(p)) stops = p; } catch (e) { /* ignore */ }
}
}
if (!Array.isArray(stops) || stops.length === 0) stops = mock.stops || [];
@@ -1,6 +1,7 @@
import React, { useEffect, useState, useCallback } from 'react';
import './ClassiquesDepartsQuais.css';
import supabase from '../../../../lib/supabase';
import { fetchAllRows } from '../../../../lib/fetchAllRows';
import useAnnonceSonoreRealtime from '../../../../hooks/useAnnonceSonoreRealtime';
import useAutoAnnonce from '../../../../hooks/useAutoAnnonce';
function Clock() {
@@ -490,7 +491,7 @@ export default function ClassiquesDepartsQuais() {
}
} catch (e) {}
const { data: all, error } = await supabase.from('horaires').select('id, *');
const { data: all, error } = await fetchAllRows(supabase.from('horaires').select('*'));
if (error) {
if (mounted) setHoraireError(error.message || String(error));
setLoadingHoraire(false);
@@ -781,8 +782,8 @@ export default function ClassiquesDepartsQuais() {
const horaire = selectedHoraire && selectedHoraire.horaire ? selectedHoraire.horaire : null;
let stops = horaire ? getStops(horaire) : [];
if (!Array.isArray(stops) || stops.length === 0) {
if (horaire && typeof horaire.stops === 'string') {
try { const p = JSON.parse(horaire.stops); if (Array.isArray(p)) stops = p; } catch (e) { /* ignore */ }
if (horaire && typeof horaire.gares_desservies === 'string') {
try { const p = JSON.parse(horaire.gares_desservies); if (Array.isArray(p)) stops = p; } catch (e) { /* ignore */ }
}
}
if (!Array.isArray(stops) || stops.length === 0) stops = mock.stops || [];