vor Import/Export
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
// lib/features/igel/data/igel_repository.dart
|
||||
import '../../../shared/api_client.dart';
|
||||
import '../domain/igel.dart';
|
||||
|
||||
@@ -12,24 +13,27 @@ class IgelRepository {
|
||||
}
|
||||
|
||||
Future<Igel> create(String name,
|
||||
{String? note, String? gender, String? feature}) async {
|
||||
{String? note,
|
||||
String? gender,
|
||||
String? feature,
|
||||
DateTime? rescuedAt,
|
||||
String? location}) async {
|
||||
String? fmtDate(DateTime? d) =>
|
||||
d == null ? null : d.toIso8601String().split('T').first;
|
||||
final res = await api.post<dynamic>('/igel', {
|
||||
'name': name,
|
||||
'note': note,
|
||||
'gender': gender,
|
||||
'feature': feature,
|
||||
'rescued_at': fmtDate(rescuedAt),
|
||||
'location': (location?.trim().isEmpty ?? true) ? null : location!.trim(),
|
||||
});
|
||||
return Igel.fromMap(res as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<void> update(int id, String name,
|
||||
{String? note, String? gender, String? feature}) async {
|
||||
await api.put<dynamic>('/igel/$id', {
|
||||
'name': name,
|
||||
'note': note,
|
||||
'gender': gender,
|
||||
'feature': feature,
|
||||
});
|
||||
try {
|
||||
final id = (res['id'] as num).toInt();
|
||||
return await get(id);
|
||||
} catch (_) {
|
||||
return Igel.fromMap((res as Map).cast<String, dynamic>());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> delete(int id) async {
|
||||
@@ -40,4 +44,24 @@ class IgelRepository {
|
||||
final map = await api.get<Map<String, dynamic>>('/igel/$id');
|
||||
return Igel.fromMap(map);
|
||||
}
|
||||
|
||||
/// Update hedgehog data. Backend requires name; other fields optional.
|
||||
Future<void> update(int id, String name,
|
||||
{String? note,
|
||||
String? gender,
|
||||
String? feature,
|
||||
DateTime? rescuedAt,
|
||||
String? location}) async {
|
||||
String? fmtDate(DateTime? d) =>
|
||||
d == null ? null : d.toIso8601String().split('T').first;
|
||||
final body = {
|
||||
'name': name,
|
||||
'note': note,
|
||||
'gender': gender,
|
||||
'feature': feature,
|
||||
'rescued_at': fmtDate(rescuedAt),
|
||||
'location': (location?.trim().isEmpty ?? true) ? null : location!.trim(),
|
||||
};
|
||||
await api.put<dynamic>('/igel/$id', body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@ class Igel {
|
||||
final String? gender; // 'männlich' | 'weiblich' | 'unbekannt'
|
||||
final String? feature; // Merkmal
|
||||
final String? note; // Information
|
||||
|
||||
// 🆕 NEU:
|
||||
final DateTime? rescuedAt; // YYYY-MM-DD (Datum)
|
||||
final String? location; // Ort / Fundstelle
|
||||
|
||||
final DateTime? createdAt;
|
||||
final DateTime? updatedAt;
|
||||
|
||||
@@ -13,52 +18,67 @@ class Igel {
|
||||
this.gender,
|
||||
this.feature,
|
||||
this.note,
|
||||
this.rescuedAt,
|
||||
this.location,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
// ---------- Factory fromMap ----------
|
||||
factory Igel.fromMap(Map<String, dynamic> map) => Igel(
|
||||
id: (map['id'] as num).toInt(),
|
||||
name: map['name'] as String,
|
||||
gender: map['gender'] as String?,
|
||||
feature: map['feature'] as String?,
|
||||
note: map['note'] as String?,
|
||||
createdAt: map['created_at'] != null
|
||||
? DateTime.parse(map['created_at'])
|
||||
: null,
|
||||
updatedAt: map['updated_at'] != null
|
||||
? DateTime.parse(map['updated_at'])
|
||||
: null,
|
||||
);
|
||||
static DateTime? _parseDateOrNull(String? s) {
|
||||
if (s == null || s.isEmpty) return null;
|
||||
try {
|
||||
return DateTime.parse(s);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- toMap (optional für PUT/POST) ----------
|
||||
Map<String, dynamic> toMap() => {
|
||||
'id': id,
|
||||
'name': name,
|
||||
if (gender != null) 'gender': gender,
|
||||
if (feature != null) 'feature': feature,
|
||||
if (note != null) 'note': note,
|
||||
if (createdAt != null) 'created_at': createdAt!.toIso8601String(),
|
||||
if (updatedAt != null) 'updated_at': updatedAt!.toIso8601String(),
|
||||
};
|
||||
factory Igel.fromMap(Map<String, dynamic> m) {
|
||||
return Igel(
|
||||
id: (m['id'] as num).toInt(),
|
||||
name: (m['name'] ?? '') as String,
|
||||
gender: m['gender'] as String?,
|
||||
feature: m['feature'] as String?,
|
||||
note: m['note'] as String?,
|
||||
rescuedAt: _parseDateOrNull(m['rescued_at'] as String?),
|
||||
location: m['location'] as String?,
|
||||
createdAt: _parseDateOrNull(m['created_at'] as String?),
|
||||
updatedAt: _parseDateOrNull(m['updated_at'] as String?),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMapForCreateUpdate() {
|
||||
String? fmtDate(DateTime? d) =>
|
||||
d == null ? null : d.toIso8601String().split('T').first;
|
||||
|
||||
return {
|
||||
'name': name,
|
||||
'gender': gender,
|
||||
'feature': feature,
|
||||
'note': note,
|
||||
'rescued_at': fmtDate(rescuedAt),
|
||||
'location': location?.trim().isEmpty == true ? null : location,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- copyWith ----------
|
||||
Igel copyWith({
|
||||
int? id,
|
||||
String? name,
|
||||
String? gender,
|
||||
String? feature,
|
||||
String? note,
|
||||
DateTime? rescuedAt,
|
||||
String? location,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return Igel(
|
||||
id: id ?? this.id,
|
||||
id: id,
|
||||
name: name ?? this.name,
|
||||
gender: gender ?? this.gender,
|
||||
feature: feature ?? this.feature,
|
||||
note: note ?? this.note,
|
||||
rescuedAt: rescuedAt ?? this.rescuedAt,
|
||||
location: location ?? this.location,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
|
||||
@@ -43,6 +43,13 @@ class _IgelDetailState extends ConsumerState<IgelDetailScreen> {
|
||||
final nameC = TextEditingController();
|
||||
final featureC = TextEditingController();
|
||||
final noteC = TextEditingController();
|
||||
|
||||
// NEU: Ort/Fundstelle + Gerettet am
|
||||
final locationC = TextEditingController();
|
||||
DateTime? rescuedAt;
|
||||
DateTime? _initRescuedAt;
|
||||
String? _initLocation;
|
||||
|
||||
String? gender; // 'männlich' | 'weiblich' | 'unbekannt' | null
|
||||
|
||||
// Bilder
|
||||
@@ -70,6 +77,7 @@ class _IgelDetailState extends ConsumerState<IgelDetailScreen> {
|
||||
|
||||
featureC.addListener(_markDirtyBasics);
|
||||
noteC.addListener(_markDirtyBasics);
|
||||
locationC.addListener(_markDirtyBasics); // NEU
|
||||
|
||||
_loadAll();
|
||||
}
|
||||
@@ -79,6 +87,7 @@ class _IgelDetailState extends ConsumerState<IgelDetailScreen> {
|
||||
nameC.dispose();
|
||||
featureC.dispose();
|
||||
noteC.dispose();
|
||||
locationC.dispose(); // NEU
|
||||
mwGewichtC.dispose();
|
||||
mwBehandlungC.dispose();
|
||||
mwBemerkungC.dispose();
|
||||
@@ -91,14 +100,25 @@ class _IgelDetailState extends ConsumerState<IgelDetailScreen> {
|
||||
|
||||
bool get _basicsChanged {
|
||||
if (igel == null) return false;
|
||||
|
||||
final newName = nameC.text.trim();
|
||||
final newFeature =
|
||||
featureC.text.trim().isEmpty ? null : featureC.text.trim();
|
||||
final newNote = noteC.text.trim().isEmpty ? null : noteC.text.trim();
|
||||
final changed = newName != igel!.name ||
|
||||
|
||||
bool changed = newName != igel!.name ||
|
||||
newFeature != igel!.feature ||
|
||||
newNote != igel!.note ||
|
||||
gender != igel!.gender;
|
||||
|
||||
// NEU: rescuedAt & location berücksichtigen
|
||||
String? fmt(DateTime? d) =>
|
||||
d == null ? null : DateFormat('yyyy-MM-dd').format(d);
|
||||
if (fmt(rescuedAt) != fmt(_initRescuedAt)) changed = true;
|
||||
|
||||
final newLoc = locationC.text.trim().isEmpty ? null : locationC.text.trim();
|
||||
if (newLoc != (_initLocation?.trim())) changed = true;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -116,6 +136,12 @@ class _IgelDetailState extends ConsumerState<IgelDetailScreen> {
|
||||
noteC.text = data.note ?? '';
|
||||
gender = data.gender;
|
||||
|
||||
// NEU: rescuedAt + location aus Domain (falls Backend schon erweitert)
|
||||
rescuedAt = data.rescuedAt;
|
||||
locationC.text = data.location ?? '';
|
||||
_initRescuedAt = data.rescuedAt;
|
||||
_initLocation = data.location;
|
||||
|
||||
// Bilder
|
||||
images = await imagesRepo.list(widget.igelId);
|
||||
if (mounted) await _prefetchImages(context);
|
||||
@@ -158,15 +184,31 @@ class _IgelDetailState extends ConsumerState<IgelDetailScreen> {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// NEU: rescuedAt + location mitsenden (Repo wurde entsprechend erweitert)
|
||||
await igelRepo.update(
|
||||
widget.igelId,
|
||||
newName,
|
||||
note: newNote,
|
||||
gender: gender,
|
||||
feature: newFeature,
|
||||
rescuedAt: rescuedAt,
|
||||
location: locationC.text.trim().isEmpty ? null : locationC.text.trim(),
|
||||
);
|
||||
|
||||
igel = igel!.copyWith(
|
||||
name: newName, note: newNote, gender: gender, feature: newFeature);
|
||||
name: newName,
|
||||
note: newNote,
|
||||
gender: gender,
|
||||
feature: newFeature,
|
||||
rescuedAt: rescuedAt,
|
||||
location: locationC.text.trim().isEmpty ? null : locationC.text.trim(),
|
||||
);
|
||||
|
||||
// NEU: Init-Stand für Change-Detection aktualisieren
|
||||
_initRescuedAt = rescuedAt;
|
||||
_initLocation =
|
||||
locationC.text.trim().isEmpty ? null : locationC.text.trim();
|
||||
|
||||
setState(() {});
|
||||
_snack('Gespeichert');
|
||||
} catch (e) {
|
||||
@@ -547,6 +589,50 @@ class _IgelDetailState extends ConsumerState<IgelDetailScreen> {
|
||||
prefixIcon: Icon(Icons.style),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// NEU: Gerettet am (Datum)
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading:
|
||||
const Icon(Icons.calendar_today),
|
||||
title: const Text('Gerettet am'),
|
||||
subtitle: Text(
|
||||
rescuedAt == null
|
||||
? '—'
|
||||
: DateFormat('dd.MM.yyyy')
|
||||
.format(rescuedAt!),
|
||||
),
|
||||
onTap: () async {
|
||||
final now = DateTime.now();
|
||||
final init = rescuedAt ?? now;
|
||||
final picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: init,
|
||||
firstDate: DateTime(2000),
|
||||
lastDate:
|
||||
DateTime(now.year + 1, 12, 31),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() => rescuedAt = picked);
|
||||
}
|
||||
},
|
||||
onLongPress: () =>
|
||||
setState(() => rescuedAt = null),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// NEU: Ort / Fundstelle
|
||||
TextField(
|
||||
controller: locationC,
|
||||
decoration: const InputDecoration(
|
||||
labelText:
|
||||
'Ort / Fundstelle (optional)',
|
||||
prefixIcon:
|
||||
Icon(Icons.place_outlined),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: noteC,
|
||||
@@ -758,7 +844,7 @@ class _IgelDetailState extends ConsumerState<IgelDetailScreen> {
|
||||
),
|
||||
],
|
||||
)),
|
||||
]),
|
||||
]), // for
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user