diff --git a/src/app/components/import/import.component.css b/src/app/components/import/import.component.css new file mode 100644 index 0000000..8630c5b --- /dev/null +++ b/src/app/components/import/import.component.css @@ -0,0 +1,82 @@ +.import-container { + padding: 20px; + max-width: 1200px; + margin: 0 auto; +} + +.full-width { + width: 100%; +} + +.button-container { + display: flex; + justify-content: flex-end; + margin: 20px 0; + gap: 10px; +} + +.mapping-container { + margin-top: 30px; + padding: 20px; + background-color: #f5f5f5; + border-radius: 4px; +} + +.mapping-description { + color: #666; + margin-bottom: 20px; +} + +.mapping-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.mapping-row { + display: flex; + flex-direction: column; + gap: 8px; +} + +.field-label { + font-size: 14px; + color: #333; +} + +.preview-section { + margin-top: 30px; + padding: 20px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; +} + +.preview-section h3 { + margin-top: 0; + margin-bottom: 15px; +} + +.preview-section pre { + background-color: #f8f8f8; + padding: 15px; + border-radius: 4px; + overflow-x: auto; + font-size: 12px; +} + +mat-spinner { + display: inline-block; + margin-right: 8px; +} + +.error-snackbar { + background: #f44336; + color: white; +} + +.success-snackbar { + background: #4caf50; + color: white; +} \ No newline at end of file diff --git a/src/app/components/import/import.component.html b/src/app/components/import/import.component.html new file mode 100644 index 0000000..0b85de3 --- /dev/null +++ b/src/app/components/import/import.component.html @@ -0,0 +1,84 @@ +
+
+
+

Import de données

+

Importez des données depuis une API Geoapify

+
+ +
+
+ + +
+ L'URL est requise +
+
+ L'URL doit commencer par http:// ou https:// +
+
+ +
+ +
+
+ +
+
+

Correspondance des champs

+

+ Sélectionnez les champs de l'API qui correspondent à vos champs système +

+ +
+
+ + +
+
+
+ +
+

Aperçu des données

+
{{data.features[0] | json}}
+
+ +
+ +
+
+
+
\ No newline at end of file diff --git a/src/app/components/import/import.component.ts b/src/app/components/import/import.component.ts new file mode 100644 index 0000000..351f9f7 --- /dev/null +++ b/src/app/components/import/import.component.ts @@ -0,0 +1,129 @@ +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { ImportService, GeoapifyResponse, GeoapifyFeature } from '../../services/import.service'; +import { CommonModule } from '@angular/common'; +import { POIService } from '../../services/poi.service'; +import { POI } from '../../model/POI'; + +@Component({ + selector: 'app-import', + templateUrl: './import.component.html', + styleUrls: ['./import.component.css'], + imports: [CommonModule, ReactiveFormsModule] +}) +export class ImportComponent { + importForm: FormGroup; + isLoading = false; + data: GeoapifyResponse | null = null; + fieldMappings: { [key: string]: string } = {}; + availableFields: string[] = []; + targetFields = [ + 'title', + 'description', + 'complete_address', + 'latitude', + 'longitude' + ]; + + constructor( + private fb: FormBuilder, + private importService: ImportService, + private poiService: POIService + ) { + this.importForm = this.fb.group({ + apiUrl: ['', [Validators.required, Validators.pattern('^https?://.+')]] + }); + } + + onSubmit(): void { + if (this.importForm.valid) { + this.isLoading = true; + const url = this.importForm.get('apiUrl')?.value; + + this.importService.fetchDataFromUrl(url).subscribe({ + next: (response) => { + if (this.importService.validateGeoapifyData(response)) { + this.data = response; + this.extractAvailableFields(response.features[0]); + this.initializeFieldMappings(); + } else { + this.showError('Format de données invalide'); + } + this.isLoading = false; + }, + error: (error) => { + this.showError(error.message); + this.isLoading = false; + } + }); + } + } + + private extractAvailableFields(feature: GeoapifyFeature): void { + this.availableFields = Object.keys(feature.properties); + } + + private initializeFieldMappings(): void { + this.fieldMappings = {}; + this.targetFields.forEach(field => { + this.fieldMappings[field] = ''; + }); + } + + updateFieldMapping(targetField: string, sourceField: string): void { + this.fieldMappings[targetField] = sourceField; + } + + processImport(): void { + if (!this.data) return; + + this.isLoading = true; + let successCount = 0; + let errorCount = 0; + + // Traiter chaque feature + this.data.features.forEach(feature => { + const poi = this.importService.convertToPOI(feature, this.fieldMappings); + + this.poiService.addPOI(poi).subscribe({ + next: () => { + successCount++; + if (successCount + errorCount === this.data!.features.length) { + this.showSuccess(`${successCount} POIs importés avec succès`); + this.isLoading = false; + } + }, + error: (error) => { + console.error('Erreur lors de l\'import du POI:', error); + errorCount++; + if (successCount + errorCount === this.data!.features.length) { + this.showError(`${errorCount} erreurs lors de l'import`); + this.isLoading = false; + } + } + }); + }); + } + + private showError(message: string): void { + const notification = document.createElement('div'); + notification.className = 'fixed top-4 right-4 bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg z-50'; + notification.textContent = message; + document.body.appendChild(notification); + + setTimeout(() => { + notification.remove(); + }, 5000); + } + + private showSuccess(message: string): void { + const notification = document.createElement('div'); + notification.className = 'fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg z-50'; + notification.textContent = message; + document.body.appendChild(notification); + + setTimeout(() => { + notification.remove(); + }, 5000); + } +} \ No newline at end of file