feat: dynamic units table with localized names via GET /units
- Add units + unit_translations tables with FK constraints on products and ingredient_mappings - Normalize products.unit from Russian strings (г, кг) to English codes (g, kg) - Load units at startup (in-memory registry) and serve via GET /units (language-aware) - Replace hardcoded _units lists and _mapUnit() functions in Flutter with unitsProvider FutureProvider - Re-fetches automatically when language changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../core/locale/unit_provider.dart';
|
||||
import '../products/product_provider.dart';
|
||||
import 'recognition_service.dart';
|
||||
|
||||
@@ -21,8 +22,6 @@ class _RecognitionConfirmScreenState
|
||||
late final List<_EditableItem> _items;
|
||||
bool _saving = false;
|
||||
|
||||
static const _units = ['г', 'кг', 'мл', 'л', 'шт', 'уп'];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -30,7 +29,7 @@ class _RecognitionConfirmScreenState
|
||||
.map((item) => _EditableItem(
|
||||
name: item.name,
|
||||
quantity: item.quantity,
|
||||
unit: _mapUnit(item.unit),
|
||||
unit: item.unit,
|
||||
category: item.category,
|
||||
mappingId: item.mappingId,
|
||||
storageDays: item.storageDays,
|
||||
@@ -39,27 +38,6 @@ class _RecognitionConfirmScreenState
|
||||
.toList();
|
||||
}
|
||||
|
||||
String _mapUnit(String unit) {
|
||||
// Backend may return 'pcs', 'g', 'kg', etc. — normalise to display units.
|
||||
switch (unit.toLowerCase()) {
|
||||
case 'g':
|
||||
return 'г';
|
||||
case 'kg':
|
||||
return 'кг';
|
||||
case 'ml':
|
||||
return 'мл';
|
||||
case 'l':
|
||||
return 'л';
|
||||
case 'pcs':
|
||||
case 'шт':
|
||||
return 'шт';
|
||||
case 'уп':
|
||||
return 'уп';
|
||||
default:
|
||||
return unit;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -80,7 +58,7 @@ class _RecognitionConfirmScreenState
|
||||
itemCount: _items.length,
|
||||
itemBuilder: (_, i) => _ItemTile(
|
||||
item: _items[i],
|
||||
units: _units,
|
||||
units: ref.watch(unitsProvider).valueOrNull ?? {},
|
||||
onDelete: () => setState(() => _items.removeAt(i)),
|
||||
onChanged: () => setState(() {}),
|
||||
),
|
||||
@@ -173,7 +151,7 @@ class _ItemTile extends StatefulWidget {
|
||||
});
|
||||
|
||||
final _EditableItem item;
|
||||
final List<String> units;
|
||||
final Map<String, String> units;
|
||||
final VoidCallback onDelete;
|
||||
final VoidCallback onChanged;
|
||||
|
||||
@@ -268,21 +246,23 @@ class _ItemTileState extends State<_ItemTile> {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
DropdownButton<String>(
|
||||
value: widget.units.contains(widget.item.unit)
|
||||
? widget.item.unit
|
||||
: widget.units.last,
|
||||
underline: const SizedBox(),
|
||||
items: widget.units
|
||||
.map((u) => DropdownMenuItem(value: u, child: Text(u)))
|
||||
.toList(),
|
||||
onChanged: (v) {
|
||||
if (v != null) {
|
||||
setState(() => widget.item.unit = v);
|
||||
widget.onChanged();
|
||||
}
|
||||
},
|
||||
),
|
||||
widget.units.isEmpty
|
||||
? const SizedBox(width: 48)
|
||||
: DropdownButton<String>(
|
||||
value: widget.units.containsKey(widget.item.unit)
|
||||
? widget.item.unit
|
||||
: widget.units.keys.first,
|
||||
underline: const SizedBox(),
|
||||
items: widget.units.entries
|
||||
.map((e) => DropdownMenuItem(value: e.key, child: Text(e.value)))
|
||||
.toList(),
|
||||
onChanged: (v) {
|
||||
if (v != null) {
|
||||
setState(() => widget.item.unit = v);
|
||||
widget.onChanged();
|
||||
}
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: widget.onDelete,
|
||||
|
||||
Reference in New Issue
Block a user