Primer commit de mi proyecto React

This commit is contained in:
Meinar00
2025-02-13 01:22:17 +01:00
commit c469b99bcc
59 changed files with 16597 additions and 0 deletions

67
src/App.css Normal file
View File

@@ -0,0 +1,67 @@
/* Estilos generales */
body {
font-family: sans-serif;
transition: background-color 0.3s ease; /* Transición para el cambio de tema */
}
.container {
margin-top: 50px;
display: flex;
flex-direction: column;
align-items: center; /* Centrar horizontalmente */
}
h1 {
color: #343a40; /* Color de texto oscuro */
margin-bottom: 30px;
}
/* Estilos para los selectores */
label {
font-weight: bold;
margin-bottom: 5px;
}
select {
width: 100%;
padding: 8px;
margin-bottom: 15px;
border: 1px solid #ced4da;
border-radius: 4px;
box-sizing: border-box;
}
/* Estilos para los radio buttons */
.form-check-input {
margin-right: 5px;
}
.form-check-label {
margin-right: 15px;
}
/* Estilos para el botón */
button {
background-color: #007bff; /* Color de fondo azul */
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0069d9; /* Color de fondo azul más oscuro al pasar el mouse */
}
/* Estilos para el tema claro */
body.light-theme {
background-color: #f8f9fa; /* Color de fondo claro */
color: #343a40; /* Color de texto oscuro */
}
/* Estilos para el tema oscuro */
body.dark-theme {
background-color: #343a40; /* Color de fondo oscuro */
color: #f8f9fa; /* Color de texto claro */
}

202
src/App.js Normal file
View File

