225 lines
8.3 KiB
Dart
225 lines
8.3 KiB
Dart
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)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|