239 lines
9.6 KiB
Dart
239 lines
9.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
void test(BuildContext context) {
|
|
final name = TextEditingController();
|
|
final barcode = TextEditingController();
|
|
final price = TextEditingController();
|
|
final priceReseller = TextEditingController();
|
|
final stock = TextEditingController();
|
|
final minStockCtrl = TextEditingController(text: '5');
|
|
final hargaPokokCtrl = TextEditingController();
|
|
final focusNodes = <FocusNode>[
|
|
FocusNode(),
|
|
FocusNode(),
|
|
FocusNode(),
|
|
FocusNode(),
|
|
FocusNode(),
|
|
FocusNode(),
|
|
FocusNode(),
|
|
];
|
|
final _scrollTimers = <FocusNode, Timer>{};
|
|
for (final node in focusNodes) {
|
|
node.addListener(() {
|
|
if (!node.hasFocus) {
|
|
_scrollTimers[node]?.cancel();
|
|
_scrollTimers.remove(node);
|
|
} else if (context.mounted) {
|
|
_scrollTimers[node]?.cancel();
|
|
_scrollTimers[node] = Timer(const Duration(milliseconds: 350), () {
|
|
if (node.context != null && node.context!.mounted) {
|
|
Scrollable.ensureVisible(
|
|
node.context!,
|
|
duration: const Duration(milliseconds: 250),
|
|
alignment: 0.1,
|
|
);
|
|
}
|
|
_scrollTimers.remove(node);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
final _scrollController = ScrollController();
|
|
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
backgroundColor: Colors.transparent,
|
|
builder: (sheetContext) {
|
|
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
|
return Container(
|
|
constraints: BoxConstraints(
|
|
maxHeight: MediaQuery.of(context).size.height * 0.92 - bottomInset,
|
|
),
|
|
decoration: const BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
|
border: Border(top: BorderSide(color: Colors.grey)),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
margin: const EdgeInsets.only(top: 12),
|
|
width: 40,
|
|
height: 4,
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
|
|
child: Row(
|
|
children: [
|
|
const Icon(Icons.add_box_outlined, color: Colors.amber),
|
|
const SizedBox(width: 8),
|
|
const Text('Tambah Produk',
|
|
style: TextStyle(
|
|
fontSize: 16, fontWeight: FontWeight.w700)),
|
|
const Spacer(),
|
|
IconButton(
|
|
icon: const Icon(Icons.close, color: Colors.grey),
|
|
onPressed: () => Navigator.pop(sheetContext),
|
|
tooltip: 'Tutup',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const Divider(height: 12),
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
controller: _scrollController,
|
|
padding: EdgeInsets.fromLTRB(16, 8, 16, 16 + bottomInset),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
TextField(
|
|
controller: barcode,
|
|
focusNode: focusNodes[0],
|
|
decoration: InputDecoration(
|
|
labelText: 'Barcode / No. Produk',
|
|
prefixIcon: const Icon(Icons.qr_code_2_outlined),
|
|
suffixIcon: IconButton(
|
|
icon: const Icon(Icons.qr_code_scanner_outlined, color: Colors.amber),
|
|
onPressed: () async {
|
|
final code = await Navigator.push<String>(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => const Scaffold(body: Center(child: Text('Scan'))),
|
|
),
|
|
);
|
|
if (code != null && context.mounted) {
|
|
barcode.text = code;
|
|
}
|
|
},
|
|
tooltip: 'Scan barcode',
|
|
)),
|
|
),
|
|
const SizedBox(height: 10),
|
|
TextField(
|
|
controller: name,
|
|
focusNode: focusNodes[1],
|
|
decoration: const InputDecoration(
|
|
labelText: 'Nama produk',
|
|
prefixIcon: Icon(Icons.label_outline)),
|
|
),
|
|
const SizedBox(height: 10),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextField(
|
|
controller: price,
|
|
focusNode: focusNodes[2],
|
|
keyboardType: TextInputType.number,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Harga Umum',
|
|
prefixText: 'Rp ',
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: TextField(
|
|
controller: priceReseller,
|
|
focusNode: focusNodes[3],
|
|
keyboardType: TextInputType.number,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Harga Reseller',
|
|
prefixText: 'Rp ',
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
TextField(
|
|
controller: stock,
|
|
focusNode: focusNodes[4],
|
|
keyboardType: TextInputType.number,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Stok awal',
|
|
prefixIcon: Icon(Icons.numbers),
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
TextField(
|
|
controller: hargaPokokCtrl,
|
|
focusNode: focusNodes[5],
|
|
keyboardType: TextInputType.number,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Harga Pokok (modal)',
|
|
prefixText: 'Rp ',
|
|
helperText: 'Kosongkan jika tidak diketahui',
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
TextField(
|
|
controller: minStockCtrl,
|
|
focusNode: focusNodes[6],
|
|
keyboardType: TextInputType.number,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Stok minimum (warning)',
|
|
prefixIcon: Icon(Icons.warning_outlined),
|
|
helperText: 'Default: 5',
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: FilledButton.icon(
|
|
onPressed: () async {
|
|
final n = name.text.trim();
|
|
final bc = barcode.text.trim();
|
|
final pr = (price.text.isEmpty ? 0 : double.tryParse(price.text) ?? 0);
|
|
final prR = (priceReseller.text.isEmpty ? pr : double.tryParse(priceReseller.text) ?? pr);
|
|
final st = int.tryParse(stock.text) ?? 0;
|
|
final ms = int.tryParse(minStockCtrl.text) ?? 5;
|
|
final hp = (hargaPokokCtrl.text.isEmpty ? 0 : double.tryParse(hargaPokokCtrl.text) ?? 0);
|
|
if (n.isEmpty || pr <= 0) return;
|
|
final err = 'OK'; // await state.addProduct(n, bc, pr, prR, st, ms, hp);
|
|
if (!context.mounted) return;
|
|
final messenger = ScaffoldMessenger.of(context);
|
|
if (err == null) {
|
|
name.clear();
|
|
barcode.clear();
|
|
price.clear();
|
|
priceReseller.clear();
|
|
stock.clear();
|
|
minStockCtrl.clear();
|
|
hargaPokokCtrl.clear();
|
|
messenger.showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Produk berhasil ditambahkan'),
|
|
backgroundColor: Colors.green,
|
|
),
|
|
);
|
|
Navigator.pop(sheetContext);
|
|
} else {
|
|
messenger.showSnackBar(
|
|
SnackBar(
|
|
content: Text('Gagal menambah produk: $err'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
},
|
|
icon: const Icon(Icons.add),
|
|
label: const Text('Tambah Produk'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|