681 lines
25 KiB
Dart
681 lines
25 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import '../../config/app_config.dart';
|
|
import '../../models/document_model.dart';
|
|
import '../../main.dart'; // To access localStorageProvider
|
|
import '../documents/pending_documents_provider.dart';
|
|
import '../../data/master_sku.dart';
|
|
import '../../core/network/api_client.dart';
|
|
|
|
class EditorScreen extends ConsumerStatefulWidget {
|
|
final String? pendingId;
|
|
|
|
const EditorScreen({super.key, this.pendingId});
|
|
|
|
@override
|
|
ConsumerState<EditorScreen> createState() => _EditorScreenState();
|
|
}
|
|
|
|
class _EditorScreenState extends ConsumerState<EditorScreen> {
|
|
DocumentModel? _document;
|
|
|
|
// Controllers for Header
|
|
final _tanggalCtrl = TextEditingController();
|
|
final _noPoCtrl = TextEditingController();
|
|
final _noSoCtrl = TextEditingController();
|
|
final _noDoCtrl = TextEditingController();
|
|
|
|
// Controllers for Shipment
|
|
final _kepadaYthCtrl = TextEditingController();
|
|
final _orderUntukCtrl = TextEditingController();
|
|
final _alamatCtrl = TextEditingController();
|
|
final _platTrukCtrl = TextEditingController();
|
|
final _namaDriverCtrl = TextEditingController();
|
|
final _namaPenerimaCtrl = TextEditingController();
|
|
|
|
// Controllers for Geotagging
|
|
final _latitudeCtrl = TextEditingController();
|
|
final _longitudeCtrl = TextEditingController();
|
|
|
|
// Controllers for Items
|
|
List<Map<String, TextEditingController>> _itemControllers = [];
|
|
|
|
bool _isAgreed = false;
|
|
final _formKey = GlobalKey<FormState>();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadDocumentData();
|
|
}
|
|
|
|
void _loadDocumentData() {
|
|
if (widget.pendingId == null) {
|
|
return;
|
|
}
|
|
|
|
final pendingDocs = ref.read(pendingDocumentsProvider);
|
|
final pendingDoc = pendingDocs.firstWhere(
|
|
(doc) => doc.id == widget.pendingId,
|
|
orElse: () => PendingDocument(id: '', imagePath: '')
|
|
);
|
|
|
|
if (pendingDoc.document != null) {
|
|
final doc = pendingDoc.document!;
|
|
setState(() {
|
|
_document = doc;
|
|
_tanggalCtrl.text = doc.tanggal;
|
|
_noPoCtrl.text = doc.noPo;
|
|
_noSoCtrl.text = doc.noSo;
|
|
_noDoCtrl.text = doc.noDo;
|
|
|
|
_kepadaYthCtrl.text = doc.kepadaYth;
|
|
_orderUntukCtrl.text = doc.orderUntuk;
|
|
_alamatCtrl.text = doc.alamat;
|
|
_platTrukCtrl.text = doc.platTruk;
|
|
_namaDriverCtrl.text = doc.namaDriver;
|
|
|
|
final lat = pendingDoc.latitude ?? doc.latitude;
|
|
final lng = pendingDoc.longitude ?? doc.longitude;
|
|
_latitudeCtrl.text = lat != null ? lat.toString() : '-';
|
|
_longitudeCtrl.text = lng != null ? lng.toString() : '-';
|
|
|
|
for (var item in doc.items) {
|
|
_addItem(
|
|
sku: item.nomorSku,
|
|
nama: item.namaBarang,
|
|
banyak: item.banyak,
|
|
jumlah: item.jumlah,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void _addItem({String sku = '', String nama = '', String banyak = '', String jumlah = ''}) {
|
|
final skuCtrl = TextEditingController(text: sku);
|
|
final namaCtrl = TextEditingController(text: nama);
|
|
final banyakCtrl = TextEditingController(text: banyak);
|
|
final jumlahCtrl = TextEditingController(text: jumlah);
|
|
|
|
// Auto rename based on master SKU
|
|
skuCtrl.addListener(() {
|
|
final currentSku = skuCtrl.text;
|
|
if (MasterSku.data.containsKey(currentSku)) {
|
|
namaCtrl.text = MasterSku.data[currentSku]!;
|
|
} else {
|
|
namaCtrl.text = 'SKU Tidak Terdaftar';
|
|
}
|
|
});
|
|
|
|
setState(() {
|
|
_itemControllers.add({
|
|
'sku': skuCtrl,
|
|
'nama': namaCtrl,
|
|
'banyak': banyakCtrl,
|
|
'jumlah': jumlahCtrl,
|
|
});
|
|
});
|
|
}
|
|
|
|
void _removeItem(int index) {
|
|
setState(() {
|
|
final ctrls = _itemControllers[index];
|
|
ctrls['sku']?.dispose();
|
|
ctrls['nama']?.dispose();
|
|
ctrls['banyak']?.dispose();
|
|
ctrls['jumlah']?.dispose();
|
|
_itemControllers.removeAt(index);
|
|
});
|
|
}
|
|
|
|
void _showConfirmationDialog() {
|
|
if (!_formKey.currentState!.validate()) return;
|
|
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
builder: (context) {
|
|
return StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setModalState) {
|
|
final isButtonEnabled = _isAgreed && _namaPenerimaCtrl.text.trim().isNotEmpty;
|
|
return Padding(
|
|
padding: EdgeInsets.only(
|
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
|
left: 24,
|
|
right: 24,
|
|
top: 24,
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Confirm Submission', style: AppConfig.h2),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Please enter your name (as recipient/storekeeper) and check the agreement box to confirm delivery.',
|
|
style: AppConfig.bodyMedium.copyWith(color: AppConfig.textSecondary),
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextField(
|
|
controller: _namaPenerimaCtrl,
|
|
onChanged: (val) {
|
|
setModalState(() {});
|
|
},
|
|
style: AppConfig.bodyMedium,
|
|
decoration: InputDecoration(
|
|
labelText: 'Nama Penerima (Penjaga Toko)',
|
|
labelStyle: AppConfig.bodyMedium.copyWith(color: AppConfig.textSecondary),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Checkbox(
|
|
value: _isAgreed,
|
|
activeColor: AppConfig.primaryColor,
|
|
onChanged: (val) {
|
|
setModalState(() {
|
|
_isAgreed = val ?? false;
|
|
});
|
|
},
|
|
),
|
|
Expanded(
|
|
child: Text('I agree and confirm the data is accurate.', style: AppConfig.bodyMedium),
|
|
)
|
|
],
|
|
),
|
|
const SizedBox(height: 24),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 50,
|
|
child: ElevatedButton(
|
|
onPressed: isButtonEnabled ? () {
|
|
Navigator.pop(context); // close modal
|
|
_submitDocument();
|
|
} : null,
|
|
child: const Text('Confirm'),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
Future<void> _submitDocument() async {
|
|
// Construct final document
|
|
final pendingDocs = ref.read(pendingDocumentsProvider);
|
|
final pendingDoc = pendingDocs.firstWhere(
|
|
(doc) => doc.id == widget.pendingId,
|
|
orElse: () => PendingDocument(id: '', imagePath: '')
|
|
);
|
|
final double? finalLat = pendingDoc.latitude ?? _document?.latitude;
|
|
final double? finalLng = pendingDoc.longitude ?? _document?.longitude;
|
|
|
|
final finalDoc = DocumentModel(
|
|
id: _document?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
|
|
tanggal: _tanggalCtrl.text,
|
|
noPo: _noPoCtrl.text,
|
|
noSo: _noSoCtrl.text,
|
|
noDo: _noDoCtrl.text,
|
|
kepadaYth: _kepadaYthCtrl.text,
|
|
orderUntuk: _orderUntukCtrl.text,
|
|
alamat: _alamatCtrl.text,
|
|
platTruk: _platTrukCtrl.text,
|
|
namaDriver: _namaDriverCtrl.text,
|
|
namaPenerima: _namaPenerimaCtrl.text,
|
|
latitude: finalLat,
|
|
longitude: finalLng,
|
|
items: _itemControllers.map((ctrls) => DocumentItem(
|
|
nomorSku: ctrls['sku']!.text,
|
|
namaBarang: ctrls['nama']!.text,
|
|
banyak: ctrls['banyak']!.text,
|
|
jumlah: ctrls['jumlah']!.text,
|
|
)).toList(),
|
|
createdAt: DateTime.now(),
|
|
);
|
|
|
|
// Save locally via Riverpod injected LocalStorage
|
|
final localStorage = ref.read(localStorageProvider);
|
|
await localStorage.saveDocument(finalDoc);
|
|
|
|
// Try to sync with server
|
|
try {
|
|
final apiClient = ApiClient();
|
|
await apiClient.client.put(
|
|
'${AppConfig.fetchDocumentsEndpoint}/${finalDoc.id}',
|
|
data: {
|
|
'tanggal': finalDoc.tanggal,
|
|
'noPo': finalDoc.noPo,
|
|
'noSo': finalDoc.noSo,
|
|
'noDo': finalDoc.noDo,
|
|
'kepadaYth': finalDoc.kepadaYth,
|
|
'orderUntuk': finalDoc.orderUntuk,
|
|
'alamat': finalDoc.alamat,
|
|
'platTruk': finalDoc.platTruk,
|
|
'namaDriver': finalDoc.namaDriver,
|
|
'namaPenerima': finalDoc.namaPenerima,
|
|
'latitude': finalDoc.latitude,
|
|
'longitude': finalDoc.longitude,
|
|
'items': finalDoc.items.map((e) => {
|
|
'nomor_sku': e.nomorSku,
|
|
'nama_barang': e.namaBarang,
|
|
'banyak': e.banyak,
|
|
'jumlah': e.jumlah,
|
|
}).toList(),
|
|
},
|
|
);
|
|
debugPrint('Document synced with server successfully');
|
|
} catch (e) {
|
|
debugPrint('Failed to sync document with server: $e. Saved locally (cached).');
|
|
}
|
|
|
|
// Remove from pending
|
|
if (widget.pendingId != null) {
|
|
ref.read(pendingDocumentsProvider.notifier).removeDocument(widget.pendingId!);
|
|
}
|
|
|
|
if (mounted) {
|
|
// Show SnackBar BEFORE pop so context is still valid
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Document Saved Successfully!'), backgroundColor: AppConfig.successColor),
|
|
);
|
|
// Pass true as result so DocumentsScreen's .then() knows to reload
|
|
context.pop(true);
|
|
}
|
|
}
|
|
|
|
Widget _buildTextField(
|
|
String label,
|
|
TextEditingController controller, {
|
|
TextInputType keyboardType = TextInputType.text,
|
|
bool readOnly = false,
|
|
VoidCallback? onTap,
|
|
List<TextInputFormatter>? inputFormatters,
|
|
String? Function(String?)? validator,
|
|
}) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 16.0),
|
|
child: TextFormField(
|
|
controller: controller,
|
|
keyboardType: keyboardType,
|
|
readOnly: readOnly,
|
|
onTap: onTap,
|
|
inputFormatters: inputFormatters,
|
|
style: AppConfig.bodyMedium,
|
|
decoration: InputDecoration(
|
|
labelText: label,
|
|
labelStyle: AppConfig.bodyMedium.copyWith(color: AppConfig.textSecondary),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: const BorderSide(color: AppConfig.primaryColor),
|
|
),
|
|
),
|
|
validator: validator ?? (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Please enter $label';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_document == null) {
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('Review Data')),
|
|
body: const Center(child: Text('Document not found or still processing')),
|
|
);
|
|
}
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Review & Edit Data'),
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back),
|
|
onPressed: () => context.pop(),
|
|
),
|
|
),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppConfig.primaryColor.withOpacity(0.05),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: AppConfig.primaryColor.withOpacity(0.2)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
const Icon(Icons.info_outline, color: AppConfig.primaryColor),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Text(
|
|
'Please review and edit the extracted data below to ensure accuracy before confirmation.',
|
|
style: AppConfig.bodyMedium.copyWith(color: AppConfig.primaryColor),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Header Details
|
|
Card(
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
side: BorderSide(color: Colors.grey.shade300),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Header Details', style: AppConfig.h2),
|
|
const SizedBox(height: 16),
|
|
_buildTextField(
|
|
'Tanggal',
|
|
_tanggalCtrl,
|
|
readOnly: true,
|
|
onTap: () async {
|
|
DateTime? pickedDate = await showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2000),
|
|
lastDate: DateTime(2101),
|
|
builder: (context, child) {
|
|
return Theme(
|
|
data: Theme.of(context).copyWith(
|
|
colorScheme: const ColorScheme.light(
|
|
primary: AppConfig.primaryColor,
|
|
onPrimary: Colors.white,
|
|
onSurface: AppConfig.textPrimary,
|
|
),
|
|
),
|
|
child: child!,
|
|
);
|
|
},
|
|
);
|
|
if (pickedDate != null) {
|
|
String formattedDate = DateFormat('dd MMMM yyyy').format(pickedDate);
|
|
_tanggalCtrl.text = formattedDate;
|
|
}
|
|
}
|
|
),
|
|
_buildTextField(
|
|
'No PO',
|
|
_noPoCtrl,
|
|
validator: (val) {
|
|
if (val == null || val.isEmpty) return 'Please enter No PO';
|
|
if (!RegExp(r'^PO/\d{2}/\d{10}$').hasMatch(val)) {
|
|
return 'Format harus PO/[XX]/[XXXXXXXXXX]';
|
|
}
|
|
return null;
|
|
}
|
|
),
|
|
_buildTextField(
|
|
'No SO',
|
|
_noSoCtrl,
|
|
keyboardType: TextInputType.number,
|
|
inputFormatters: [
|
|
FilteringTextInputFormatter.digitsOnly,
|
|
LengthLimitingTextInputFormatter(10),
|
|
],
|
|
validator: (val) {
|
|
if (val == null || val.isEmpty) return 'Please enter No SO';
|
|
if (val.length != 10) {
|
|
return 'SO harus persis 10 angka';
|
|
}
|
|
return null;
|
|
}
|
|
),
|
|
_buildTextField('No DO', _noDoCtrl),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Shipment Details
|
|
Card(
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
side: BorderSide(color: Colors.grey.shade300),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Shipment Details', style: AppConfig.h2),
|
|
const SizedBox(height: 16),
|
|
_buildTextField('Kepada Yth', _kepadaYthCtrl),
|
|
_buildTextField('Order Untuk', _orderUntukCtrl),
|
|
_buildTextField('Alamat', _alamatCtrl),
|
|
_buildTextField('Plat Truk', _platTrukCtrl),
|
|
_buildTextField('Nama Driver', _namaDriverCtrl),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Geotagging Details
|
|
Card(
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
side: BorderSide(color: Colors.grey.shade300),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Geotagging Details', style: AppConfig.h2),
|
|
const SizedBox(height: 16),
|
|
_buildTextField(
|
|
'Latitude',
|
|
_latitudeCtrl,
|
|
readOnly: true,
|
|
),
|
|
_buildTextField(
|
|
'Longitude',
|
|
_longitudeCtrl,
|
|
readOnly: true,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Items Section (Editable + CRUD)
|
|
Card(
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
side: BorderSide(color: Colors.grey.shade300),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text('Items (${_itemControllers.length})', style: AppConfig.h2),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
..._itemControllers.asMap().entries.map((entry) {
|
|
int index = entry.key;
|
|
var ctrls = entry.value;
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 16.0),
|
|
child: Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade50,
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: Colors.grey.shade200),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: ValueListenableBuilder<TextEditingValue>(
|
|
valueListenable: ctrls['nama']!,
|
|
builder: (context, value, child) {
|
|
return Text(
|
|
value.text.isNotEmpty ? value.text : 'Item Baru',
|
|
style: AppConfig.h3,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.delete_outline, color: Colors.red),
|
|
onPressed: () => _removeItem(index),
|
|
tooltip: 'Delete Item',
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
_buildTextField(
|
|
'SKU',
|
|
ctrls['sku']!,
|
|
validator: (val) {
|
|
if (val == null || val.isEmpty) {
|
|
return 'Please enter SKU';
|
|
}
|
|
if (!MasterSku.data.containsKey(val)) {
|
|
return 'SKU tidak terdaftar';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
_buildTextField('Nama Barang', ctrls['nama']!),
|
|
Row(
|
|
children: [
|
|
Expanded(child: _buildTextField('Banyak', ctrls['banyak']!)),
|
|
const SizedBox(width: 8),
|
|
Expanded(child: _buildTextField('Jumlah', ctrls['jumlah']!)),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
const SizedBox(height: 8),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: OutlinedButton.icon(
|
|
onPressed: () => _addItem(),
|
|
icon: const Icon(Icons.add),
|
|
label: const Text('Tambah Item Baru'),
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: AppConfig.primaryColor,
|
|
side: const BorderSide(color: AppConfig.primaryColor),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 32),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
bottomNavigationBar: SafeArea(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: SizedBox(
|
|
height: 54,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
onPressed: _showConfirmationDialog,
|
|
child: const Text('Review Complete', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_tanggalCtrl.dispose();
|
|
_noPoCtrl.dispose();
|
|
_noSoCtrl.dispose();
|
|
_noDoCtrl.dispose();
|
|
_kepadaYthCtrl.dispose();
|
|
_orderUntukCtrl.dispose();
|
|
_alamatCtrl.dispose();
|
|
_platTrukCtrl.dispose();
|
|
_namaDriverCtrl.dispose();
|
|
_namaPenerimaCtrl.dispose();
|
|
_latitudeCtrl.dispose();
|
|
_longitudeCtrl.dispose();
|
|
for (var ctrls in _itemControllers) {
|
|
ctrls['sku']?.dispose();
|
|
ctrls['nama']?.dispose();
|
|
ctrls['banyak']?.dispose();
|
|
ctrls['jumlah']?.dispose();
|
|
}
|
|
super.dispose();
|
|
}
|
|
}
|