Ingatlanok tulajdoni lapok és tulajdonosok táblázatos és térképi megjelenítése.
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s
This commit is contained in:
@@ -1,3 +1,263 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
class PropertyListController extends GetxController {}
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:terepi_seged/models/field_party.dart';
|
||||
import 'package:terepi_seged/models/field_property.dart';
|
||||
import 'package:terepi_seged/pages/property_list/presentations/views/parcel_map_view.dart';
|
||||
import 'package:terepi_seged/services/field_property_service.dart';
|
||||
import 'package:terepi_seged/services/parcel_geometry_service.dart';
|
||||
import 'package:terepi_seged/services/project_service.dart';
|
||||
|
||||
enum FieldViewMode { properties, parties }
|
||||
|
||||
class PropertyListController extends GetxController {
|
||||
static PropertyListController get to => Get.find();
|
||||
|
||||
final viewMode = FieldViewMode.properties.obs;
|
||||
|
||||
// ── Ingatlanok fül ───────────────────────────────────────────────
|
||||
final searchText = ''.obs;
|
||||
final settlementFilter = Rxn<String>();
|
||||
final mailingStatusFilter = Rxn<String>();
|
||||
final isLoading = false.obs;
|
||||
final properties = <FieldProperty>[].obs;
|
||||
final settlements = <String>[].obs;
|
||||
|
||||
/// Ha nem null, a lista egy KIVÁLASZTOTT tulajdonos ingatlanjait
|
||||
/// mutatja, nem az általános keresést/szűrést.
|
||||
final selectedParty = Rxn<FieldParty>();
|
||||
|
||||
// ── Tulajdonosok fül ─────────────────────────────────────────────
|
||||
final partySearchText = ''.obs;
|
||||
final isLoadingParties = false.obs;
|
||||
final parties = <FieldParty>[].obs;
|
||||
|
||||
Timer? _propertyDebounce;
|
||||
Timer? _partyDebounce;
|
||||
final partySearchController = TextEditingController();
|
||||
|
||||
@override
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
loadProperties();
|
||||
searchParties(); // a tulajdonos-lista is azonnal töltődjön, ne csak gépelésre
|
||||
ever(ProjectService.to.activeProject, (_) {
|
||||
selectedParty.value = null;
|
||||
loadProperties();
|
||||
searchParties();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_propertyDebounce?.cancel();
|
||||
_partyDebounce?.cancel();
|
||||
partySearchController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
// ── Ingatlan-keresés/szűrés ──────────────────────────────────────
|
||||
|
||||
void onSearchChanged(String v) {
|
||||
searchText.value = v;
|
||||
_propertyDebounce?.cancel();
|
||||
_propertyDebounce =
|
||||
Timer(const Duration(milliseconds: 400), loadProperties);
|
||||
}
|
||||
|
||||
void setSettlementFilter(String? v) {
|
||||
settlementFilter.value = v;
|
||||
loadProperties();
|
||||
}
|
||||
|
||||
void setMailingStatusFilter(String? v) {
|
||||
mailingStatusFilter.value = v;
|
||||
loadProperties();
|
||||
}
|
||||
|
||||
Future<void> loadProperties() async {
|
||||
// Ha épp egy tulajdonosra van szűkítve a nézet, a sima keresés ne
|
||||
// írja felül azt — a felhasználó explicit törli (clearPartySelection).
|
||||
if (selectedParty.value != null) return;
|
||||
|
||||
final projectId = ProjectService.to.activeProject.value?.uuid;
|
||||
if (projectId == null) {
|
||||
properties.clear();
|
||||
settlements.clear();
|
||||
return;
|
||||
}
|
||||
isLoading.value = true;
|
||||
try {
|
||||
final list = await FieldPropertyService.to.listProperties(
|
||||
projectId: projectId,
|
||||
search: searchText.value,
|
||||
settlement: settlementFilter.value,
|
||||
mailingStatus: mailingStatusFilter.value,
|
||||
);
|
||||
properties.value = list;
|
||||
// A település-szűrő lista a TELJES (szűretlen) körből épülne fel
|
||||
// igazán jól — egyszerűség kedvéért most a jelenlegi találatokból
|
||||
// számoljuk, ami induláskor (szűrés nélkül) helyes.
|
||||
if (settlementFilter.value == null) {
|
||||
settlements.value = list.map((p) => p.settlement).toSet().toList()
|
||||
..sort();
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar('Hiba', 'Nem sikerült betölteni az ingatlanokat: $e',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tulajdonos-keresés ───────────────────────────────────────────
|
||||
|
||||
void onPartySearchChanged(String v) {
|
||||
partySearchText.value = v;
|
||||
_partyDebounce?.cancel();
|
||||
_partyDebounce = Timer(const Duration(milliseconds: 400), searchParties);
|
||||
}
|
||||
|
||||
Future<void> searchParties() async {
|
||||
final projectId = ProjectService.to.activeProject.value?.uuid;
|
||||
if (projectId == null) return;
|
||||
isLoadingParties.value = true;
|
||||
try {
|
||||
parties.value = await FieldPropertyService.to.searchParties(
|
||||
projectId: projectId,
|
||||
search: partySearchText.value,
|
||||
);
|
||||
} catch (e) {
|
||||
Get.snackbar('Hiba', 'Nem sikerült betölteni a tulajdonosokat: $e',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
} finally {
|
||||
isLoadingParties.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Determinisztikus törlés — nem várja meg a debounce-ot, azonnal
|
||||
/// visszahozza a teljes (szűretlen) listát.
|
||||
void clearPartySearch() {
|
||||
partySearchController.clear();
|
||||
partySearchText.value = '';
|
||||
_partyDebounce?.cancel();
|
||||
searchParties();
|
||||
}
|
||||
|
||||
/// Egy tulajdonos kiválasztása → átvált az Ingatlanok fülre, az ő
|
||||
/// ingatlanjaival.
|
||||
Future<void> selectParty(FieldParty party) async {
|
||||
selectedParty.value = party;
|
||||
viewMode.value = FieldViewMode.properties;
|
||||
isLoading.value = true;
|
||||
try {
|
||||
properties.value =
|
||||
await FieldPropertyService.to.listPropertiesForParty(party.id);
|
||||
} catch (e) {
|
||||
Get.snackbar('Hiba', 'Nem sikerült betölteni az ingatlanokat: $e',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void clearPartySelection() {
|
||||
selectedParty.value = null;
|
||||
viewMode.value = FieldViewMode.parties;
|
||||
loadProperties();
|
||||
// A korábban a tulajdonos MEGTALÁLÁSÁHOZ begépelt keresőszöveg is
|
||||
// törlődjön — különben a Tulajdonosok fülre visszaváltva a lista
|
||||
// továbbra is arra az egy szűrt találatra maradna korlátozva.
|
||||
clearPartySearch();
|
||||
}
|
||||
|
||||
// ── GeoJSON import (geometria-katalógus) ────────────────────────────
|
||||
|
||||
Future<void> importGeoJsonFile() async {
|
||||
// FONTOS: nem FileType.custom + allowedExtensions — a .geojson nincs
|
||||
// benne Android beépített MIME-táblázatában, emiatt felhős forrásoknál
|
||||
// (Google Drive stb.) a fájlok szürkén, kiválaszthatatlanul jelennek
|
||||
// meg. Helyette mindent engedünk, és utólag, kézzel ellenőrizzük a
|
||||
// kiterjesztést.
|
||||
final result = await FilePicker.platform.pickFiles(type: FileType.any);
|
||||
if (result == null || result.files.single.path == null) return;
|
||||
|
||||
final pickedName = result.files.single.name.toLowerCase();
|
||||
if (!pickedName.endsWith('.geojson') && !pickedName.endsWith('.json')) {
|
||||
Get.snackbar('Nem támogatott fájl',
|
||||
'Válassz .geojson vagy .json kiterjesztésű fájlt.',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
return;
|
||||
}
|
||||
|
||||
final file = File(result.files.single.path!);
|
||||
final content = await file.readAsString();
|
||||
|
||||
Get.dialog(const Center(child: CircularProgressIndicator()),
|
||||
barrierDismissible: false);
|
||||
try {
|
||||
final projectId = ProjectService.to.activeProject.value?.uuid;
|
||||
if (projectId == null) {
|
||||
Get.back();
|
||||
Get.snackbar('Nincs aktív projekt',
|
||||
'Válassz aktív projektet az importálás előtt.',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
return;
|
||||
}
|
||||
final res = await ParcelGeometryService.to.importGeoJson(content,
|
||||
projectId: projectId, sourceFileName: result.files.single.name);
|
||||
Get.back(); // töltő-dialógus bezárása
|
||||
if (res.skipped.isNotEmpty) {
|
||||
Get.dialog(AlertDialog(
|
||||
title: Text(
|
||||
'Import: ${res.imported} sikeres, ${res.skipped.length} kihagyva'),
|
||||
content: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: res.skipped
|
||||
.take(20)
|
||||
.map(
|
||||
(s) => Text('• $s', style: const TextStyle(fontSize: 12)))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: Get.back, child: const Text('Rendben')),
|
||||
],
|
||||
));
|
||||
} else {
|
||||
Get.snackbar('Import kész', '${res.imported} parcella importálva.',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
} catch (e) {
|
||||
Get.back();
|
||||
Get.snackbar('Import hiba', '$e', snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Térképi megjelenítés ─────────────────────────────────────────
|
||||
|
||||
Future<void> viewOnMap({
|
||||
required String title,
|
||||
required List<FieldProperty> targetProperties,
|
||||
}) async {
|
||||
if (targetProperties.isEmpty) return;
|
||||
Get.dialog(const Center(child: CircularProgressIndicator()),
|
||||
barrierDismissible: false);
|
||||
try {
|
||||
final rows =
|
||||
await ParcelGeometryService.to.fetchGeometriesFor(targetProperties);
|
||||
Get.back();
|
||||
Get.to(() => ParcelMapView(title: title, geometryRows: rows));
|
||||
} catch (e) {
|
||||
Get.back();
|
||||
Get.snackbar('Hiba', 'Nem sikerült betölteni a térképi adatot: $e',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user