@@ -0,0 +1,202 @@
import React, { useState, useEffect, useRef } from 'react';
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
function App() {
const [lineaSeleccionada, setLineaSeleccionada] = useState('');
const [paradaSeleccionada, setParadaSeleccionada] = useState('');
const [tipoParada, setTipoParada] = useState('actual');
const [audio, setAudio] = useState(null);
const [volume, setVolume] = useState(1);
const audioRef = useRef(null);
const [theme, setTheme] = useState('light');
const datos = {
"Línea 1": [
"Zafra",
"Julio Caro Baroja (Aqualon)",
"Avda. Alemania (Esquina Ruiz de Alda)",
"Avda. Alemania (Plaza de Toros)",
"Bda. Navidad",
"Don Bosco",
"Plaza de los Dolores",
"Barriada del Carmen",
"Humilladero",
"Cardeñas",
"Orden Baja",
"Gonzalo de Berceo",
"Plaza Niño Miguel",
"Magnolia",
"Hospital JRJ",
"Plaza las Amapolas",
"Av. Andalucia (Castaño Robledo)",
"Monumento al Fútbol",
"Relaciones Laborales (Universidad)",
"Vista Alegre-Universidad",
"Centro Comercial Holea",
"Cruce Romeralejo",
"Universidad. Avenida de las Artes",
"Palacio de Deportes",
"Higueral (Fuerzas Armadas)",
"Bda. José Antonio",
"Isla Chica",
"Las Delicias",
"El Árbol",
"Gasolinera",
"Estación de Ferrocarril",
"El punto",
"Estación de Sevilla",
"Nuevo Mercado"
],
"Línea 5000": ["Puente de Vallecas"]
};
const handleLineaChange = (event) => {
setLineaSeleccionada(event.target.value);
setParadaSeleccionada('');
};
const handleParadaChange = (event) => {
setParadaSeleccionada(event.target.value);
};
const handleTipoParadaChange = (event) => {
setTipoParada(event.target.value);
};
const handleVolumeChange = (event) => {
setVolume(event.target.value);
if (audioRef.current) {
audioRef.current.volume = event.target.value;
}
};
const reproducirAudio = () => {
if (audio) {
audio.pause();
}
const playAudio = (audioFile) => {
const nuevoAudio = new Audio(audioFile);
nuevoAudio.play().catch(error => {
console.error("Error al reproducir audio:", error);
});
return nuevoAudio;
};
// Play "parada_actual" or "parada_siguiente" FIRST
const paradaTipoAudio = playAudio(`/audio/parada_${tipoParada}.wav`);
setAudio(paradaTipoAudio); // Update state immediately
audioRef.current = paradaTipoAudio; // Set audio ref
paradaTipoAudio.volume = volume; // Set volume
// Then, if a specific stop is selected, play that audio AFTER
if (datos[lineaSeleccionada] && datos[lineaSeleccionada].includes(paradaSeleccionada)) {
paradaTipoAudio.onended = () => { // Play stop audio after type audio
let paradaAudioFile = "";
paradaAudioFile = `/audio/${paradaSeleccionada.toLowerCase().replace(/ /g, "_")}.wav`; // Dynamic file naming
const paradaAudio = playAudio(paradaAudioFile);
setAudio(paradaAudio); // Update state again
audioRef.current = paradaAudio; // Set audio ref
paradaAudio.volume = volume; // Set volume
};
}
};
useEffect(() => {
const cargarAudios = () => {
// Preload audio files (optional but recommended)
const audioActual = new Audio(`/audio/parada_actual.wav`);
const audioSiguiente = new Audio(`/audio/parada_siguiente.wav`);
// Dynamically preload audio files for each stop
datos["Línea 1"].forEach(parada => {
const audio = new Audio(`/audio/${parada.toLowerCase().replace(/ /g, "_")}.wav`);
});
const audioVallecas = new Audio(`/audio/vallecas.wav`); // Preload new audio file
};
cargarAudios();
}, []);
useEffect(() => {
localStorage.setItem('theme', theme);
document.body.classList.toggle('dark-theme', theme === 'dark');
document.body.classList.toggle('light-theme', theme === 'light');
}, [theme]);
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<div className="container">
<h1>Aplicación de Megafonía</h1>
<div>
<label htmlFor="linea">Línea:</label>
<select id="linea" value={lineaSeleccionada} onChange={handleLineaChange} className="form-select">
<option value="">Selecciona una línea</option>
{Object.keys(datos).map((linea) => (
<option key={linea} value={linea}>{linea}</option>
))}
</select>
</div>
<div>
<label htmlFor="parada">Parada:</label>
<select id="parada" value={paradaSeleccionada} onChange={handleParadaChange} className="form-select">
<option value="">Selecciona una parada</option>
{datos[lineaSeleccionada]?.map((parada) => (
<option key={parada} value={parada}>{parada}</option>
))}
</select>
</div>
<div className="d-flex flex-column">
<label>
<input
type="radio"
value="actual"
checked={tipoParada === 'actual'}
onChange={handleTipoParadaChange}
className="form-check-input"
/>
<span className="form-check-label">Parada actual</span>
</label>
<label>
<input
type="radio"
value="siguiente"
checked={tipoParada === 'siguiente'}
onChange={handleTipoParadaChange}
className="form-check-input"
/>
<span className="form-check-label">Parada siguiente</span>
</label>
</div>
<div>
<label htmlFor="volume">Volumen:</label>
<input
type="range"
id="volume"
min="0"
max="1"
step="0.1"
value={volume}
onChange={handleVolumeChange}
className="form-range"
/>
</div>
<button onClick={reproducirAudio} className="btn btn-primary">Reproducir</button>
<button onClick={toggleTheme} className="btn btn-secondary mt-3">
Cambiar tema a {theme === 'light' ? 'oscuro' : 'claro'}
</button>
</div>
);
}
export default App;

8
src/App.test.js Normal file
View File

@@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

13
src/index.css Normal file
View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

17
src/index.js Normal file
View File

@@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

1
src/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

13
src/reportWebVitals.js Normal file
View File

@@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

5
src/setupTests.js Normal file
View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';