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'
|
import 'package:apskel_pos_flutter_v2/application/split_bill/split_bill_form/split_bill_form_bloc.dart'
|
||||||
as _i334;
|
as _i334;
|
||||||
import 'package:apskel_pos_flutter_v2/application/sync/sync_bloc.dart' as _i741;
|
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'
|
import 'package:apskel_pos_flutter_v2/application/table/table_form/table_form_bloc.dart'
|
||||||
as _i248;
|
as _i248;
|
||||||
import 'package:apskel_pos_flutter_v2/application/table/table_loader/table_loader_bloc.dart'
|
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<_i502.ICategoryRepository>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
gh.factory<_i729.SyncSettingBloc>(
|
||||||
|
() => _i729.SyncSettingBloc(
|
||||||
|
gh<_i44.IProductRepository>(),
|
||||||
|
gh<_i502.ICategoryRepository>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
gh.factory<_i268.ProductAnalyticLoaderBloc>(
|
gh.factory<_i268.ProductAnalyticLoaderBloc>(
|
||||||
() => _i268.ProductAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
|
() => _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_form/printer_form_bloc.dart';
|
||||||
import '../application/printer/printer_loader/printer_loader_bloc.dart';
|
import '../application/printer/printer_loader/printer_loader_bloc.dart';
|
||||||
import '../application/product/product_loader/product_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_form/table_form_bloc.dart';
|
||||||
import '../application/table/table_loader/table_loader_bloc.dart';
|
import '../application/table/table_loader/table_loader_bloc.dart';
|
||||||
import '../application/void/void_form/void_form_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<PrinterFormBloc>()),
|
||||||
BlocProvider(create: (context) => getIt<PrinterLoaderBloc>()),
|
BlocProvider(create: (context) => getIt<PrinterLoaderBloc>()),
|
||||||
BlocProvider(create: (context) => getIt<PrintStruckBloc>()),
|
BlocProvider(create: (context) => getIt<PrintStruckBloc>()),
|
||||||
|
BlocProvider(create: (context) => getIt<SyncSettingBloc>()),
|
||||||
],
|
],
|
||||||
child: MaterialApp.router(
|
child: MaterialApp.router(
|
||||||
debugShowCheckedModeBanner: false,
|
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 '../../../../../common/theme/theme.dart';
|
||||||
import '../../../../../injection.dart';
|
import '../../../../../injection.dart';
|
||||||
import 'sections/setting_printer_section.dart';
|
import 'sections/setting_printer_section.dart';
|
||||||
|
import 'sections/setting_sync_section.dart';
|
||||||
import 'widgets/setting_left_panel.dart';
|
import 'widgets/setting_left_panel.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
@ -25,7 +26,7 @@ class SettingPage extends StatelessWidget implements AutoRouteWrapper {
|
|||||||
flex: 4,
|
flex: 4,
|
||||||
child: switch (state.index) {
|
child: switch (state.index) {
|
||||||
0 => SettingPrinterSection(),
|
0 => SettingPrinterSection(),
|
||||||
1 => Container(),
|
1 => SettingSyncSection(),
|
||||||
_ => Container(),
|
_ => Container(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user