This commit is contained in:
2026-10-01 16:03:32 +05:30
commit 3db47c4648
102 changed files with 5067 additions and 0 deletions
+224
View File
@@ -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)),
],
),
);
}
}