2025-09-20 05:02:01 +07:00

581 lines
17 KiB
Dart

import 'dart:developer';
import 'package:enaklo_pos/core/components/flushbar.dart';
import 'package:enaklo_pos/core/components/buttons.dart';
import 'package:enaklo_pos/data/repositories/product/product_repository.dart';
import 'package:enaklo_pos/data/repositories/category/category_repository.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/presentation/setting/widgets/settings_title.dart';
import 'package:flutter/material.dart';
class SettingSyncPage extends StatefulWidget {
const SettingSyncPage({super.key});
@override
State<SettingSyncPage> createState() => _SettingSyncPageState();
}
class _SettingSyncPageState extends State<SettingSyncPage> {
final ProductRepository _productRepository = ProductRepository.instance;
final CategoryRepository _categoryRepository = CategoryRepository.instance;
final ProductLocalDatasource _productLocalDatasource =
ProductLocalDatasource.instance;
final CategoryLocalDatasource _categoryLocalDatasource =
CategoryLocalDatasource.instance;
bool _isLoading = false;
bool _isSyncing = false;
Map<String, dynamic> _productStats = {};
Map<String, dynamic> _categoryStats = {};
@override
void initState() {
super.initState();
_loadStats();
}
Future<void> _loadStats() async {
setState(() => _isLoading = true);
try {
final productStats = await _productRepository.getDatabaseStats();
final categoryStats = await _categoryRepository.getDatabaseStats();
setState(() {
_productStats = productStats;
_categoryStats = categoryStats;
_isLoading = false;
});
} catch (e) {
log('Error loading stats: $e');
setState(() => _isLoading = false);
}
}
Future<void> _syncAllData() async {
setState(() => _isSyncing = true);
try {
// Show loading dialog
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Sinkronisasi semua data...'),
],
),
),
);
// Sync categories first
final categoryResult = await _categoryRepository.syncAllCategories();
await categoryResult.fold(
(error) async {
Navigator.of(context).pop();
AppFlushbar.showError(context, 'Gagal sync kategori: $error');
return;
},
(success) async {
log('Categories synced successfully');
},
);
// Sync products after categories
final productResult = await _productRepository.syncAllProducts();
await productResult.fold(
(error) async {
Navigator.of(context).pop();
AppFlushbar.showError(context, 'Gagal sync produk: $error');
return;
},
(success) async {
log('Products synced successfully');
},
);
Navigator.of(context).pop();
AppFlushbar.showSuccess(context, 'Sinkronisasi berhasil');
_loadStats(); // Refresh stats
} catch (e) {
Navigator.of(context).pop();
AppFlushbar.showError(context, 'Gagal sinkronisasi: $e');
} finally {
setState(() => _isSyncing = false);
}
}
Future<void> _clearAllData() async {
// Show confirmation dialog
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('Hapus Semua Data'),
content: Text(
'Apakah Anda yakin ingin menghapus semua data lokal? Tindakan ini tidak dapat dibatalkan.'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text('Batal'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text('Hapus', style: TextStyle(color: Colors.red)),
),
],
),
);
if (confirmed != true) return;
setState(() => _isLoading = true);
try {
// Clear products and categories using datasource
await _productLocalDatasource.clearAllProducts();
await _categoryLocalDatasource.clearAllCategories();
// Clear caches
_productRepository.clearCache();
_categoryRepository.clearCache();
AppFlushbar.showSuccess(context, 'Semua data berhasil dihapus');
_loadStats(); // Refresh stats
} catch (e) {
AppFlushbar.showError(context, 'Gagal menghapus data: $e');
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _syncProducts() async {
setState(() => _isLoading = true);
try {
final result = await _productRepository.syncAllProducts();
await result.fold(
(error) async {
AppFlushbar.showError(context, 'Gagal sync produk: $error');
},
(success) async {
AppFlushbar.showSuccess(context, success);
_loadStats(); // Refresh stats
},
);
} catch (e) {
AppFlushbar.showError(context, 'Gagal sync produk: $e');
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _syncCategories() async {
setState(() => _isLoading = true);
try {
final result = await _categoryRepository.syncAllCategories();
await result.fold(
(error) async {
AppFlushbar.showError(context, 'Gagal sync kategori: $error');
},
(success) async {
AppFlushbar.showSuccess(context, success);
_loadStats(); // Refresh stats
},
);
} catch (e) {
AppFlushbar.showError(context, 'Gagal sync kategori: $e');
} finally {
setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
SettingsTitle(
'Sinkronisasi',
subtitle: 'Sinkronisasi data dengan server',
),
SizedBox(height: 24),
// Quick Actions
_buildQuickActions(),
SizedBox(height: 24),
// Sync Tables
_buildSyncTables(),
SizedBox(height: 24),
// Database Stats
_buildDatabaseStats(),
],
),
);
}
Widget _buildQuickActions() {
return Container(
padding: 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: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
SizedBox(height: 16),
Row(
children: [
Expanded(
child: Button.filled(
onPressed: _isSyncing || _isLoading ? null : _syncAllData,
label: _isSyncing ? 'Menyinkronkan...' : 'Sync Semua Data',
),
),
SizedBox(width: 12),
Expanded(
child: Button.outlined(
onPressed: _isLoading || _isSyncing ? null : _clearAllData,
label: 'Hapus Semua Data',
textColor: Colors.red,
),
),
],
),
],
),
);
}
Widget _buildSyncTables() {
return Container(
padding: 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: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
SizedBox(height: 16),
// Categories Sync
_buildSyncTableItem(
title: 'Kategori',
subtitle: 'Sinkronkan data kategori produk',
icon: Icons.category,
color: Colors.blue,
count: _categoryStats['total_categories'] ?? 0,
onSync: _syncCategories,
onClear: () async {
final confirmed = await _showClearConfirmation('kategori');
if (confirmed) {
await _categoryLocalDatasource.clearAllCategories();
_categoryRepository.clearCache();
AppFlushbar.showSuccess(
context, 'Data kategori berhasil dihapus');
_loadStats();
}
},
),
SizedBox(height: 12),
Divider(),
SizedBox(height: 12),
// Products Sync
_buildSyncTableItem(
title: 'Produk',
subtitle: 'Sinkronkan data produk dan variant',
icon: Icons.inventory_2,
color: Colors.green,
count: _productStats['total_products'] ?? 0,
onSync: _syncProducts,
onClear: () async {
final confirmed = await _showClearConfirmation('produk');
if (confirmed) {
await _productLocalDatasource.clearAllProducts();
_productRepository.clearCache();
AppFlushbar.showSuccess(
context, 'Data produk berhasil dihapus');
_loadStats();
}
},
),
],
),
);
}
Widget _buildSyncTableItem({
required String title,
required String subtitle,
required IconData icon,
required Color color,
required int count,
required VoidCallback onSync,
required VoidCallback onClear,
}) {
return Row(
children: [
// Icon and info
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: color, size: 24),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
SizedBox(width: 8),
Container(
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
'$count',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: color,
),
),
),
],
),
Text(
subtitle,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
),
// Actions
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: _isLoading || _isSyncing ? null : onSync,
icon: Icon(Icons.sync, size: 20),
tooltip: 'Sync $title',
style: IconButton.styleFrom(
backgroundColor: color.withOpacity(0.1),
foregroundColor: color,
),
),
SizedBox(width: 4),
IconButton(
onPressed: _isLoading || _isSyncing ? null : onClear,
icon: Icon(Icons.delete_outline, size: 20),
tooltip: 'Hapus $title',
style: IconButton.styleFrom(
backgroundColor: Colors.red.withOpacity(0.1),
foregroundColor: Colors.red,
),
),
],
),
],
);
}
Widget _buildDatabaseStats() {
return Container(
padding: 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: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
Spacer(),
if (_isLoading)
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
else
IconButton(
onPressed: _loadStats,
icon: Icon(Icons.refresh, size: 20),
tooltip: 'Refresh Stats',
),
],
),
SizedBox(height: 16),
if (_isLoading)
Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: CircularProgressIndicator(),
),
)
else
Column(
children: [
// Category stats
_buildStatRow(
'Kategori',
_categoryStats['total_categories']?.toString() ?? '0',
Icons.category,
Colors.blue,
),
SizedBox(height: 8),
// Product stats
_buildStatRow(
'Produk',
_productStats['total_products']?.toString() ?? '0',
Icons.inventory_2,
Colors.green,
),
SizedBox(height: 8),
// Variant stats
_buildStatRow(
'Variant',
_productStats['total_variants']?.toString() ?? '0',
Icons.tune,
Colors.orange,
),
SizedBox(height: 8),
// Cache stats
_buildStatRow(
'Cache Entries',
'${(_productStats['cache_entries'] ?? 0) + (_categoryStats['cache_entries'] ?? 0)}',
Icons.memory,
Colors.purple,
),
SizedBox(height: 8),
// Database size
_buildStatRow(
'Ukuran Database',
'${((_productStats['database_size_mb'] ?? 0.0) + (_categoryStats['database_size_mb'] ?? 0.0)).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),
SizedBox(width: 8),
Expanded(
child: Text(
label,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade700,
),
),
),
Text(
value,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: color,
),
),
],
);
}
Future<bool> _showClearConfirmation(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: Text('Batal'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text('Hapus', style: TextStyle(color: Colors.red)),
),
],
),
) ??
false;
}
}