Add mobile reliability fixes, Bahasa Indonesia UI, and continue OCR accuracy tuning
Reliability/PoC hardening: dedupe uploads by file_hash, surface editor sync failures instead of a false success SnackBar with a retry-without-re-OCR path, bound the OCR pipeline fetches with timeouts, share a single ApiClient/Dio instance app-wide, tune capture JPEG quality, and add an opt-in docker-compose.demo.yml for a production-mode run ahead of client demos. Translate all Flutter-side user-facing text (screens, validators, SnackBars, the printed delivery receipt, and shared API error messages) to Bahasa Indonesia. Also includes in-progress OCR parser/accuracy-tuning work from the same session: table column/unit normalization fixes, store/customer master data, accuracy history log, and test-image renaming/cleanup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eRAsLqN9Sg1b9YPz22Lxz
This commit is contained in:
1 parent
095dd4cb8b
commit
4808a798fb
78 files changed
+8069
-584
No files matched your search
@@ -37,7 +37,7 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
});
|
||||
|
||||
try {
|
||||
final apiClient = ApiClient();
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
final response = await apiClient.client.get(AppConfig.fetchDocumentsEndpoint);
|
||||
|
||||
final List serverData = response.data['data'] ?? [];
|
||||
@@ -88,7 +88,7 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Documents'),
|
||||
title: const Text('Dokumen'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => context.go('/camera'),
|
||||
@@ -103,7 +103,7 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Search by No PO, No SO, DO, Date...',
|
||||
hintText: 'Cari berdasarkan No PO, No SO, DO, Tanggal...',
|
||||
prefixIcon: Icon(Icons.search),
|
||||
),
|
||||
),
|
||||
@@ -118,7 +118,7 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
children: [
|
||||
Icon(Icons.folder_open, size: 64, color: AppConfig.textSecondary.withOpacity(0.5)),
|
||||
const SizedBox(height: 16),
|
||||
Text('No documents found', style: AppConfig.bodyLarge),
|
||||
Text('Tidak ada dokumen ditemukan', style: AppConfig.bodyLarge),
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -128,7 +128,7 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
if (pendingDocs.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text('Pending & Processing', style: AppConfig.h3),
|
||||
child: Text('Tertunda & Diproses', style: AppConfig.h3),
|
||||
),
|
||||
...pendingDocs.map((doc) => _buildPendingCard(doc)),
|
||||
const SizedBox(height: 16),
|
||||
@@ -138,7 +138,7 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
if (_filteredDocuments.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text('Confirmed Documents', style: AppConfig.h3),
|
||||
child: Text('Dokumen Terkonfirmasi', style: AppConfig.h3),
|
||||
),
|
||||
..._filteredDocuments.map((doc) => _buildDocumentCard(doc)),
|
||||
],
|
||||
@@ -172,6 +172,11 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
statusText = 'Gagal Memproses';
|
||||
statusIcon = Icons.error;
|
||||
break;
|
||||
case PendingDocumentStatus.syncFailed:
|
||||
statusColor = Colors.deepOrange;
|
||||
statusText = 'Belum tersinkron ke server';
|
||||
statusIcon = Icons.sync_problem;
|
||||
break;
|
||||
}
|
||||
|
||||
return Card(
|
||||
@@ -198,7 +203,7 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.refresh),
|
||||
title: const Text('Retry Upload'),
|
||||
title: const Text('Coba Unggah Ulang'),
|
||||
onTap: () {
|
||||
Navigator.pop(ctx);
|
||||
ref.read(pendingDocumentsProvider.notifier).retryUpload(doc.id);
|
||||
@@ -206,7 +211,35 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete, color: AppConfig.errorColor),
|
||||
title: const Text('Delete Document', style: TextStyle(color: AppConfig.errorColor)),
|
||||
title: const Text('Hapus Dokumen', style: TextStyle(color: AppConfig.errorColor)),
|
||||
onTap: () {
|
||||
Navigator.pop(ctx);
|
||||
ref.read(pendingDocumentsProvider.notifier).removeDocument(doc.id);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (doc.status == PendingDocumentStatus.syncFailed) {
|
||||
// The correction was saved locally but never reached the server - retry
|
||||
// just the sync (no re-upload, no re-running OCR).
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (ctx) => SafeArea(
|
||||
child: Wrap(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.sync),
|
||||
title: const Text('Coba Sinkron Ulang'),
|
||||
onTap: () {
|
||||
Navigator.pop(ctx);
|
||||
ref.read(pendingDocumentsProvider.notifier).retrySync(doc.id);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete, color: AppConfig.errorColor),
|
||||
title: const Text('Hapus Dokumen', style: TextStyle(color: AppConfig.errorColor)),
|
||||
onTap: () {
|
||||
Navigator.pop(ctx);
|
||||
ref.read(pendingDocumentsProvider.notifier).removeDocument(doc.id);
|
||||
@@ -263,7 +296,9 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
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),
|
||||
Text(doc.errorMessage ?? 'Kesalahan tidak diketahui', style: AppConfig.bodySmall.copyWith(color: AppConfig.errorColor), maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
if (doc.status == PendingDocumentStatus.syncFailed)
|
||||
Text(doc.errorMessage ?? 'Ketuk untuk coba sinkron ulang', style: AppConfig.bodySmall.copyWith(color: Colors.deepOrange), maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -312,7 +347,7 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
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)),
|
||||
Text('Terkonfirmasi', style: AppConfig.bodySmall.copyWith(color: AppConfig.successColor, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -362,7 +397,7 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
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)),
|
||||
label: const Text('Cetak Tanda Terima', style: TextStyle(color: AppConfig.primaryColor, fontWeight: FontWeight.bold)),
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: const BorderSide(color: AppConfig.primaryColor),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
@@ -397,7 +432,7 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen> {
|
||||
Future<void> _printReceipt(DocumentModel doc) async {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Generating PDF...'),
|
||||
content: Text('Membuat PDF...'),
|
||||
duration: Duration(seconds: 1),
|
||||
backgroundColor: AppConfig.secondaryColor,
|
||||
),
|
||||
|
||||
Reference in new issue
Block a user