import 'dart:io'; import 'package:flutter/material.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'; // For localStorageProvider import 'pending_documents_provider.dart'; import '../editor/pdf_service.dart'; import '../../core/network/api_client.dart'; class DocumentsScreen extends ConsumerStatefulWidget { const DocumentsScreen({super.key}); @override ConsumerState createState() => _DocumentsScreenState(); } class _DocumentsScreenState extends ConsumerState { final _searchController = TextEditingController(); List _allDocuments = []; List _filteredDocuments = []; @override void initState() { super.initState(); _loadDocuments(); _searchController.addListener(_onSearchChanged); } Future _loadDocuments() async { final localStorage = ref.read(localStorageProvider); final docs = localStorage.getAllDocuments(); setState(() { _allDocuments = docs; _filteredDocuments = docs; }); try { final apiClient = ApiClient(); final response = await apiClient.client.get(AppConfig.fetchDocumentsEndpoint); final List serverData = response.data['data'] ?? []; final List serverDocs = serverData .map((json) => DocumentModel.fromJson(Map.from(json))) .toList(); await localStorage.documentBox.clear(); for (var doc in serverDocs) { await localStorage.saveDocument(doc); } final updatedDocs = localStorage.getAllDocuments(); if (mounted) { setState(() { _allDocuments = updatedDocs; _filteredDocuments = updatedDocs; }); } } catch (e) { debugPrint('Offline or failed fetching from server: $e'); } } void _onSearchChanged() { final query = _searchController.text.toLowerCase(); setState(() { _filteredDocuments = _allDocuments.where((doc) { return doc.noDo.toLowerCase().contains(query) || doc.noPo.toString().contains(query) || doc.noSo.toString().contains(query) || doc.tanggal.toLowerCase().contains(query) || doc.kepadaYth.toLowerCase().contains(query); }).toList(); }); } @override void dispose() { _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final pendingDocs = ref.watch(pendingDocumentsProvider); return Scaffold( appBar: AppBar( title: const Text('Documents'), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.go('/camera'), ), ), body: Column( children: [ // Sticky Search Bar Container( padding: const EdgeInsets.all(16.0), color: AppConfig.surfaceColor, child: TextField( controller: _searchController, decoration: const InputDecoration( hintText: 'Search by No PO, No SO, DO, Date...', prefixIcon: Icon(Icons.search), ), ), ), // List of documents Expanded( child: (pendingDocs.isEmpty && _filteredDocuments.isEmpty) ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.folder_open, size: 64, color: AppConfig.textSecondary.withOpacity(0.5)), const SizedBox(height: 16), Text('No documents found', style: AppConfig.bodyLarge), ], ), ) : ListView( padding: const EdgeInsets.all(16.0), children: [ if (pendingDocs.isNotEmpty) ...[ Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Text('Pending & Processing', style: AppConfig.h3), ), ...pendingDocs.map((doc) => _buildPendingCard(doc)), const SizedBox(height: 16), const Divider(), const SizedBox(height: 16), ], if (_filteredDocuments.isNotEmpty) ...[ Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Text('Confirmed Documents', style: AppConfig.h3), ), ..._filteredDocuments.map((doc) => _buildDocumentCard(doc)), ], ], ), ), ], ), ); } Widget _buildPendingCard(PendingDocument doc) { Color statusColor; String statusText; IconData statusIcon; switch (doc.status) { case PendingDocumentStatus.uploading: case PendingDocumentStatus.processing: statusColor = Colors.orange; statusText = 'Sedang memproses dokumen...'; statusIcon = Icons.cloud_upload; break; case PendingDocumentStatus.success: statusColor = AppConfig.successColor; statusText = 'Ketuk untuk dikonfirmasi'; statusIcon = Icons.check_circle; break; case PendingDocumentStatus.error: statusColor = AppConfig.errorColor; statusText = 'Gagal Memproses'; statusIcon = Icons.error; break; } return Card( elevation: 0, margin: const EdgeInsets.only(bottom: 12.0), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide(color: statusColor.withOpacity(0.5)), ), child: InkWell( onTap: () { if (doc.status == PendingDocumentStatus.success) { context.push('/editor', extra: doc.id).then((_) { _loadDocuments(); }); } else if (doc.status == PendingDocumentStatus.error) { // Show error options showModalBottomSheet( context: context, builder: (ctx) => SafeArea( child: Wrap( children: [ ListTile( leading: const Icon(Icons.refresh), title: const Text('Retry Upload'), onTap: () { Navigator.pop(ctx); ref.read(pendingDocumentsProvider.notifier).retryUpload(doc.id); }, ), ListTile( leading: const Icon(Icons.delete, color: AppConfig.errorColor), title: const Text('Delete Document', style: TextStyle(color: AppConfig.errorColor)), onTap: () { Navigator.pop(ctx); ref.read(pendingDocumentsProvider.notifier).removeDocument(doc.id); }, ), ], ), ), ); } }, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.file( File(doc.imagePath), width: 60, height: 60, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Container( width: 60, height: 60, color: Colors.grey.shade200, child: const Icon(Icons.image, color: Colors.grey), ), ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(statusIcon, size: 16, color: statusColor), const SizedBox(width: 4), Expanded( child: Text( statusText, style: AppConfig.bodyMedium.copyWith(color: statusColor, fontWeight: FontWeight.bold), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 8), if (doc.status == PendingDocumentStatus.uploading || doc.status == PendingDocumentStatus.processing) const LinearProgressIndicator(), if (doc.status == PendingDocumentStatus.success) Text('Ketuk untuk mereview dan konfirmasi data', style: AppConfig.bodySmall), if (doc.status == PendingDocumentStatus.error) Text(doc.errorMessage ?? 'Unknown error', style: AppConfig.bodySmall.copyWith(color: AppConfig.errorColor), maxLines: 1, overflow: TextOverflow.ellipsis), ], ), ), if (doc.status == PendingDocumentStatus.success) const Icon(Icons.chevron_right, color: AppConfig.primaryColor), ], ), ), ), ); } Widget _buildDocumentCard(DocumentModel doc) { return Card( elevation: 4, shadowColor: Colors.black.withOpacity(0.05), margin: const EdgeInsets.only(bottom: 16.0), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide(color: Colors.grey.shade200), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( 'DO: ${doc.noDo}', style: AppConfig.h3.copyWith(fontWeight: FontWeight.bold), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: AppConfig.successColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.check_circle, size: 14, color: AppConfig.successColor), const SizedBox(width: 4), Text('Confirmed', style: AppConfig.bodySmall.copyWith(color: AppConfig.successColor, fontWeight: FontWeight.bold)), ], ), ), ], ), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade100), ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded(child: _buildInfoItem(Icons.receipt_long, 'PO: ${doc.noPo}')), Expanded(child: _buildInfoItem(Icons.inventory_2, 'SO: ${doc.noSo}')), ], ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded(child: _buildInfoItem(Icons.calendar_today, doc.tanggal)), Expanded(child: _buildInfoItem(Icons.person, doc.kepadaYth)), ], ), if (doc.latitude != null && doc.longitude != null) ...[ const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded(child: _buildInfoItem(Icons.location_on, 'Lat: ${doc.latitude!.toStringAsFixed(4)}')), Expanded(child: _buildInfoItem(Icons.location_on, 'Lng: ${doc.longitude!.toStringAsFixed(4)}')), ], ), ], ], ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: () => _printReceipt(doc), icon: const Icon(Icons.print, color: AppConfig.primaryColor), label: const Text('Print Receipt', style: TextStyle(color: AppConfig.primaryColor, fontWeight: FontWeight.bold)), style: OutlinedButton.styleFrom( side: const BorderSide(color: AppConfig.primaryColor), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), ], ), ), ); } Widget _buildInfoItem(IconData icon, String text) { return Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 14, color: AppConfig.textSecondary), const SizedBox(width: 6), Expanded( child: Text( text, style: AppConfig.bodyMedium, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ); } Future _printReceipt(DocumentModel doc) async { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Generating PDF...'), duration: Duration(seconds: 1), backgroundColor: AppConfig.secondaryColor, ), ); await PdfService.generateAndPrintReceipt(doc); } }