add some new features
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../controller/abfall_controller.dart';
|
||||
import '../helper/display_formats.dart';
|
||||
import '../models/abfall_model.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
import 'entry_panel.dart';
|
||||
|
||||
class AbfallInventory extends StatelessWidget {
|
||||
const AbfallInventory({super.key, required this.controller});
|
||||
final AbfallController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => DecoratedSliver(
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.inventorySurface,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
sliver: SliverPadding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
sliver: Obx(() {
|
||||
final entries = controller.abfaelle.toList();
|
||||
final busy = controller.isSaving.value;
|
||||
return SliverMainAxisGroup(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Abfallübersicht',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 25,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${entries.length} Einträge · die neuesten zuerst.',
|
||||
style: const TextStyle(color: Color(0xFFB9CCD9)),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
_AbfallStat(
|
||||
label: 'Summe Abfall',
|
||||
value:
|
||||
'${DisplayFormats.number(controller.summeAbfall)} g',
|
||||
),
|
||||
_AbfallStat(
|
||||
label: 'Summe Abfallkosten',
|
||||
value: DisplayFormats.currency(controller.summeKosten),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (entries.any((entry) => entry.kostenUnbekannt)) ...[
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'Bei einigen älteren Bauteilen fehlen Filamentdaten. Ihr Gewicht ist enthalten; ihre unbekannten Kosten fehlen in der Kostensumme.',
|
||||
style: TextStyle(color: Color(0xFFB9CCD9), height: 1.5),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (entries.isEmpty)
|
||||
const SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 24),
|
||||
child: Text(
|
||||
'Noch kein Abfall gespeichert. Erfasse deinen ersten Eintrag.',
|
||||
style: TextStyle(color: Color(0xFFB9CCD9), height: 1.5),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
SliverList.separated(
|
||||
itemCount: entries.length,
|
||||
separatorBuilder: (context, index) =>
|
||||
const SizedBox(height: 12),
|
||||
itemBuilder: (context, index) {
|
||||
final entry = entries[index];
|
||||
return SavedEntryCard(
|
||||
key: ValueKey(entry.abfallId),
|
||||
title:
|
||||
'${DisplayFormats.number(entry.gewichtGramm)} g Abfall',
|
||||
subtitle: DisplayFormats.date(entry.datumZeit),
|
||||
notes: entry.ausBauteil
|
||||
? 'Aus Bauteil „${entry.bauteilName}“. Änderungen und Löschen über die Bauteilverwaltung.${entry.kostenNachtraeglich ? ' Kosten nachträglich aus den aktuellen Rollenpreisen und Startgewichten berechnet.' : ''}'
|
||||
: '',
|
||||
details: [
|
||||
entry.kostenUnbekannt
|
||||
? 'Abfallkosten: nicht ermittelbar'
|
||||
: 'Abfallkosten: ${DisplayFormats.currency(entry.kosten)}',
|
||||
],
|
||||
trailing: entry.ausBauteil
|
||||
? null
|
||||
: PopupMenuButton<String>(
|
||||
tooltip: 'Abfalleintrag verwalten',
|
||||
enabled: !busy,
|
||||
iconColor: AppTheme.ink,
|
||||
onSelected: (action) {
|
||||
if (action == 'edit') {
|
||||
controller.editAbfall(entry);
|
||||
} else if (action == 'delete') {
|
||||
_confirmDelete(context, entry);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => const [
|
||||
PopupMenuItem(
|
||||
value: 'edit',
|
||||
child: Text('Bearbeiten'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'delete',
|
||||
child: Text('Löschen'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _confirmDelete(BuildContext context, AbfallModel entry) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Abfall löschen?'),
|
||||
content: Text(
|
||||
'${DisplayFormats.number(entry.gewichtGramm)} g Abfall vom ${DisplayFormats.date(entry.datumZeit)} löschen?',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('Abbrechen'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('Löschen'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == true) await controller.deleteAbfall(entry.abfallId);
|
||||
}
|
||||
}
|
||||
|
||||
class _AbfallStat extends StatelessWidget {
|
||||
const _AbfallStat({required this.label, required this.value});
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF243E50),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(color: Color(0xFFB9CCD9), fontSize: 12),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user