Implement geolocation features and enhance UI for location handling
This commit is contained in:
@@ -19,6 +19,9 @@ RUN npm run build -- --configuration production
|
|||||||
# Etapa de producción con nginx
|
# Etapa de producción con nginx
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Instala curl
|
||||||
|
RUN apk add --no-cache curl
|
||||||
|
|
||||||
# Copiar configuración personalizada de nginx
|
# Copiar configuración personalizada de nginx
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
<div class="weather-app">
|
<div class="weather-app">
|
||||||
@if (loading()) {
|
@if (gettingLocation()) {
|
||||||
|
<div class="loading">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>📍 Obtenint la teva ubicació...</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (locationDenied() && !loading()) {
|
||||||
|
<div class="location-denied">
|
||||||
|
<p>⚠️ Permisos d'ubicació denegats. Mostrant el temps de Lliçà d'Amunt.</p>
|
||||||
|
<button class="location-button" (click)="requestLocation()">
|
||||||
|
📍 Activar ubicació GPS
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (loading() && !gettingLocation()) {
|
||||||
<div class="loading">
|
<div class="loading">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>Carregant informació meteorològica...</p>
|
<p>Carregant informació meteorològica...</p>
|
||||||
|
|||||||
@@ -15,6 +15,46 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.location-denied {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(255, 152, 0, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
padding: 16px;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-button {
|
||||||
|
padding: 8px 20px;
|
||||||
|
background: white;
|
||||||
|
color: #ff9800;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.spinner {
|
.spinner {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import { PrediccioHorariaComponent } from '../prediccio-horaria/prediccio-horari
|
|||||||
styleUrl: './home.scss',
|
styleUrl: './home.scss',
|
||||||
})
|
})
|
||||||
export class Home implements OnInit {
|
export class Home implements OnInit {
|
||||||
// Lliçà d'Amunt por defecto
|
// Municipio dinámico según GPS
|
||||||
readonly municipiCodi = '081075';
|
municipiCodi = signal<string>('081075'); // Lliçà d'Amunt por defecto
|
||||||
|
|
||||||
municipi = signal<Municipi | null>(null);
|
municipi = signal<Municipi | null>(null);
|
||||||
prediccioDiaria = signal<PrediccioDiaria[]>([]);
|
prediccioDiaria = signal<PrediccioDiaria[]>([]);
|
||||||
@@ -25,19 +25,69 @@ export class Home implements OnInit {
|
|||||||
|
|
||||||
loading = signal(true);
|
loading = signal(true);
|
||||||
error = signal<string | null>(null);
|
error = signal<string | null>(null);
|
||||||
|
gettingLocation = signal(false);
|
||||||
|
locationDenied = signal(false);
|
||||||
|
|
||||||
constructor(private meteocatService: MeteocatService) {}
|
constructor(private meteocatService: MeteocatService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.loadWeatherData();
|
this.getCurrentLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadWeatherData(): void {
|
getCurrentLocation(): void {
|
||||||
|
if (!navigator.geolocation) {
|
||||||
|
console.warn('Geolocalización no disponible, usando municipio por defecto');
|
||||||
|
this.loadWeatherData(this.municipiCodi());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gettingLocation.set(true);
|
||||||
|
this.loading.set(true);
|
||||||
|
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(position) => {
|
||||||
|
const lat = position.coords.latitude;
|
||||||
|
const lon = position.coords.longitude;
|
||||||
|
|
||||||
|
console.log(`Ubicación GPS obtenida: ${lat}, ${lon}`);
|
||||||
|
|
||||||
|
// Buscar municipio más cercano
|
||||||
|
this.meteocatService.getMunicipiCercano(lat, lon).subscribe({
|
||||||
|
next: (municipi) => {
|
||||||
|
console.log(`Municipio más cercano: ${municipi.nom} (${municipi.codi})`);
|
||||||
|
this.municipiCodi.set(municipi.codi);
|
||||||
|
this.gettingLocation.set(false);
|
||||||
|
this.loadWeatherData(municipi.codi);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Error al buscar municipio cercano:', err);
|
||||||
|
this.gettingLocation.set(false);
|
||||||
|
// Usar municipio por defecto si falla
|
||||||
|
this.loadWeatherData(this.municipiCodi());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.warn('Error obteniendo ubicación:', error.message);
|
||||||
|
this.gettingLocation.set(false);
|
||||||
|
this.locationDenied.set(true);
|
||||||
|
// Usar municipio por defecto si se deniega el permiso
|
||||||
|
this.loadWeatherData(this.municipiCodi());
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
timeout: 10000,
|
||||||
|
maximumAge: 300000 // Cache de 5 minutos
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadWeatherData(codiMunicipi: string): void {
|
||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
this.error.set(null);
|
this.error.set(null);
|
||||||
|
|
||||||
// Cargar municipio
|
// Cargar municipio
|
||||||
this.meteocatService.getMunicipiByCode(this.municipiCodi).subscribe({
|
this.meteocatService.getMunicipiByCode(codiMunicipi).subscribe({
|
||||||
next: (municipi) => {
|
next: (municipi) => {
|
||||||
this.municipi.set(municipi);
|
this.municipi.set(municipi);
|
||||||
},
|
},
|
||||||
@@ -48,7 +98,7 @@ export class Home implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Cargar predicción diaria
|
// Cargar predicción diaria
|
||||||
this.meteocatService.getPrediccioDiaria(this.municipiCodi).subscribe({
|
this.meteocatService.getPrediccioDiaria(codiMunicipi).subscribe({
|
||||||
next: (prediccions) => {
|
next: (prediccions) => {
|
||||||
this.prediccioDiaria.set(prediccions);
|
this.prediccioDiaria.set(prediccions);
|
||||||
},
|
},
|
||||||
@@ -59,7 +109,7 @@ export class Home implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Cargar predicción horaria
|
// Cargar predicción horaria
|
||||||
this.meteocatService.getPrediccioHoraria(this.municipiCodi).subscribe({
|
this.meteocatService.getPrediccioHoraria(codiMunicipi).subscribe({
|
||||||
next: (prediccions) => {
|
next: (prediccions) => {
|
||||||
// Filtrar solo predicciones desde la hora actual y limitamos a 48 horas
|
// Filtrar solo predicciones desde la hora actual y limitamos a 48 horas
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -81,7 +131,7 @@ export class Home implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Cargar estación XEMA más cercana
|
// Cargar estación XEMA más cercana
|
||||||
this.meteocatService.getEstacioCercana(this.municipiCodi).subscribe({
|
this.meteocatService.getEstacioCercana(codiMunicipi).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
this.estacioInfo.set(response);
|
this.estacioInfo.set(response);
|
||||||
},
|
},
|
||||||
@@ -111,6 +161,13 @@ export class Home implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refresh(): void {
|
refresh(): void {
|
||||||
this.loadWeatherData();
|
// Recargar datos con el municipio actual
|
||||||
|
this.loadWeatherData(this.municipiCodi());
|
||||||
|
}
|
||||||
|
|
||||||
|
requestLocation(): void {
|
||||||
|
// Solicitar ubicación nuevamente
|
||||||
|
this.locationDenied.set(false);
|
||||||
|
this.getCurrentLocation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ export class MeteocatService {
|
|||||||
return this.http.get<Municipi[]>(`${this.apiUrl}/municipis/search`, { params });
|
return this.http.get<Municipi[]>(`${this.apiUrl}/municipis/search`, { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMunicipiCercano(lat: number, lon: number): Observable<Municipi> {
|
||||||
|
const params = new HttpParams()
|
||||||
|
.set('lat', lat.toString())
|
||||||
|
.set('lon', lon.toString());
|
||||||
|
return this.http.get<Municipi>(`${this.apiUrl}/municipis/cercano`, { params });
|
||||||
|
}
|
||||||
|
|
||||||
// === PREDICCIONS ===
|
// === PREDICCIONS ===
|
||||||
|
|
||||||
getPrediccioDiaria(municipiCodi: string): Observable<PrediccioDiaria[]> {
|
getPrediccioDiaria(municipiCodi: string): Observable<PrediccioDiaria[]> {
|
||||||
|
|||||||
Reference in New Issue
Block a user