init
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../labels.dart';
|
||||
import '../models/models.dart';
|
||||
import '../services/auth_controller.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
import '../ui/feedback.dart';
|
||||
|
||||
class StockDetailScreen extends StatefulWidget {
|
||||
const StockDetailScreen({super.key, required this.stock, required this.dealers});
|
||||
|
||||
final StockItem stock;
|
||||
final List<Dealer> dealers;
|
||||
|
||||
@override
|
||||
State<StockDetailScreen> createState() => _StockDetailScreenState();
|
||||
}
|
||||
|
||||
class _StockDetailScreenState extends State<StockDetailScreen> {
|
||||
late StockItem _stock = widget.stock;
|
||||
String _paymentType = 'cash';
|
||||
bool _useGci = true;
|
||||
bool _useFinance = false;
|
||||
String? _transferDealerId;
|
||||
final _financeName = TextEditingController();
|
||||
final _plateNumber = TextEditingController();
|
||||
bool _saving = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_financeName.dispose();
|
||||
_plateNumber.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _advance() async {
|
||||
final stage = nextStage(_stock.currentStage);
|
||||
if (stage == null) return;
|
||||
final body = <String, dynamic>{'stage': stage};
|
||||
if (stage == 'paymentApproved') {
|
||||
body['paymentType'] = _paymentType;
|
||||
if (_paymentType == 'lease') {
|
||||
if (!_useGci && !_useFinance) {
|
||||
showError(context, 'Choose GCI, finance, or both');
|
||||
return;
|
||||
}
|
||||
body['leaseProviderType'] = _useGci && _useFinance
|
||||
? 'both'
|
||||
: _useGci
|
||||
? 'gci'
|
||||
: 'thirdPartyFinance';
|
||||
if (_useFinance) body['financeCompanyName'] = _financeName.text.trim();
|
||||
}
|
||||
}
|
||||
if (stage == 'plateReceived') {
|
||||
final plateNumber = _plateNumber.text.trim();
|
||||
if (plateNumber.isEmpty) {
|
||||
showError(context, 'Enter the plate number');
|
||||
return;
|
||||
}
|
||||
body['plateNumber'] = plateNumber;
|
||||
}
|
||||
setState(() => _saving = true);
|
||||
try {
|
||||
final updated = await context.read<AuthController>().api.updateStage(_stock.chassisNumber, body);
|
||||
if (!mounted) return;
|
||||
setState(() => _stock = updated);
|
||||
} catch (error) {
|
||||
if (mounted) showError(context, error);
|
||||
} finally {
|
||||
if (mounted) setState(() => _saving = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _transfer() async {
|
||||
final dealerId = _transferDealerId;
|
||||
if (dealerId == null) return;
|
||||
setState(() => _saving = true);
|
||||
try {
|
||||
final updated = await context.read<AuthController>().api.transferStock(_stock.chassisNumber, dealerId);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_stock = updated;
|
||||
_transferDealerId = null;
|
||||
});
|
||||
} catch (error) {
|
||||
if (mounted) showError(context, error);
|
||||
} finally {
|
||||
if (mounted) setState(() => _saving = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final role = context.watch<AuthController>().user?.role;
|
||||
final upcoming = nextStage(_stock.currentStage);
|
||||
final targets = widget.dealers.where((dealer) => dealer.id != _stock.currentDealerId).toList();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(_stock.chassisNumber)),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: badgeColor(_stock.badge),
|
||||
child: Text('${_stock.ageInDays}', style: const TextStyle(color: Colors.white, fontSize: 12)),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${_stock.ageInDays} days in stock · ${stageLabel(_stock.currentStage)}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_line('Model', modelLabel(_stock.model)),
|
||||
_line('Color', _stock.color),
|
||||
_line('Engine', _stock.engineNumber),
|
||||
if (_stock.plateNumber != null) _line('Plate', _stock.plateNumber!),
|
||||
_line('Dealer', _stock.dealerName ?? _stock.currentDealerId),
|
||||
_line('Received', _stock.dateReceived.toLocal().toString().split(' ').first),
|
||||
if (_stock.paymentType != null) _line('Payment', _stock.paymentType!),
|
||||
if (_stock.leaseProviderType != null)
|
||||
_line('Lease', leaseProviderLabel(_stock.leaseProviderType!)),
|
||||
if (_stock.financeCompanyName != null) _line('Finance company', _stock.financeCompanyName!),
|
||||
const SizedBox(height: 16),
|
||||
Text('Stage log', style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 8),
|
||||
for (final stage in stageOrder)
|
||||
if (_stock.stageLogDates.containsKey(stage))
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(stageLabel(stage)),
|
||||
trailing: Text(_stock.stageLogDates[stage]!.toLocal().toString().split(' ').first),
|
||||
),
|
||||
if (upcoming != null) ...[
|
||||
const Divider(),
|
||||
Text('Advance to ${stageLabel(upcoming)}', style: Theme.of(context).textTheme.titleMedium),
|
||||
if (upcoming == 'paymentApproved') ...[
|
||||
const SizedBox(height: 12),
|
||||
SegmentedButton<String>(
|
||||
segments: const [
|
||||
ButtonSegment(value: 'cash', label: Text('Cash')),
|
||||
ButtonSegment(value: 'lease', label: Text('Lease')),
|
||||
],
|
||||
selected: {_paymentType},
|
||||
onSelectionChanged: (value) => setState(() => _paymentType = value.first),
|
||||
),
|
||||
if (_paymentType == 'lease') ...[
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: const Text('GCI'),
|
||||
value: _useGci,
|
||||
onChanged: (value) => setState(() => _useGci = value ?? false),
|
||||
),
|
||||
CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: const Text('Finance'),
|
||||
value: _useFinance,
|
||||
onChanged: (value) => setState(() => _useFinance = value ?? false),
|
||||
),
|
||||
if (_useFinance)
|
||||
TextField(
|
||||
controller: _financeName,
|
||||
decoration: const InputDecoration(labelText: 'Finance company'),
|
||||
),
|
||||
],
|
||||
],
|
||||
if (upcoming == 'plateReceived') ...[
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _plateNumber,
|
||||
textCapitalization: TextCapitalization.characters,
|
||||
decoration: const InputDecoration(labelText: 'Plate number'),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
FilledButton(
|
||||
onPressed: _saving ? null : _advance,
|
||||
child: Text(_saving ? 'Saving' : 'Update stage'),
|
||||
),
|
||||
],
|
||||
if (role != 'dealer' && targets.isNotEmpty) ...[
|
||||
const Divider(),
|
||||
Text('Transfer', style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: _transferDealerId,
|
||||
decoration: const InputDecoration(labelText: 'Destination dealer'),
|
||||
items: [
|
||||
for (final dealer in targets)
|
||||
DropdownMenuItem(value: dealer.id, child: Text('${dealer.name} (${dealer.code})')),
|
||||
],
|
||||
onChanged: (value) => setState(() => _transferDealerId = value),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
OutlinedButton(
|
||||
onPressed: _saving || _transferDealerId == null ? null : _transfer,
|
||||
child: const Text('Transfer unit'),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _line(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(width: 140, child: Text(label)),
|
||||
Expanded(child: Text(value)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user