Sync Page
This commit is contained in:
parent
f5256fb33d
commit
0a8f8d93bb
3
devtools_options.yaml
Normal file
3
devtools_options.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
234
lib/application/sync/sync_setting/sync_setting_bloc.dart
Normal file
234
lib/application/sync/sync_setting/sync_setting_bloc.dart
Normal file
@ -0,0 +1,234 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
import '../../../domain/category/category.dart';
|
||||
import '../../../domain/product/product.dart';
|
||||
|
||||
part 'sync_setting_event.dart';
|
||||
part 'sync_setting_state.dart';
|
||||
part 'sync_setting_bloc.freezed.dart';
|
||||
|
||||
@injectable
|
||||
class SyncSettingBloc extends Bloc<SyncSettingEvent, SyncSettingState> {
|
||||
final IProductRepository _productRepository;
|
||||
final ICategoryRepository _categoryRepository;
|
||||
|
||||
SyncSettingBloc(this._productRepository, this._categoryRepository)
|
||||
: super(SyncSettingState.initial()) {
|
||||
on<SyncSettingEvent>(_onSyncSettingEvent);
|
||||
}
|
||||
|
||||
Future<void> _onSyncSettingEvent(
|
||||
SyncSettingEvent event,
|
||||
Emitter<SyncSettingState> emit,
|
||||
) {
|
||||
return event.map(
|
||||
loadStats: (e) => _onLoadStats(emit),
|
||||
syncAllData: (e) => _onSyncAllData(emit),
|
||||
clearAllData: (e) => _onClearAllData(emit),
|
||||
syncProducts: (e) => _onSyncProducts(emit),
|
||||
syncCategories: (e) => _onSyncCategories(emit),
|
||||
clearCategories: (e) => _onClearCategories(emit),
|
||||
clearProducts: (e) => _onClearProducts(emit),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onLoadStats(Emitter<SyncSettingState> emit) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: true,
|
||||
failureOptionProduct: none(),
|
||||
failureOptionCategory: none(),
|
||||
),
|
||||
);
|
||||
|
||||
final productStats = await _productRepository.getDatabaseStats();
|
||||
final categoryStats = await _categoryRepository.getDatabaseStats();
|
||||
|
||||
productStats.fold(
|
||||
(failure) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
failureOptionProduct: optionOf(failure),
|
||||
),
|
||||
);
|
||||
},
|
||||
(data) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
productStats: data,
|
||||
failureOptionProduct: none(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
categoryStats.fold(
|
||||
(failure) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
failureOptionCategory: optionOf(failure),
|
||||
),
|
||||
);
|
||||
},
|
||||
(data) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
categoryStats: data,
|
||||
failureOptionCategory: none(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSyncAllData(Emitter<SyncSettingState> emit) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSyncing: true,
|
||||
failureOptionSyncCategory: none(),
|
||||
failureOptionSyncProduct: none(),
|
||||
),
|
||||
);
|
||||
|
||||
// Sync categories first
|
||||
final categoryResult = await _categoryRepository.syncAllCategories();
|
||||
|
||||
bool categorySuccess = false;
|
||||
categoryResult.fold(
|
||||
(failure) {
|
||||
emit(
|
||||
state.copyWith(failureOptionSyncCategory: optionOf(left(failure))),
|
||||
);
|
||||
},
|
||||
(success) {
|
||||
categorySuccess = true;
|
||||
emit(
|
||||
state.copyWith(failureOptionSyncCategory: optionOf(right(success))),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Only sync products if categories synced successfully
|
||||
if (categorySuccess) {
|
||||
final productResult = await _productRepository.syncAllProducts();
|
||||
|
||||
productResult.fold(
|
||||
(failure) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSyncing: false,
|
||||
failureOptionSyncProduct: optionOf(left(failure)),
|
||||
),
|
||||
);
|
||||
},
|
||||
(success) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSyncing: false,
|
||||
failureOptionSyncProduct: optionOf(right(success)),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
emit(state.copyWith(isSyncing: false));
|
||||
}
|
||||
|
||||
// Reload stats after sync
|
||||
add(const SyncSettingEvent.loadStats());
|
||||
}
|
||||
|
||||
Future<void> _onClearAllData(Emitter<SyncSettingState> emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
|
||||
try {
|
||||
// Clear products and categories
|
||||
await _productRepository.clearAllProducts();
|
||||
await _categoryRepository.clearAllCategories();
|
||||
|
||||
// Clear caches
|
||||
_productRepository.clearCache();
|
||||
_categoryRepository.clearCache();
|
||||
|
||||
emit(state.copyWith(isLoading: false));
|
||||
|
||||
// Reload stats after clearing
|
||||
add(const SyncSettingEvent.loadStats());
|
||||
} catch (e) {
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSyncProducts(Emitter<SyncSettingState> emit) async {
|
||||
emit(state.copyWith(isLoading: true, failureOptionSyncProduct: none()));
|
||||
|
||||
final result = await _productRepository.syncAllProducts();
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
failureOptionSyncProduct: optionOf(left(failure)),
|
||||
),
|
||||
);
|
||||
},
|
||||
(success) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
failureOptionSyncProduct: optionOf(right(success)),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Reload stats after sync
|
||||
add(const SyncSettingEvent.loadStats());
|
||||
}
|
||||
|
||||
Future<void> _onSyncCategories(Emitter<SyncSettingState> emit) async {
|
||||
emit(state.copyWith(isLoading: true, failureOptionSyncCategory: none()));
|
||||
|
||||
final result = await _categoryRepository.syncAllCategories();
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
failureOptionSyncCategory: optionOf(left(failure)),
|
||||
),
|
||||
);
|
||||
},
|
||||
(success) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
failureOptionSyncCategory: optionOf(right(success)),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Reload stats after sync
|
||||
add(const SyncSettingEvent.loadStats());
|
||||
}
|
||||
|
||||
Future<void> _onClearCategories(Emitter<SyncSettingState> emit) async {
|
||||
await _categoryRepository.clearAllCategories();
|
||||
_categoryRepository.clearCache();
|
||||
}
|
||||
|
||||
Future<void> _onClearProducts(Emitter<SyncSettingState> emit) async {
|
||||
await _productRepository.clearAllProducts();
|
||||
_productRepository.clearCache();
|
||||
}
|
||||
}
|
||||
1399
lib/application/sync/sync_setting/sync_setting_bloc.freezed.dart
Normal file
1399
lib/application/sync/sync_setting/sync_setting_bloc.freezed.dart
Normal file
File diff suppressed because it is too large
Load Diff
12
lib/application/sync/sync_setting/sync_setting_event.dart
Normal file
12
lib/application/sync/sync_setting/sync_setting_event.dart
Normal file
@ -0,0 +1,12 @@
|
||||
part of 'sync_setting_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class SyncSettingEvent with _$SyncSettingEvent {
|
||||
const factory SyncSettingEvent.loadStats() = _LoadStats;
|
||||
const factory SyncSettingEvent.syncAllData() = _SyncAllData;
|
||||
const factory SyncSettingEvent.clearAllData() = _ClearAllData;
|
||||
const factory SyncSettingEvent.syncProducts() = _SyncProducts;
|
||||
const factory SyncSettingEvent.syncCategories() = _SyncCategories;
|
||||
const factory SyncSettingEvent.clearCategories() = _ClearCategories;
|
||||
const factory SyncSettingEvent.clearProducts() = _ClearProducts;
|
||||
}
|
||||
24
lib/application/sync/sync_setting/sync_setting_state.dart
Normal file
24
lib/application/sync/sync_setting/sync_setting_state.dart
Normal file
@ -0,0 +1,24 @@
|
||||
part of 'sync_setting_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class SyncSettingState with _$SyncSettingState {
|
||||
factory SyncSettingState({
|
||||
required Map<String, dynamic> productStats,
|
||||
required Map<String, dynamic> categoryStats,
|
||||
required Option<CategoryFailure> failureOptionCategory,
|
||||
required Option<ProductFailure> failureOptionProduct,
|
||||
required Option<Either<ProductFailure, String>> failureOptionSyncProduct,
|
||||
required Option<Either<CategoryFailure, String>> failureOptionSyncCategory,
|
||||
@Default(false) bool isSyncing,
|
||||
@Default(false) bool isLoading,
|
||||
}) = _SyncSettingState;
|
||||
|
||||
factory SyncSettingState.initial() => SyncSettingState(
|
||||
productStats: {},
|
||||
categoryStats: {},
|
||||
failureOptionCategory: none(),
|
||||
failureOptionProduct: none(),
|
||||
failureOptionSyncProduct: none(),
|
||||
failureOptionSyncCategory: none(),
|
||||
);
|
||||
}
|
||||
@ -63,6 +63,8 @@ import 'package:apskel_pos_flutter_v2/application/report/report_bloc.dart'
|
||||
import 'package:apskel_pos_flutter_v2/application/split_bill/split_bill_form/split_bill_form_bloc.dart'
|
||||
as _i334;
|
||||
import 'package:apskel_pos_flutter_v2/application/sync/sync_bloc.dart' as _i741;
|
||||
import 'package:apskel_pos_flutter_v2/application/sync/sync_setting/sync_setting_bloc.dart'
|
||||
as _i729;
|
||||
import 'package:apskel_pos_flutter_v2/application/table/table_form/table_form_bloc.dart'
|
||||
as _i248;
|
||||
import 'package:apskel_pos_flutter_v2/application/table/table_loader/table_loader_bloc.dart'
|
||||
@ -348,6 +350,12 @@ extension GetItInjectableX on _i174.GetIt {
|
||||
gh<_i502.ICategoryRepository>(),
|
||||
),
|
||||
);
|
||||
gh.factory<_i729.SyncSettingBloc>(
|
||||
() => _i729.SyncSettingBloc(
|
||||
gh<_i44.IProductRepository>(),
|
||||
gh<_i502.ICategoryRepository>(),
|
||||
),
|
||||
);
|
||||
gh.factory<_i268.ProductAnalyticLoaderBloc>(
|
||||
() => _i268.ProductAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
|
||||
);
|
||||
|
||||
@ -15,6 +15,7 @@ import '../application/printer/print_struck/print_struck_bloc.dart';
|
||||
import '../application/printer/printer_form/printer_form_bloc.dart';
|
||||
import '../application/printer/printer_loader/printer_loader_bloc.dart';
|
||||
import '../application/product/product_loader/product_loader_bloc.dart';
|
||||
import '../application/sync/sync_setting/sync_setting_bloc.dart';
|
||||
import '../application/table/table_form/table_form_bloc.dart';
|
||||
import '../application/table/table_loader/table_loader_bloc.dart';
|
||||
import '../application/void/void_form/void_form_bloc.dart';
|
||||
@ -55,6 +56,7 @@ class _AppWidgetState extends State<AppWidget> {
|
||||
BlocProvider(create: (context) => getIt<PrinterFormBloc>()),
|
||||
BlocProvider(create: (context) => getIt<PrinterLoaderBloc>()),
|
||||
BlocProvider(create: (context) => getIt<PrintStruckBloc>()),
|
||||
BlocProvider(create: (context) => getIt<SyncSettingBloc>()),
|
||||
],
|
||||
child: MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
|
||||
@ -0,0 +1,511 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../../../application/sync/sync_setting/sync_setting_bloc.dart';
|
||||
import '../../../../../../common/theme/theme.dart';
|
||||
import '../../../../../../injection.dart';
|
||||
import '../../../../../components/button/button.dart';
|
||||
import '../../../../../components/loader/loader_with_text.dart';
|
||||
import '../../../../../components/page/page_title.dart';
|
||||
import '../../../../../components/spaces/space.dart';
|
||||
import '../../../../../components/toast/flushbar.dart';
|
||||
|
||||
class SettingSyncSection extends StatelessWidget {
|
||||
const SettingSyncSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
getIt<SyncSettingBloc>()..add(const SyncSettingEvent.loadStats()),
|
||||
child: const _SettingSyncPageContent(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingSyncPageContent extends StatelessWidget {
|
||||
const _SettingSyncPageContent();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<SyncSettingBloc, SyncSettingState>(
|
||||
listener: (context, state) {
|
||||
// Handle product stats failure
|
||||
state.failureOptionProduct.fold(() {}, (failure) {
|
||||
AppFlushbar.showError(
|
||||
context,
|
||||
'Gagal memuat stats produk: ${failure.toString()}',
|
||||
);
|
||||
});
|
||||
|
||||
// Handle category stats failure
|
||||
state.failureOptionCategory.fold(() {}, (failure) {
|
||||
AppFlushbar.showError(
|
||||
context,
|
||||
'Gagal memuat stats kategori: ${failure.toString()}',
|
||||
);
|
||||
});
|
||||
|
||||
// Handle sync product result
|
||||
state.failureOptionSyncProduct.fold(() {}, (result) {
|
||||
result.fold(
|
||||
(failure) {
|
||||
AppFlushbar.showError(
|
||||
context,
|
||||
'Gagal sync produk: ${failure.toString()}',
|
||||
);
|
||||
},
|
||||
(success) {
|
||||
AppFlushbar.showSuccess(context, success);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// Handle sync category result
|
||||
state.failureOptionSyncCategory.fold(() {}, (result) {
|
||||
result.fold(
|
||||
(failure) {
|
||||
AppFlushbar.showError(
|
||||
context,
|
||||
'Gagal sync kategori: ${failure.toString()}',
|
||||
);
|
||||
},
|
||||
(success) {
|
||||
AppFlushbar.showSuccess(context, success);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
PageTitle(
|
||||
title: 'Sinkronisasi',
|
||||
subtitle: 'Sinkronisasi data dengan server',
|
||||
isBack: false,
|
||||
),
|
||||
Expanded(
|
||||
child: Material(
|
||||
color: AppColor.background,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
SpaceHeight(24),
|
||||
_buildQuickActions(context, state),
|
||||
SpaceHeight(24),
|
||||
_buildSyncTables(context, state),
|
||||
SpaceHeight(24),
|
||||
_buildDatabaseStats(context, state),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuickActions(BuildContext context, SyncSettingState state) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Aksi Cepat',
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
SpaceHeight(16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AppElevatedButton.filled(
|
||||
onPressed: state.isSyncing || state.isLoading
|
||||
? null
|
||||
: () => _syncAllData(context),
|
||||
label: state.isSyncing
|
||||
? 'Menyinkronkan...'
|
||||
: 'Sync Semua Data',
|
||||
),
|
||||
),
|
||||
SpaceWidth(12),
|
||||
Expanded(
|
||||
child: AppElevatedButton.outlined(
|
||||
onPressed: state.isLoading || state.isSyncing
|
||||
? null
|
||||
: () => _clearAllData(context),
|
||||
label: 'Hapus Semua Data',
|
||||
textColor: Colors.red,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSyncTables(BuildContext context, SyncSettingState state) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Sinkronisasi per Tabel',
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
SpaceHeight(16),
|
||||
_buildSyncTableItem(
|
||||
context: context,
|
||||
state: state,
|
||||
title: 'Kategori',
|
||||
subtitle: 'Sinkronkan data kategori produk',
|
||||
icon: Icons.category,
|
||||
color: Colors.blue,
|
||||
count: state.categoryStats['total_categories'] ?? 0,
|
||||
onSync: () {
|
||||
context.read<SyncSettingBloc>().add(
|
||||
const SyncSettingEvent.syncCategories(),
|
||||
);
|
||||
},
|
||||
onClear: () => _clearCategories(context),
|
||||
),
|
||||
SpaceHeight(12),
|
||||
const Divider(),
|
||||
SpaceHeight(12),
|
||||
_buildSyncTableItem(
|
||||
context: context,
|
||||
state: state,
|
||||
title: 'Produk',
|
||||
subtitle: 'Sinkronkan data produk dan variant',
|
||||
icon: Icons.inventory_2,
|
||||
color: Colors.green,
|
||||
count: state.productStats['total_products'] ?? 0,
|
||||
onSync: () {
|
||||
context.read<SyncSettingBloc>().add(
|
||||
const SyncSettingEvent.syncProducts(),
|
||||
);
|
||||
},
|
||||
onClear: () => _clearProducts(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSyncTableItem({
|
||||
required BuildContext context,
|
||||
required SyncSettingState state,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
required int count,
|
||||
required VoidCallback onSync,
|
||||
required VoidCallback onClear,
|
||||
}) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 24),
|
||||
),
|
||||
SpaceWidth(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: AppStyle.md.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
SpaceWidth(8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
'$count',
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: AppStyle.sm.copyWith(color: Colors.grey.shade600),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: state.isLoading || state.isSyncing ? null : onSync,
|
||||
icon: const Icon(Icons.sync, size: 20),
|
||||
tooltip: 'Sync $title',
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: color.withOpacity(0.1),
|
||||
foregroundColor: color,
|
||||
),
|
||||
),
|
||||
SpaceWidth(4),
|
||||
IconButton(
|
||||
onPressed: state.isLoading || state.isSyncing ? null : onClear,
|
||||
icon: const Icon(Icons.delete_outline, size: 20),
|
||||
tooltip: 'Hapus $title',
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.red.withOpacity(0.1),
|
||||
foregroundColor: Colors.red,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDatabaseStats(BuildContext context, SyncSettingState state) {
|
||||
final totalCacheEntries =
|
||||
(state.productStats['cache_entries'] ?? 0) +
|
||||
(state.categoryStats['cache_entries'] ?? 0);
|
||||
final totalDatabaseSize =
|
||||
(state.productStats['database_size_mb'] ?? 0.0) +
|
||||
(state.categoryStats['database_size_mb'] ?? 0.0);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Statistik Database',
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
const Spacer(),
|
||||
if (state.isLoading)
|
||||
Center(child: LoaderWithText())
|
||||
else
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<SyncSettingBloc>().add(
|
||||
const SyncSettingEvent.loadStats(),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.refresh, size: 20),
|
||||
tooltip: 'Refresh Stats',
|
||||
),
|
||||
],
|
||||
),
|
||||
SpaceHeight(16),
|
||||
if (state.isLoading)
|
||||
const Center(child: LoaderWithText())
|
||||
else
|
||||
Column(
|
||||
children: [
|
||||
_buildStatRow(
|
||||
'Kategori',
|
||||
state.categoryStats['total_categories']?.toString() ?? '0',
|
||||
Icons.category,
|
||||
Colors.blue,
|
||||
),
|
||||
SpaceHeight(8),
|
||||
_buildStatRow(
|
||||
'Produk',
|
||||
state.productStats['total_products']?.toString() ?? '0',
|
||||
Icons.inventory_2,
|
||||
Colors.green,
|
||||
),
|
||||
SpaceHeight(8),
|
||||
_buildStatRow(
|
||||
'Variant',
|
||||
state.productStats['total_variants']?.toString() ?? '0',
|
||||
Icons.tune,
|
||||
Colors.orange,
|
||||
),
|
||||
SpaceHeight(8),
|
||||
_buildStatRow(
|
||||
'Cache Entries',
|
||||
'$totalCacheEntries',
|
||||
Icons.memory,
|
||||
Colors.purple,
|
||||
),
|
||||
SpaceHeight(8),
|
||||
_buildStatRow(
|
||||
'Ukuran Database',
|
||||
'${totalDatabaseSize.toStringAsFixed(2)} MB',
|
||||
Icons.storage,
|
||||
Colors.grey.shade600,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatRow(String label, String value, IconData icon, Color color) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon, size: 16, color: color),
|
||||
SpaceWidth(8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: AppStyle.md.copyWith(color: Colors.grey.shade700),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _syncAllData(BuildContext context) async {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const AlertDialog(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
SpaceHeight(16),
|
||||
Text('Sinkronisasi semua data...'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
context.read<SyncSettingBloc>().add(const SyncSettingEvent.syncAllData());
|
||||
|
||||
// Wait a bit for the sync to complete, then close dialog
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _clearAllData(BuildContext context) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Hapus Semua Data'),
|
||||
content: const Text(
|
||||
'Apakah Anda yakin ingin menghapus semua data lokal? Tindakan ini tidak dapat dibatalkan.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text(
|
||||
'Hapus',
|
||||
style: AppStyle.md.copyWith(color: Colors.red),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true && context.mounted) {
|
||||
context.read<SyncSettingBloc>().add(
|
||||
const SyncSettingEvent.clearAllData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _clearCategories(BuildContext context) async {
|
||||
final confirmed = await _showClearConfirmation(context, 'kategori');
|
||||
if (confirmed && context.mounted) {
|
||||
context.read<SyncSettingBloc>().add(
|
||||
const SyncSettingEvent.clearCategories(),
|
||||
);
|
||||
AppFlushbar.showSuccess(context, 'Data kategori berhasil dihapus');
|
||||
context.read<SyncSettingBloc>().add(const SyncSettingEvent.loadStats());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _clearProducts(BuildContext context) async {
|
||||
final confirmed = await _showClearConfirmation(context, 'produk');
|
||||
if (confirmed && context.mounted) {
|
||||
context.read<SyncSettingBloc>().add(
|
||||
const SyncSettingEvent.clearProducts(),
|
||||
);
|
||||
AppFlushbar.showSuccess(context, 'Data produk berhasil dihapus');
|
||||
context.read<SyncSettingBloc>().add(const SyncSettingEvent.loadStats());
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _showClearConfirmation(
|
||||
BuildContext context,
|
||||
String dataType,
|
||||
) async {
|
||||
return await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Hapus Data $dataType'),
|
||||
content: Text(
|
||||
'Apakah Anda yakin ingin menghapus semua data $dataType?',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text(
|
||||
'Hapus',
|
||||
style: AppStyle.md.copyWith(color: Colors.red),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
) ??
|
||||
false;
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ import '../../../../../application/printer/printer_bloc.dart';
|
||||
import '../../../../../common/theme/theme.dart';
|
||||
import '../../../../../injection.dart';
|
||||
import 'sections/setting_printer_section.dart';
|
||||
import 'sections/setting_sync_section.dart';
|
||||
import 'widgets/setting_left_panel.dart';
|
||||
|
||||
@RoutePage()
|
||||
@ -25,7 +26,7 @@ class SettingPage extends StatelessWidget implements AutoRouteWrapper {
|
||||
flex: 4,
|
||||
child: switch (state.index) {
|
||||
0 => SettingPrinterSection(),
|
||||
1 => Container(),
|
||||
1 => SettingSyncSection(),
|
||||
_ => Container(),
|
||||
},
|
||||
),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user