367 lines
12 KiB
Dart
367 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
|
import 'dart:async';
|
|
|
|
import '../state/app_state.dart';
|
|
import '../utils/colors.dart';
|
|
import 'kelola_kasir_screen.dart';
|
|
import 'printer_screen.dart';
|
|
|
|
class PengaturanScreen extends StatefulWidget {
|
|
const PengaturanScreen({super.key});
|
|
|
|
@override
|
|
State<PengaturanScreen> createState() => _PengaturanScreenState();
|
|
}
|
|
|
|
class _PengaturanScreenState extends State<PengaturanScreen> {
|
|
final TextEditingController _storeNameCtrl = TextEditingController();
|
|
final TextEditingController _currencyCtrl = TextEditingController(text: 'IDR');
|
|
final TextEditingController _whatsappCtrl = TextEditingController();
|
|
final TextEditingController _notesCtrl = TextEditingController();
|
|
bool _resellerEnabled = true;
|
|
bool _loading = true;
|
|
Timer? _settingsSyncTimer;
|
|
bool _loadingSettings = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadSettings();
|
|
_settingsSyncTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
|
if (!_loadingSettings && mounted) {
|
|
_loadSettings();
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
Future<void> _loadSettings() async {
|
|
if (_loadingSettings) return;
|
|
_loadingSettings = true;
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
String storeName = prefs.getString('store_name') ?? 'Toko Grosir';
|
|
String currency = prefs.getString('currency') ?? 'IDR';
|
|
String whatsapp = prefs.getString('store_whatsapp') ?? '';
|
|
String notes = prefs.getString('store_notes') ?? '';
|
|
bool resellerEnabled = prefs.getBool('reseller_enabled') ?? true;
|
|
|
|
try {
|
|
final data = await Supabase.instance.client
|
|
.from('store_settings')
|
|
.select()
|
|
.eq('store_id', '00000000-0000-0000-0000-000000000000')
|
|
.maybeSingle();
|
|
if (data != null) {
|
|
storeName = (data['store_name'] as String?) ?? storeName;
|
|
currency = (data['currency'] as String?) ?? currency;
|
|
whatsapp = data['whatsapp'] as String? ?? whatsapp;
|
|
notes = data['notes'] as String? ?? notes;
|
|
resellerEnabled = data['reseller_enabled'] as bool? ?? resellerEnabled;
|
|
}
|
|
} catch (e) {
|
|
debugPrint('Gagal load store settings dari Supabase: $e');
|
|
}
|
|
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_storeNameCtrl.text = storeName;
|
|
_currencyCtrl.text = currency;
|
|
_whatsappCtrl.text = whatsapp;
|
|
_notesCtrl.text = notes;
|
|
_resellerEnabled = resellerEnabled;
|
|
_loading = false;
|
|
});
|
|
} finally {
|
|
_loadingSettings = false;
|
|
}
|
|
}
|
|
|
|
Future<void> _saveSettings() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString('store_name', _storeNameCtrl.text.trim());
|
|
await prefs.setString('currency', _currencyCtrl.text.trim());
|
|
await prefs.setString('store_whatsapp', _whatsappCtrl.text.trim());
|
|
await prefs.setString('store_notes', _notesCtrl.text.trim());
|
|
await prefs.setBool('reseller_enabled', _resellerEnabled);
|
|
if (!mounted) return;
|
|
final state = context.read<AppState>();
|
|
state.setResellerEnabled(_resellerEnabled);
|
|
|
|
try {
|
|
await Supabase.instance.client.from('store_settings').upsert({
|
|
'id': 'store',
|
|
'store_id': '00000000-0000-0000-0000-000000000000',
|
|
'store_name': _storeNameCtrl.text.trim(),
|
|
'currency': _currencyCtrl.text.trim(),
|
|
'whatsapp': _whatsappCtrl.text.trim(),
|
|
'notes': _notesCtrl.text.trim(),
|
|
'reseller_enabled': _resellerEnabled,
|
|
'updated_at': DateTime.now().toUtc().toIso8601String(),
|
|
});
|
|
} catch (e) {
|
|
debugPrint('Gagal simpan store settings ke Supabase: $e');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_loading) {
|
|
return const Center(child: CircularProgressIndicator(color: kAccent));
|
|
}
|
|
|
|
final state = context.watch<AppState>();
|
|
final isAdmin = state.isAdmin;
|
|
|
|
return RefreshIndicator(
|
|
onRefresh: () async {
|
|
context.read<AppState>().recordManualRefresh();
|
|
await context.read<AppState>().refresh();
|
|
_settingsSyncTimer?.cancel();
|
|
_settingsSyncTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
|
if (!_loadingSettings && mounted) {
|
|
_loadSettings();
|
|
}
|
|
});
|
|
},
|
|
color: kAccent,
|
|
child: ListView(
|
|
padding: const EdgeInsets.all(12),
|
|
children: [
|
|
if (isAdmin) ...[
|
|
_SettingsListTile(
|
|
icon: Icons.receipt_long_outlined,
|
|
title: 'Pengaturan Struk',
|
|
subtitle: _whatsappCtrl.text.isEmpty
|
|
? '${_storeNameCtrl.text} • ${_currencyCtrl.text}'
|
|
: '${_storeNameCtrl.text} • ${_currencyCtrl.text} • ${_whatsappCtrl.text}',
|
|
onTap: () => _showStoreSettingsDialog(context),
|
|
),
|
|
const SizedBox(height: 8),
|
|
_SettingsListTile(
|
|
icon: Icons.people_outline,
|
|
title: 'Akun Kasir',
|
|
subtitle: 'Tambah, edit, atau hapus akun kasir',
|
|
onTap: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => const KelolaKasirScreen(),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
const SizedBox(height: 8),
|
|
],
|
|
_SettingsListTile(
|
|
icon: Icons.print_outlined,
|
|
title: 'Printer Bluetooth',
|
|
subtitle: 'Scan, connect, dan cetak struk',
|
|
onTap: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => const PrinterScreen(),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
const SizedBox(height: 8),
|
|
if (!isAdmin)
|
|
_SettingsListTile(
|
|
icon: Icons.receipt_long_outlined,
|
|
title: 'Info Struk',
|
|
subtitle: _whatsappCtrl.text.isEmpty
|
|
? '${_storeNameCtrl.text} • ${_currencyCtrl.text}'
|
|
: '${_storeNameCtrl.text} • ${_currencyCtrl.text} • ${_whatsappCtrl.text}',
|
|
onTap: null,
|
|
),
|
|
const SizedBox(height: 24),
|
|
Center(
|
|
child: FutureBuilder<PackageInfo>(
|
|
future: PackageInfo.fromPlatform(),
|
|
builder: (context, snapshot) {
|
|
if (snapshot.hasData) {
|
|
final info = snapshot.data!;
|
|
return Text(
|
|
'Versi ${info.version} (${info.buildNumber})',
|
|
style: const TextStyle(color: kMuted, fontSize: 12),
|
|
);
|
|
}
|
|
return const Text(
|
|
'Versi ...',
|
|
style: TextStyle(color: kMuted, fontSize: 12),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _showStoreSettingsDialog(BuildContext context) async {
|
|
final storeNameCtrl = TextEditingController(text: _storeNameCtrl.text);
|
|
final currencyCtrl = TextEditingController(text: _currencyCtrl.text);
|
|
final whatsappCtrl = TextEditingController(text: _whatsappCtrl.text);
|
|
final notesCtrl = TextEditingController(text: _notesCtrl.text);
|
|
bool resellerEnabled = _resellerEnabled;
|
|
|
|
final messenger = ScaffoldMessenger.of(context);
|
|
|
|
final result = await showDialog<bool>(
|
|
context: context,
|
|
builder: (ctx) => StatefulBuilder(
|
|
builder: (ctx, setModalState) => AlertDialog(
|
|
title: const Text('Pengaturan Toko'),
|
|
content: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextField(
|
|
controller: storeNameCtrl,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Nama Toko',
|
|
prefixIcon: Icon(Icons.badge_outlined),
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
TextField(
|
|
controller: currencyCtrl,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Mata Uang',
|
|
prefixIcon: Icon(Icons.money_outlined),
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
TextField(
|
|
controller: whatsappCtrl,
|
|
keyboardType: TextInputType.phone,
|
|
decoration: const InputDecoration(
|
|
labelText: 'No. WhatsApp',
|
|
prefixIcon: Icon(Icons.phone_outlined),
|
|
helperText: 'Contoh: 081234567890',
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
TextField(
|
|
controller: notesCtrl,
|
|
maxLines: 3,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Catatan Tambahan',
|
|
prefixIcon: Icon(Icons.notes_outlined),
|
|
helperText: 'Opsional, akan dicetak di struk',
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
SwitchListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
title: const Text('Mode Reseller Aktif',
|
|
style: TextStyle(fontSize: 14)),
|
|
subtitle: const Text(
|
|
'Jika aktif, kasir bisa toggle harga reseller per transaksi',
|
|
style: TextStyle(fontSize: 12, color: kMuted)),
|
|
value: resellerEnabled,
|
|
onChanged: (v) => setModalState(() => resellerEnabled = v),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx, false),
|
|
child: const Text('Batal'),
|
|
),
|
|
const SizedBox(width: 8),
|
|
SizedBox(
|
|
width: 120,
|
|
child: FilledButton.icon(
|
|
onPressed: () async {
|
|
_storeNameCtrl.text = storeNameCtrl.text.trim();
|
|
_currencyCtrl.text = currencyCtrl.text.trim();
|
|
_whatsappCtrl.text = whatsappCtrl.text.trim();
|
|
_notesCtrl.text = notesCtrl.text.trim();
|
|
_resellerEnabled = resellerEnabled;
|
|
await _saveSettings();
|
|
if (ctx.mounted) {
|
|
Navigator.pop(ctx, true);
|
|
}
|
|
},
|
|
icon: const Icon(Icons.save_outlined, size: 18),
|
|
label: const Text('Simpan'),
|
|
style: FilledButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
if (result == true && mounted) {
|
|
messenger.showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Pengaturan tersimpan'),
|
|
backgroundColor: kSuccess,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_settingsSyncTimer?.cancel();
|
|
_storeNameCtrl.dispose();
|
|
_currencyCtrl.dispose();
|
|
_whatsappCtrl.dispose();
|
|
_notesCtrl.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
class _SettingsListTile extends StatelessWidget {
|
|
final IconData icon;
|
|
final String title;
|
|
final String subtitle;
|
|
final VoidCallback? onTap;
|
|
const _SettingsListTile({
|
|
required this.icon,
|
|
required this.title,
|
|
required this.subtitle,
|
|
this.onTap,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
child: ListTile(
|
|
leading: Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: kAccent.withValues(alpha: 0.15),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Icon(icon, color: kAccent, size: 20),
|
|
),
|
|
title: Text(
|
|
title,
|
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w700),
|
|
),
|
|
subtitle: Text(
|
|
subtitle,
|
|
style: const TextStyle(fontSize: 12, color: kMuted),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
trailing: Icon(Icons.chevron_right_rounded, color: kMuted, size: 20),
|
|
onTap: onTap,
|
|
),
|
|
);
|
|
}
|
|
}
|