Files
saat-app/flutter/lib/add_transaksi_screen.dart
Alex Ardhian 5ab72678a3 Gabung app Android Flutter 'Saat Invest' ke repo saat-app (folder flutter/)
Satu repo untuk backend Odoo (addon saat_invest) + app Android.
- flutter/ berisi project Flutter (lib/, android/, pubspec, test)
- Login Odoo JSON-RPC, tab Portofolio+Equity, tab Transaksi, catat jual/beli otomatis sync
- Build .gitignore nested dipakai sehingga artefak build/.dart_tool tak ter-track
Sejarah: gabung flat (satu sejarah backend+app).
2026-09-02 14:48:32 +07:00

127 lines
5.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../models/saham_transaksi.dart';
import '../services/stock_service.dart';
class AddTransaksiScreen extends StatefulWidget {
const AddTransaksiScreen({super.key, required this.service});
final StockService service;
@override
State<AddTransaksiScreen> createState() => _AddTransaksiScreenState();
}
class _AddTransaksiScreenState extends State<AddTransaksiScreen> {
final _tanggal = TextEditingController(
text: DateFormat('yyyy-MM-dd').format(DateTime.now()));
final _volume = TextEditingController();
final _harga = TextEditingController();
final _fee = TextEditingController(text: '0');
late Future<List<Saham>> _sahamFut;
int? _sahamId;
bool _isBuy = true;
bool _busy = false;
String? _err;
@override
void initState() {
super.initState();
_sahamFut = widget.service.listSaham();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Catat Transaksi')),
body: Padding(
padding: const EdgeInsets.all(16),
child: ListView(
children: [
SegmentedButton<bool>(
segments: const [
ButtonSegment(value: true, label: Text('Beli'), icon: Icon(Icons.add)),
ButtonSegment(value: false, label: Text('Jual'), icon: Icon(Icons.remove)),
],
selected: {_isBuy},
onSelectionChanged: (s) => setState(() => _isBuy = s.first),
),
const SizedBox(height: 16),
TextField(controller: _tanggal,
decoration: const InputDecoration(labelText: 'Tanggal (YYYY-MM-DD)',
prefixIcon: Icon(Icons.calendar_today))),
const SizedBox(height: 12),
FutureBuilder<List<Saham>>(
future: _sahamFut,
builder: (context, snap) {
if (snap.connectionState != ConnectionState.done) {
return const ListTile(
leading: SizedBox(width:18,height:18,child:CircularProgressIndicator(strokeWidth:2)),
title: Text('Memuat saham...'));
}
if (snap.hasError) {
return Text('Error saham: ${snap.error}', style: const TextStyle(color: Colors.red));
}
final saham = snap.data ?? [];
return DropdownButtonFormField<int>(
initialValue: _sahamId,
decoration: const InputDecoration(labelText: 'Pilih Saham',
prefixIcon: Icon(Icons.business)),
items: [
for (final s in saham)
DropdownMenuItem(value: s.id, child: Text('${s.kode} — ${s.perusahaan ?? ''}')),
],
onChanged: (v) => setState(() => _sahamId = v),
);
},
),
const SizedBox(height: 12),
TextField(controller: _volume, keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: 'Jumlah Lembar', prefixIcon: Icon(Icons.numbers))),
const SizedBox(height: 12),
TextField(controller: _harga, keyboardType: const TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration(labelText: 'Harga per Lembar (Rp)', prefixIcon: Icon(Icons.attach_money))),
const SizedBox(height: 12),
TextField(controller: _fee, keyboardType: const TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration(labelText: 'Biaya / Fee (Rp)', prefixIcon: Icon(Icons.payments))),
if (_err != null)
Padding(padding: const EdgeInsets.only(top: 8),
child: Text(_err!, style: const TextStyle(color: Colors.red))),
const SizedBox(height: 20),
FilledButton.icon(
onPressed: _busy ? null : _save,
icon: _busy ? const SizedBox(width:18,height:18,child:CircularProgressIndicator(strokeWidth:2))
: const Icon(Icons.save),
label: const Text('Simpan'),
),
],
),
),
);
}
Future<void> _save() async {
setState(() { _busy = true; _err = null; });
try {
final date = _tanggal.text.trim();
final vol = int.tryParse(_volume.text.trim()) ?? 0;
final harga = double.tryParse(_harga.text.replaceAll('.', '').replaceAll(',', '.')) ?? 0;
final feeC = double.tryParse(_fee.text.trim().isEmpty ? '0' : _fee.text.trim()) ?? 0;
if (_sahamId == null) throw Exception('Pilih saham dulu');
if (vol <= 0) throw Exception('Volume harus > 0');
if (harga <= 0) throw Exception('Harga harus > 0');
if (!date.contains('-')) throw Exception('Format tanggal salah');
await widget.service.addTransaksi(
sahamId: _sahamId!, tipe: _isBuy ? 'buy' : 'sell',
volume: vol, harga: harga, fee: feeC, tanggal: date);
if (!mounted) return;
Navigator.pop(context, true);
} catch (e) {
if (!mounted) return;
setState(() { _busy = false; _err = e.toString().replaceFirst('Exception: ', ''); });
}
}
}