add two views

This commit is contained in:
atseirjo
2026-10-27 15:00:16 +01:00
parent 864da57537
commit 07a1bb02fd
42 changed files with 3582 additions and 346 deletions
+82 -264
View File
@@ -1,10 +1,13 @@
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 '../theme/app_theme.dart';
import '../widgets/filament_form.dart';
import '../widgets/filament_inventory.dart';
import 'anwendung_view.dart';
import 'bauteil_view.dart';
class HomeView extends GetView<HomeController> {
static const routeName = '/home-view';
@@ -12,271 +15,91 @@ class HomeView extends GetView<HomeController> {
@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),
),
],
)),
],
toolbarHeight: 80,
actions: [
IconButton(
tooltip: 'Bauteile verwalten',
icon: const Icon(Icons.view_in_ar_outlined),
onPressed: () => Get.toNamed(BauteilView.routeName),
),
const SizedBox(width: 8),
],
title: const Row(
children: [
Icon(Icons.all_inbox_rounded, color: AppTheme.teal, size: 28),
SizedBox(width: 12),
Flexible(
child: Text(
'Filamentverwaltung',
style: TextStyle(fontSize: 21, fontWeight: FontWeight.w700),
),
),
),
const Divider(),
// LISTENBEREICH
Expanded(
child: Obx(() {
final filamente = controller.filamente;
if (filamente.isEmpty) {
return const Center(
child: Text('Noch keine Filamente gespeichert'),
],
),
),
body: SafeArea(
top: false,
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 1320),
child: LayoutBuilder(
builder: (context, constraints) {
final wide = constraints.maxWidth >= 900;
final form = FilamentForm(controller: controller);
final inventory = FilamentInventory(
controller: controller,
onOpen: (filament) => Get.toNamed(
'${AnwendungView.routeName}/${Uri.encodeComponent(filament.filamentId)}',
),
onAction: (action, filament) =>
_handleAction(context, action, filament),
);
}
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,
if (wide) {
return Padding(
padding: const EdgeInsets.fromLTRB(24, 8, 24, 24),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 390,
child: SingleChildScrollView(
controller: controller.formScrollController,
child: form,
),
),
),
trailing: PopupMenuButton<String>(
onSelected: (action) =>
_handleAction(context, action, filament),
itemBuilder: (context) => const [
PopupMenuItem(
value: 'edit',
child: ListTile(
leading: Icon(Icons.edit),
title: Text('Bearbeiten'),
),
const SizedBox(width: 24),
Expanded(
child: CustomScrollView(
controller: controller.pageScrollController,
slivers: [inventory],
),
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'),
),
),
],
),
),
],
),
);
},
);
}),
}
return CustomScrollView(
controller: controller.pageScrollController,
keyboardDismissBehavior:
ScrollViewKeyboardDismissBehavior.onDrag,
slivers: [
SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 20),
sliver: SliverToBoxAdapter(child: form),
),
SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 24),
sliver: inventory,
),
],
);
},
),
),
],
),
),
);
}
@@ -288,21 +111,17 @@ class HomeView extends GetView<HomeController> {
) async {
switch (action) {
case 'edit':
controller.editFilament(filament);
await 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?',
),
content: Text('${filament.material} – ${filament.farbe} löschen?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext, false),
@@ -315,11 +134,10 @@ class HomeView extends GetView<HomeController> {
],
),
);
if (confirmed == true) {
await controller.deleteFilament(filament.filamentId);
}
break;
}
}
}
}