import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:file_picker/file_picker.dart'; import 'package:intl/intl.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/loading_button.dart'; import '../../core/api/api_client.dart'; import '../../shared/models/contract.dart'; import '../../shared/providers/lookup_providers.dart'; import 'contracts_provider.dart'; class ContractUploadScreen extends ConsumerStatefulWidget { const ContractUploadScreen({super.key}); @override ConsumerState createState() => _ContractUploadScreenState(); } class _ContractUploadScreenState extends ConsumerState { int _step = 0; // 0: upload, 1: analyzing, 2: fill form File? _file; ContractAnalysis? _analysis; bool _saving = false; final _titleCtrl = TextEditingController(); DateTime? _periodStart; DateTime? _periodEnd; int? _companyId; int? _vendorId; final _formKey = GlobalKey(); @override void dispose() { _titleCtrl.dispose(); super.dispose(); } Future _pickFile() async { final result = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ['pdf'], ); if (result != null && result.files.single.path != null) { setState(() { _file = File(result.files.single.path!); }); } } Future _analyze() async { if (_file == null) return; setState(() => _step = 1); try { final analysis = await analyzePdf(_file!.path, _file!.path.split('/').last); _titleCtrl.text = analysis.title; if (analysis.periodStart != null) _periodStart = DateTime.tryParse(analysis.periodStart!); if (analysis.periodEnd != null) _periodEnd = DateTime.tryParse(analysis.periodEnd!); setState(() { _analysis = analysis; _step = 2; }); } on ApiException catch (e) { setState(() => _step = 0); if (mounted) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.message), backgroundColor: AppColors.danger)); } finally { } } Future _save() async { if (!_formKey.currentState!.validate()) return; setState(() => _saving = true); try { final contract = await createContract({ 'title': _titleCtrl.text.trim(), 'company_id': _companyId, 'vendor_id': _vendorId, if (_periodStart != null) 'period_start': DateFormat('yyyy-MM-dd').format(_periodStart!), if (_periodEnd != null) 'period_end': DateFormat('yyyy-MM-dd').format(_periodEnd!), if (_analysis != null) 'body': _analysis!.summary, }); // Upload the PDF file if (_file != null) { await uploadContractFile(contract.id, _file!.path, _file!.path.split('/').last); } if (mounted) { context.pop(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Contract saved successfully'), backgroundColor: AppColors.primary)); } } on ApiException catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.message), backgroundColor: AppColors.danger)); } finally { if (mounted) setState(() => _saving = false); } } Future _pickDate(bool isStart) async { final picked = await showDatePicker( context: context, initialDate: (isStart ? _periodStart : _periodEnd) ?? DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2035), ); if (picked != null) setState(() => isStart ? _periodStart = picked : _periodEnd = picked); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Upload Contract')), body: Column( children: [ // Step indicator Padding( padding: const EdgeInsets.all(16), child: Row( children: List.generate(3, (i) { final done = i < _step; final active = i == _step; final labels = ['Upload PDF', 'AI Analysis', 'Fill Form']; return Expanded(child: Row(children: [ if (i > 0) Expanded(child: Container(height: 2, color: done ? AppColors.primary : AppColors.border)), Column(children: [ CircleAvatar( radius: 14, backgroundColor: active || done ? AppColors.primary : AppColors.border, child: done ? const Icon(Icons.check, size: 14, color: Colors.white) : Text('${i + 1}', style: TextStyle( color: active ? Colors.white : AppColors.textSub, fontSize: 12, fontWeight: FontWeight.w700)), ), const SizedBox(height: 4), Text(labels[i], style: TextStyle( fontSize: 10, color: active || done ? AppColors.primary : AppColors.textSub, fontWeight: FontWeight.w600)), ]), if (i < 2) Expanded(child: Container(height: 2, color: done ? AppColors.primary : AppColors.border)), ])); }), ), ), const Divider(height: 1), Expanded(child: _buildStep()), ], ), ); } Widget _buildStep() { return switch (_step) { 0 => _buildUploadStep(), 1 => _buildAnalyzingStep(), 2 => _buildFormStep(), _ => const SizedBox(), }; } Widget _buildUploadStep() => Padding( padding: const EdgeInsets.all(24), child: Column( children: [ GestureDetector( onTap: _pickFile, child: Container( width: double.infinity, height: 180, decoration: BoxDecoration( color: AppColors.primaryLight, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.primary, style: BorderStyle.solid, width: 2), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(_file != null ? Icons.picture_as_pdf : Icons.upload_file, size: 48, color: AppColors.primary), const SizedBox(height: 12), Text( _file != null ? _file!.path.split('/').last : 'Tap to select PDF', style: TextStyle(color: AppColors.primary, fontWeight: FontWeight.w600, fontSize: _file != null ? 13 : 15), textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, ), if (_file != null) ...[ const SizedBox(height: 6), TextButton(onPressed: _pickFile, child: const Text('Change file')), ], ], ), ), ), const SizedBox(height: 32), LoadingButton( loading: false, label: 'Analisis dengan AI', onPressed: _file != null ? _analyze : null, ), ], ), ); Widget _buildAnalyzingStep() => Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ const CircularProgressIndicator(color: AppColors.primary, strokeWidth: 3), const SizedBox(height: 24), const Text('Analyzing contract with AI...', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), const SizedBox(height: 8), const Text('This may take 30–60 seconds for scanned documents', textAlign: TextAlign.center, style: TextStyle(fontSize: 13, color: AppColors.textSub)), ], ), ), ); Widget _buildFormStep() { final companiesAsync = ref.watch(companiesProvider); final vendorsAsync = ref.watch(vendorsProvider); return Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ if (_analysis != null) Container( padding: const EdgeInsets.all(12), margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: AppColors.primaryLight, borderRadius: BorderRadius.circular(10), ), child: Row(children: [ const Icon(Icons.auto_awesome, color: AppColors.primary, size: 16), const SizedBox(width: 8), const Expanded(child: Text('Data has been auto-filled from AI analysis', style: TextStyle(fontSize: 13, color: AppColors.primaryDark, fontWeight: FontWeight.w600))), ]), ), _label('Contract Title'), TextFormField( controller: _titleCtrl, decoration: const InputDecoration(hintText: 'Contract title'), validator: (v) => (v == null || v.isEmpty) ? 'Title is required' : null, ), const SizedBox(height: 16), _label('Company'), companiesAsync.when( loading: () => const LinearProgressIndicator(), error: (_, __) => const Text('Failed'), data: (list) => DropdownButtonFormField( value: _companyId, decoration: const InputDecoration(hintText: 'Select company'), items: list.map((c) => DropdownMenuItem(value: c.id, child: Text(c.name))).toList(), onChanged: (v) => setState(() => _companyId = v), ), ), const SizedBox(height: 16), _label('Vendor (optional)'), vendorsAsync.when( loading: () => const LinearProgressIndicator(), error: (_, __) => const Text('Failed'), data: (list) => DropdownButtonFormField( value: _vendorId, decoration: const InputDecoration(hintText: 'Select vendor'), items: [ const DropdownMenuItem(value: null, child: Text('None')), ...list.map((v) => DropdownMenuItem(value: v.id, child: Text(v.name))), ], onChanged: (v) => setState(() => _vendorId = v), ), ), const SizedBox(height: 16), Row(children: [ Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ _label('Start Date'), _datePicker(_periodStart, () => _pickDate(true), 'Select date'), ])), const SizedBox(width: 12), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ _label('End Date'), _datePicker(_periodEnd, () => _pickDate(false), 'Select date'), ])), ]), const SizedBox(height: 32), LoadingButton(loading: _saving, label: 'Save Contract', onPressed: _save), ], ), ); } Widget _datePicker(DateTime? date, VoidCallback onTap, String hint) => GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), decoration: BoxDecoration( color: AppColors.surface, border: Border.all(color: AppColors.border), borderRadius: BorderRadius.circular(10), ), child: Text( date != null ? DateFormat('d MMM yyyy').format(date) : hint, style: TextStyle(fontSize: 13, color: date != null ? AppColors.text : AppColors.textSub), ), ), ); Widget _label(String text) => Padding( padding: const EdgeInsets.only(bottom: 6), child: Text(text, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.text)), ); }