From 72a464b4c074d1e76e86fd6ef7fba60ce4d617a5 Mon Sep 17 00:00:00 2001 From: efrilm Date: Sat, 20 Sep 2025 04:45:08 +0700 Subject: [PATCH] data sync page --- .../data_sync/bloc/data_sync_bloc.dart | 89 ++++++++++++++----- .../data_sync/pages/data_sync_page.dart | 46 ++++------ 2 files changed, 85 insertions(+), 50 deletions(-) diff --git a/lib/presentation/data_sync/bloc/data_sync_bloc.dart b/lib/presentation/data_sync/bloc/data_sync_bloc.dart index b3ab86c..6a3f47e 100644 --- a/lib/presentation/data_sync/bloc/data_sync_bloc.dart +++ b/lib/presentation/data_sync/bloc/data_sync_bloc.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'dart:developer'; import 'package:bloc/bloc.dart'; import 'package:enaklo_pos/data/datasources/product/product_local_datasource.dart'; +import 'package:enaklo_pos/data/datasources/category/category_local_datasource.dart'; +import 'package:enaklo_pos/data/repositories/category/category_repository.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import '../../../data/datasources/product_remote_datasource.dart'; @@ -9,7 +11,7 @@ part 'data_sync_event.dart'; part 'data_sync_state.dart'; part 'data_sync_bloc.freezed.dart'; -enum SyncStep { products, categories, variants, completed } +enum SyncStep { categories, products, variants, completed } class SyncStats { final int totalProducts; @@ -26,9 +28,13 @@ class SyncStats { } class DataSyncBloc extends Bloc { - final ProductRemoteDatasource _remoteDatasource = ProductRemoteDatasource(); - final ProductLocalDatasource _localDatasource = + final ProductRemoteDatasource _productRemoteDatasource = + ProductRemoteDatasource(); + final ProductLocalDatasource _productLocalDatasource = ProductLocalDatasource.instance; + final CategoryLocalDatasource _categoryLocalDatasource = + CategoryLocalDatasource.instance; + final CategoryRepository _categoryRepository = CategoryRepository.instance; Timer? _progressTimer; bool _isCancelled = false; @@ -48,36 +54,75 @@ class DataSyncBloc extends Bloc { _StartSync event, Emitter emit, ) async { - log('🔄 Starting data sync...'); + log('🔄 Starting full data sync (categories + products)...'); _isCancelled = false; try { // Step 1: Clear existing local data emit(const DataSyncState.syncing( - SyncStep.products, 0.1, 'Membersihkan data lama...')); - await _localDatasource.clearAllProducts(); + SyncStep.categories, 0.05, 'Membersihkan data lama...')); + + await _productLocalDatasource.clearAllProducts(); + await _categoryLocalDatasource.clearAllCategories(); if (_isCancelled) return; - // Step 2: Sync products + // Step 2: Sync categories first (products depend on categories) + await _syncCategories(emit); + + if (_isCancelled) return; + + // Step 3: Sync products await _syncProducts(emit); if (_isCancelled) return; - // Step 3: Generate final stats + // Step 4: Generate final stats emit(const DataSyncState.syncing( - SyncStep.completed, 0.9, 'Menyelesaikan sinkronisasi...')); + SyncStep.completed, 0.95, 'Menyelesaikan sinkronisasi...')); final stats = await _generateSyncStats(); emit(DataSyncState.completed(stats)); - log('✅ Sync completed successfully'); + log('✅ Full sync completed successfully'); } catch (e) { log('❌ Sync failed: $e'); emit(DataSyncState.error('Gagal sinkronisasi: $e')); } } + Future _syncCategories(Emitter emit) async { + log('📁 Syncing categories...'); + + emit(const DataSyncState.syncing( + SyncStep.categories, + 0.1, + 'Mengunduh kategori...', + )); + + try { + // Use CategoryRepository sync method + final result = await _categoryRepository.syncAllCategories(); + + await result.fold( + (failure) async { + throw Exception('Gagal sync kategori: $failure'); + }, + (successMessage) async { + log('✅ Categories sync completed: $successMessage'); + emit(const DataSyncState.syncing( + SyncStep.categories, + 0.2, + 'Kategori berhasil diunduh', + )); + }, + ); + } catch (e) { + log('❌ Category sync failed: $e'); + throw Exception('Gagal sync kategori: $e'); + } + } + Future _syncProducts(Emitter emit) async { log('📦 Syncing products...'); @@ -88,10 +133,10 @@ class DataSyncBloc extends Bloc { bool shouldContinue = true; while (!_isCancelled && shouldContinue) { - // Calculate accurate progress based on total count + // Calculate accurate progress (categories = 0.2, products = 0.2-0.9) double progress = 0.2; if (totalCount != null && (totalCount ?? 0) > 0) { - progress = 0.2 + (totalSynced / (totalCount ?? 0)) * 0.6; + progress = 0.2 + (totalSynced / (totalCount ?? 0)) * 0.7; } emit(DataSyncState.syncing( @@ -102,7 +147,7 @@ class DataSyncBloc extends Bloc { : 'Mengunduh produk... ($totalSynced produk)', )); - final result = await _remoteDatasource.getProducts( + final result = await _productRemoteDatasource.getProducts( page: page, limit: 50, // Bigger batch for sync ); @@ -128,7 +173,7 @@ class DataSyncBloc extends Bloc { } // Save to local database in batches - await _localDatasource.saveProductsBatch(products); + await _productLocalDatasource.saveProductsBatch(products); totalSynced += products.length; page++; @@ -154,8 +199,8 @@ class DataSyncBloc extends Bloc { } emit(DataSyncState.syncing( - SyncStep.completed, - 0.8, + SyncStep.products, + 0.9, 'Produk berhasil diunduh ($totalSynced dari ${totalCount ?? totalSynced})', )); @@ -163,13 +208,15 @@ class DataSyncBloc extends Bloc { } Future _generateSyncStats() async { - final dbStats = await _localDatasource.getDatabaseStats(); + final productStats = await _productLocalDatasource.getDatabaseStats(); + final categoryStats = await _categoryLocalDatasource.getDatabaseStats(); return SyncStats( - totalProducts: dbStats['total_products'] ?? 0, - totalCategories: dbStats['total_categories'] ?? 0, - totalVariants: dbStats['total_variants'] ?? 0, - databaseSizeMB: dbStats['database_size_mb'] ?? 0.0, + totalProducts: productStats['total_products'] ?? 0, + totalCategories: categoryStats['total_categories'] ?? 0, + totalVariants: productStats['total_variants'] ?? 0, + databaseSizeMB: (productStats['database_size_mb'] ?? 0.0) + + (categoryStats['database_size_mb'] ?? 0.0), ); } diff --git a/lib/presentation/data_sync/pages/data_sync_page.dart b/lib/presentation/data_sync/pages/data_sync_page.dart index 27ae572..f1408ca 100644 --- a/lib/presentation/data_sync/pages/data_sync_page.dart +++ b/lib/presentation/data_sync/pages/data_sync_page.dart @@ -87,20 +87,15 @@ class _DataSyncPageState extends State ); } - // Portrait layout (original) + // Portrait layout Widget _buildPortraitLayout(DataSyncState state, double screenHeight) { return Padding( padding: EdgeInsets.all(24), child: Column( children: [ - SizedBox(height: screenHeight * 0.08), // Responsive spacing - - // Header - _buildHeader(false), - SizedBox(height: screenHeight * 0.08), - - // Sync progress + _buildHeader(false), + SizedBox(height: screenHeight * 0.08), Expanded( child: state.when( initial: () => _buildInitialState(false), @@ -110,24 +105,20 @@ class _DataSyncPageState extends State error: (message) => _buildErrorState(message, false), ), ), - SizedBox(height: 20), - - // Actions _buildActions(state), ], ), ); } - // Landscape layout (side by side) + // Landscape layout Widget _buildLandscapeLayout( DataSyncState state, double screenWidth, double screenHeight) { return Padding( padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16), child: Row( children: [ - // Left side - Header and info Expanded( flex: 2, child: Column( @@ -139,10 +130,7 @@ class _DataSyncPageState extends State ], ), ), - SizedBox(width: 40), - - // Right side - Sync progress Expanded( flex: 3, child: Container( @@ -188,7 +176,7 @@ class _DataSyncPageState extends State ), SizedBox(height: isLandscape ? 4 : 8), Text( - 'Mengunduh data terbaru ke perangkat', + 'Mengunduh kategori dan produk terbaru', style: TextStyle( fontSize: isLandscape ? 14 : 16, color: Colors.grey.shade600, @@ -319,8 +307,8 @@ class _DataSyncPageState extends State Widget _buildStepIndicator(SyncStep currentStep, bool isLandscape) { final steps = [ - ('Produk', SyncStep.products, Icons.inventory_2), ('Kategori', SyncStep.categories, Icons.category), + ('Produk', SyncStep.products, Icons.inventory_2), ('Variant', SyncStep.variants, Icons.tune), ('Selesai', SyncStep.completed, Icons.check_circle), ]; @@ -551,11 +539,11 @@ class _DataSyncPageState extends State // Vertical layout for landscape Column( children: [ - _buildStatItem('Produk', '${stats.totalProducts}', - Icons.inventory_2, Colors.blue, isLandscape), - SizedBox(height: 8), _buildStatItem('Kategori', '${stats.totalCategories}', - Icons.category, Colors.green, isLandscape), + Icons.category, Colors.blue, isLandscape), + SizedBox(height: 8), + _buildStatItem('Produk', '${stats.totalProducts}', + Icons.inventory_2, Colors.green, isLandscape), SizedBox(height: 8), _buildStatItem('Variant', '${stats.totalVariants}', Icons.tune, Colors.orange, isLandscape), @@ -566,10 +554,10 @@ class _DataSyncPageState extends State Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - _buildStatItem('Produk', '${stats.totalProducts}', - Icons.inventory_2, Colors.blue, isLandscape), _buildStatItem('Kategori', '${stats.totalCategories}', - Icons.category, Colors.green, isLandscape), + Icons.category, Colors.blue, isLandscape), + _buildStatItem('Produk', '${stats.totalProducts}', + Icons.inventory_2, Colors.green, isLandscape), _buildStatItem('Variant', '${stats.totalVariants}', Icons.tune, Colors.orange, isLandscape), ], @@ -765,10 +753,10 @@ class _DataSyncPageState extends State IconData _getSyncIcon(SyncStep step) { switch (step) { - case SyncStep.products: - return Icons.inventory_2; case SyncStep.categories: return Icons.category; + case SyncStep.products: + return Icons.inventory_2; case SyncStep.variants: return Icons.tune; case SyncStep.completed: @@ -778,10 +766,10 @@ class _DataSyncPageState extends State String _getStepLabel(SyncStep step) { switch (step) { - case SyncStep.products: - return 'Mengunduh Produk'; case SyncStep.categories: return 'Mengunduh Kategori'; + case SyncStep.products: + return 'Mengunduh Produk'; case SyncStep.variants: return 'Mengunduh Variant'; case SyncStep.completed: