325 lines
12 KiB
Dart
325 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:get/get.dart';
|
||
import 'package:intl/intl.dart';
|
||
|
||
import '../controller/home_controller.dart';
|
||
import '../models/kauf_filament_model.dart';
|
||
import 'anwendung_view.dart';
|
||
|
||
class HomeView extends GetView<HomeController> {
|
||
static const routeName = '/home-view';
|
||
const HomeView({super.key});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final dateFormat = DateFormat('dd.MM.yyyy');
|
||
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: const Text('Filament Einkaufsverwaltung'),
|
||
centerTitle: true,
|
||
backgroundColor: Colors.blue.shade300,
|
||
),
|
||
body: Column(
|
||
children: [
|
||
// EINGABEBEREICH
|
||
Padding(
|
||
padding: const EdgeInsets.all(12),
|
||
child: Form(
|
||
key: controller.formKey,
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
Obx(() => ListTile(
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(4),
|
||
side: const BorderSide(
|
||
color: Colors.grey,
|
||
width: 1,
|
||
),
|
||
),
|
||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||
title: const Text('Kaufdatum'),
|
||
subtitle: Text(
|
||
dateFormat.format(controller.kaufdatum.value),
|
||
),
|
||
trailing: const Icon(Icons.calendar_month),
|
||
onTap: () async {
|
||
final date = await showDatePicker(
|
||
context: context,
|
||
initialDate: controller.kaufdatum.value,
|
||
firstDate: DateTime(2000),
|
||
lastDate: DateTime(2100),
|
||
);
|
||
|
||
if (date != null) {
|
||
controller.kaufdatum.value = date;
|
||
}
|
||
},
|
||
)),
|
||
|
||
const SizedBox(height: 8),
|
||
|
||
TextFormField(
|
||
controller: controller.haendlerController,
|
||
decoration: const InputDecoration(
|
||
labelText: 'Wo gekauft / Händler',
|
||
border: OutlineInputBorder(),
|
||
),
|
||
validator: (value) =>
|
||
value == null || value.trim().isEmpty
|
||
? 'Händler eingeben'
|
||
: null,
|
||
),
|
||
|
||
const SizedBox(height: 8),
|
||
|
||
TextFormField(
|
||
controller: controller.markeController,
|
||
decoration: const InputDecoration(
|
||
labelText: 'Marke',
|
||
border: OutlineInputBorder(),
|
||
),
|
||
validator: (value) =>
|
||
value == null || value.trim().isEmpty
|
||
? 'Marke eingeben'
|
||
: null,
|
||
),
|
||
|
||
const SizedBox(height: 8),
|
||
|
||
Obx(() => DropdownButtonFormField<String>(
|
||
key: ValueKey(
|
||
'${controller.editingId.value ?? "new"}'
|
||
'_${controller.material.value}',
|
||
),
|
||
initialValue: controller.material.value,
|
||
decoration: const InputDecoration(
|
||
labelText: 'Material',
|
||
border: OutlineInputBorder(),
|
||
),
|
||
items: controller.materialien.map((item) {
|
||
return DropdownMenuItem(
|
||
value: item,
|
||
child: Text(item),
|
||
);
|
||
}).toList(),
|
||
onChanged: (value) {
|
||
if (value != null) {
|
||
controller.material.value = value;
|
||
}
|
||
},
|
||
)),
|
||
|
||
const SizedBox(height: 8),
|
||
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: TextFormField(
|
||
controller: controller.farbeController,
|
||
decoration: const InputDecoration(
|
||
labelText: 'Farbe',
|
||
border: OutlineInputBorder(),
|
||
),
|
||
validator: (value) =>
|
||
value == null || value.trim().isEmpty
|
||
? 'Farbe eingeben'
|
||
: null,
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: TextFormField(
|
||
controller: controller.preisController,
|
||
keyboardType:
|
||
const TextInputType.numberWithOptions(
|
||
decimal: true,
|
||
),
|
||
decoration: const InputDecoration(
|
||
labelText: 'Preis (€)',
|
||
border: OutlineInputBorder(),
|
||
),
|
||
validator: (value) {
|
||
final price = double.tryParse(
|
||
(value ?? '').replaceAll(',', '.'),
|
||
);
|
||
if (price == null ||
|
||
!price.isFinite ||
|
||
price < 0) {
|
||
return 'Preis ungültig';
|
||
}
|
||
return null;
|
||
},
|
||
),
|
||
),
|
||
],
|
||
),
|
||
|
||
const SizedBox(height: 8),
|
||
|
||
TextFormField(
|
||
controller: controller.gewichtController,
|
||
decoration: const InputDecoration(
|
||
labelText: 'Gewicht in Gramm (g)',
|
||
border: OutlineInputBorder(),
|
||
),
|
||
validator: (value) =>
|
||
value == null || value.trim().isEmpty
|
||
? 'Gewicht eingeben g'
|
||
: null,
|
||
),
|
||
|
||
const SizedBox(height: 12),
|
||
|
||
Obx(() => Row(
|
||
children: [
|
||
Expanded(
|
||
child: ElevatedButton.icon(
|
||
onPressed: controller.saveFilament,
|
||
icon: Icon(
|
||
controller.editingId.value == null
|
||
? Icons.add
|
||
: Icons.save,
|
||
),
|
||
label: Text(
|
||
controller.editingId.value == null
|
||
? 'Filament speichern'
|
||
: 'Änderungen speichern',
|
||
),
|
||
),
|
||
),
|
||
if (controller.editingId.value != null)
|
||
IconButton(
|
||
tooltip: 'Bearbeitung abbrechen',
|
||
onPressed: controller.resetForm,
|
||
icon: const Icon(Icons.close),
|
||
),
|
||
],
|
||
)),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
|
||
const Divider(),
|
||
|
||
// LISTENBEREICH
|
||
Expanded(
|
||
child: Obx(() {
|
||
final filamente = controller.filamente;
|
||
|
||
if (filamente.isEmpty) {
|
||
return const Center(
|
||
child: Text('Noch keine Filamente gespeichert'),
|
||
);
|
||
}
|
||
|
||
return ListView.builder(
|
||
itemCount: filamente.length,
|
||
itemBuilder: (context, index) {
|
||
final filament = filamente[index];
|
||
|
||
return Card(
|
||
margin: const EdgeInsets.symmetric(
|
||
horizontal: 12,
|
||
vertical: 4,
|
||
),
|
||
child: ListTile(
|
||
leading: const CircleAvatar(
|
||
child: Icon(Icons.inventory_2_outlined),
|
||
),
|
||
title: Text(
|
||
'${filament.material} – ${filament.farbe}',
|
||
),
|
||
subtitle: Text(
|
||
'${filament.haendler}\n'
|
||
'${dateFormat.format(filament.kaufdatum)}'
|
||
' | ${filament.preis.toStringAsFixed(2)} €',
|
||
),
|
||
isThreeLine: true,
|
||
onTap: () => Get.to(
|
||
() => AnwendungView(
|
||
filamentId: filament.filamentId,
|
||
),
|
||
),
|
||
trailing: PopupMenuButton<String>(
|
||
onSelected: (action) =>
|
||
_handleAction(context, action, filament),
|
||
itemBuilder: (context) => const [
|
||
PopupMenuItem(
|
||
value: 'edit',
|
||
child: ListTile(
|
||
leading: Icon(Icons.edit),
|
||
title: Text('Bearbeiten'),
|
||
),
|
||
),
|
||
PopupMenuItem(
|
||
value: 'duplicate',
|
||
child: ListTile(
|
||
leading: Icon(Icons.copy),
|
||
title: Text('Duplizieren'),
|
||
),
|
||
),
|
||
PopupMenuItem(
|
||
value: 'delete',
|
||
child: ListTile(
|
||
leading: Icon(Icons.delete_outline),
|
||
title: Text('Löschen'),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Future<void> _handleAction(
|
||
BuildContext context,
|
||
String action,
|
||
KaufFilamentModel filament,
|
||
) async {
|
||
switch (action) {
|
||
case 'edit':
|
||
controller.editFilament(filament);
|
||
break;
|
||
|
||
case 'duplicate':
|
||
await controller.duplicateFilament(filament);
|
||
break;
|
||
|
||
case 'delete':
|
||
final confirmed = await showDialog<bool>(
|
||
context: context,
|
||
builder: (dialogContext) => AlertDialog(
|
||
title: const Text('Filament löschen?'),
|
||
content: Text(
|
||
'${filament.material} – ${filament.farbe} löschen?',
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(dialogContext, false),
|
||
child: const Text('Abbrechen'),
|
||
),
|
||
FilledButton(
|
||
onPressed: () => Navigator.pop(dialogContext, true),
|
||
child: const Text('Löschen'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
|
||
if (confirmed == true) {
|
||
await controller.deleteFilament(filament.filamentId);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
} |