category local

This commit is contained in:
efrilm
2025-09-20 04:36:22 +07:00
parent c12d6525fa
commit 5b980d237f
12 changed files with 3443 additions and 515 deletions
@@ -0,0 +1,285 @@
import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:enaklo_pos/data/datasources/category/category_local_datasource.dart';
import 'package:enaklo_pos/data/datasources/category/category_remote_datasource.dart';
import 'package:enaklo_pos/data/models/response/category_response_model.dart';
class CategoryRepository {
static CategoryRepository? _instance;
final CategoryLocalDatasource _localDatasource;
final CategoryRemoteDatasource _remoteDatasource;
CategoryRepository._internal()
: _localDatasource = CategoryLocalDatasource.instance,
_remoteDatasource = CategoryRemoteDatasource();
static CategoryRepository get instance {
_instance ??= CategoryRepository._internal();
return _instance!;
}
// ========================================
// SYNC STRATEGY: REMOTE-FIRST WITH LOCAL FALLBACK
// ========================================
Future<Either<String, CategoryResponseModel>> getCategories({
int page = 1,
int limit = 10,
bool isActive = true,
String? search,
bool forceRemote = false,
}) async {
try {
log('πŸ“± Getting categories - page: $page, isActive: $isActive, search: $search, forceRemote: $forceRemote');
// Clean expired cache
_localDatasource.clearExpiredCache();
// Check if we should try remote first
if (forceRemote || !await _localDatasource.hasCategories()) {
log('🌐 Attempting remote fetch first...');
final remoteResult = await _getRemoteCategories(
page: page,
limit: limit,
isActive: isActive,
);
return await remoteResult.fold(
(failure) async {
log('❌ Remote fetch failed: $failure');
log('πŸ“± Falling back to local data...');
return _getLocalCategories(
page: page,
limit: limit,
isActive: isActive,
search: search,
);
},
(response) async {
log('βœ… Remote fetch successful, syncing to local...');
// Sync remote data to local
if (response.data.categories.isNotEmpty) {
await _syncToLocal(response.data.categories,
clearFirst: page == 1);
}
return Right(response);
},
);
} else {
log('πŸ“± Using local data (cache available)...');
return _getLocalCategories(
page: page,
limit: limit,
isActive: isActive,
search: search,
);
}
} catch (e) {
log('❌ Error in getCategories: $e');
return Left('Gagal memuat kategori: $e');
}
}
// ========================================
// PURE LOCAL OPERATIONS
// ========================================
Future<Either<String, CategoryResponseModel>> _getLocalCategories({
int page = 1,
int limit = 10,
bool isActive = true,
String? search,
}) async {
try {
final cachedCategories = await _localDatasource.getCachedCategories(
page: page,
limit: limit,
isActive: isActive,
search: search,
);
final totalCount = await _localDatasource.getTotalCount(
isActive: isActive,
search: search,
);
final categoryData = CategoryData(
categories: cachedCategories,
totalCount: totalCount,
page: page,
limit: limit,
totalPages: totalCount > 0 ? (totalCount / limit).ceil() : 0,
);
final response = CategoryResponseModel(
success: true,
data: categoryData,
);
log('βœ… Returned ${cachedCategories.length} local categories (${totalCount} total)');
return Right(response);
} catch (e) {
log('❌ Error getting local categories: $e');
return Left('Gagal memuat kategori dari database lokal: $e');
}
}
// ========================================
// REMOTE FETCH
// ========================================
Future<Either<String, CategoryResponseModel>> _getRemoteCategories({
int page = 1,
int limit = 10,
bool isActive = true,
}) async {
try {
log('🌐 Fetching categories from remote...');
return await _remoteDatasource.getCategories(
page: page,
limit: limit,
isActive: isActive,
);
} catch (e) {
log('❌ Remote fetch error: $e');
return Left('Gagal mengambil data dari server: $e');
}
}
// ========================================
// SYNC TO LOCAL
// ========================================
Future<void> _syncToLocal(List<CategoryModel> categories,
{bool clearFirst = false}) async {
try {
log('πŸ’Ύ Syncing ${categories.length} categories to local database...');
await _localDatasource.saveCategoriesBatch(categories,
clearFirst: clearFirst);
log('βœ… Categories synced to local successfully');
} catch (e) {
log('❌ Error syncing categories to local: $e');
rethrow;
}
}
// ========================================
// MANUAL SYNC OPERATIONS
// ========================================
Future<Either<String, String>> syncAllCategories() async {
try {
log('πŸ”„ Starting manual sync of all categories...');
int page = 1;
const limit = 50; // Higher limit for bulk sync
bool hasMore = true;
int totalSynced = 0;
// Clear local data first for fresh sync
await _localDatasource.clearAllCategories();
while (hasMore) {
log('πŸ“„ Syncing page $page...');
final result = await _remoteDatasource.getCategories(
page: page,
limit: limit,
isActive: true,
);
await result.fold(
(failure) async {
log('❌ Sync failed at page $page: $failure');
throw Exception(failure);
},
(response) async {
final categories = response.data.categories;
if (categories.isNotEmpty) {
await _localDatasource.saveCategoriesBatch(
categories,
clearFirst: false, // Don't clear on subsequent pages
);
totalSynced += categories.length;
// Check if we have more pages
hasMore = page < response.data.totalPages;
page++;
log('πŸ“¦ Page $page synced: ${categories.length} categories');
} else {
hasMore = false;
}
},
);
}
final message = 'Berhasil sinkronisasi $totalSynced kategori';
log('βœ… $message');
return Right(message);
} catch (e) {
final error = 'Gagal sinkronisasi kategori: $e';
log('❌ $error');
return Left(error);
}
}
// ========================================
// UTILITY METHODS
// ========================================
Future<Either<String, CategoryResponseModel>> refreshCategories({
bool isActive = true,
String? search,
}) async {
log('πŸ”„ Refreshing categories...');
clearCache();
return await getCategories(
page: 1,
limit: 10,
isActive: isActive,
search: search,
forceRemote: true, // Force remote refresh
);
}
Future<CategoryModel?> getCategoryById(String id) async {
log('πŸ” Getting category by ID: $id');
return await _localDatasource.getCategoryById(id);
}
Future<List<CategoryModel>> getAllCategories() async {
log('πŸ“‹ Getting all categories for dropdown...');
return await _localDatasource.getAllCategories();
}
Future<bool> hasLocalCategories() async {
final hasCategories = await _localDatasource.hasCategories();
log('πŸ“Š Has local categories: $hasCategories');
return hasCategories;
}
Future<Map<String, dynamic>> getDatabaseStats() async {
final stats = await _localDatasource.getDatabaseStats();
log('πŸ“Š Category database stats: $stats');
return stats;
}
void clearCache() {
log('🧹 Clearing category cache');
_localDatasource.clearCache();
}
Future<bool> isLocalDatabaseReady() async {
try {
final stats = await getDatabaseStats();
final categoryCount = stats['total_categories'] ?? 0;
final isReady = categoryCount > 0;
log('πŸ” Category database ready: $isReady ($categoryCount categories)');
return isReady;
} catch (e) {
log('❌ Error checking category database readiness: $e');
return false;
}
}
}