sync data
This commit is contained in:
parent
72a464b4c0
commit
a58d1040af
@ -282,4 +282,16 @@ class CategoryRepository {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clearAllCategories() async {
|
||||
try {
|
||||
log('🗑️ Clearing all categories from repository...');
|
||||
await _localDatasource.clearAllCategories();
|
||||
clearCache();
|
||||
log('✅ All categories cleared successfully');
|
||||
} catch (e) {
|
||||
log('❌ Error clearing all categories: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
import 'dart:developer';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:enaklo_pos/data/datasources/product/product_local_datasource.dart';
|
||||
import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart';
|
||||
import 'package:enaklo_pos/data/models/response/product_response_model.dart';
|
||||
|
||||
class ProductRepository {
|
||||
static ProductRepository? _instance;
|
||||
|
||||
final ProductLocalDatasource _localDatasource;
|
||||
final ProductRemoteDatasource _remoteDatasource;
|
||||
|
||||
ProductRepository._internal()
|
||||
: _localDatasource = ProductLocalDatasource.instance;
|
||||
: _localDatasource = ProductLocalDatasource.instance,
|
||||
_remoteDatasource = ProductRemoteDatasource();
|
||||
|
||||
static ProductRepository get instance {
|
||||
_instance ??= ProductRepository._internal();
|
||||
@ -85,6 +88,66 @@ class ProductRepository {
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// PRODUCT SYNC OPERATIONS
|
||||
// ========================================
|
||||
Future<Either<String, String>> syncAllProducts() async {
|
||||
try {
|
||||
log('🔄 Starting manual sync of all products...');
|
||||
|
||||
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.clearAllProducts();
|
||||
|
||||
while (hasMore) {
|
||||
log('📄 Syncing page $page...');
|
||||
|
||||
final result = await _remoteDatasource.getProducts(
|
||||
page: page,
|
||||
limit: limit,
|
||||
);
|
||||
|
||||
await result.fold(
|
||||
(failure) async {
|
||||
log('❌ Sync failed at page $page: $failure');
|
||||
throw Exception(failure);
|
||||
},
|
||||
(response) async {
|
||||
final products = response.data?.products ?? [];
|
||||
|
||||
if (products.isNotEmpty) {
|
||||
await _localDatasource.saveProductsBatch(
|
||||
products,
|
||||
clearFirst: false, // Don't clear on subsequent pages
|
||||
);
|
||||
totalSynced += products.length;
|
||||
|
||||
// Check if we have more pages
|
||||
hasMore = page < (response.data?.totalPages ?? 0);
|
||||
page++;
|
||||
|
||||
log('📦 Page $page synced: ${products.length} products');
|
||||
} else {
|
||||
hasMore = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final message = 'Berhasil sinkronisasi $totalSynced produk';
|
||||
log('✅ $message');
|
||||
return Right(message);
|
||||
} catch (e) {
|
||||
final error = 'Gagal sinkronisasi produk: $e';
|
||||
log('❌ $error');
|
||||
return Left(error);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// LOCAL DATABASE OPERATIONS
|
||||
// ========================================
|
||||
@ -142,4 +205,16 @@ class ProductRepository {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clearAllProducts() async {
|
||||
try {
|
||||
log('🗑️ Clearing all products from repository...');
|
||||
await _localDatasource.clearAllProducts();
|
||||
clearCache();
|
||||
log('✅ All products cleared successfully');
|
||||
} catch (e) {
|
||||
log('❌ Error clearing all products: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import 'package:enaklo_pos/core/constants/colors.dart';
|
||||
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
|
||||
import 'package:enaklo_pos/presentation/setting/pages/printer_page.dart';
|
||||
import 'package:enaklo_pos/presentation/setting/pages/setting_tile.dart';
|
||||
import 'package:enaklo_pos/presentation/setting/pages/sync_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SettingPage extends StatefulWidget {
|
||||
@ -84,6 +85,14 @@ class _SettingPageState extends State<SettingPage> {
|
||||
icon: Icons.print_outlined,
|
||||
onTap: () => indexValue(0),
|
||||
),
|
||||
SettingTile(
|
||||
index: 1,
|
||||
currentIndex: currentIndex,
|
||||
title: 'Sinkronisasi',
|
||||
subtitle: 'Sinkronisasi data',
|
||||
icon: Icons.sync_outlined,
|
||||
onTap: () => indexValue(1),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -101,6 +110,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
index: currentIndex,
|
||||
children: [
|
||||
SettingPrinterPage(),
|
||||
SettingSyncPage(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
580
lib/presentation/setting/pages/sync_page.dart
Normal file
580
lib/presentation/setting/pages/sync_page.dart
Normal file
@ -0,0 +1,580 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user