chore: initial commit Dutra Store POS app
This commit is contained in:
238
test_syntax2.dart
Normal file
238
test_syntax2.dart
Normal file
@@ -0,0 +1,238 @@
|
||||
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'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user