204 lines
6.9 KiB
Dart
204 lines
6.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:dio/dio.dart';
|
|
import '../../../core/storage/secure_storage.dart';
|
|
import '../../../core/utils/app_snackbar.dart';
|
|
import '../../../core/utils/logger.dart';
|
|
import '../../../app/routes/app_pages.dart';
|
|
|
|
class DashboardController extends GetxController {
|
|
final SecureStorage _storage = SecureStorage();
|
|
final Dio _dio = Dio(BaseOptions(
|
|
baseUrl: 'https://musadaq.intaleqapp.com/api/v1',
|
|
connectTimeout: const Duration(seconds: 15),
|
|
receiveTimeout: const Duration(seconds: 15),
|
|
responseType: ResponseType.json,
|
|
));
|
|
|
|
var isLoading = true.obs;
|
|
var stats = {}.obs;
|
|
var recentActivities = [].obs;
|
|
var userRole = ''.obs;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
_loadDashboardData();
|
|
}
|
|
|
|
Future<void> _loadDashboardData() async {
|
|
try {
|
|
isLoading.value = true;
|
|
final token = await _storage.getToken();
|
|
|
|
if (token == null || token.isEmpty) {
|
|
Get.offAllNamed(AppRoutes.PHONE_INPUT);
|
|
return;
|
|
}
|
|
|
|
_dio.options.headers['Authorization'] = 'Bearer $token';
|
|
|
|
// Fetch Stats
|
|
final statsResponse = await _dio.get('/dashboard/stats');
|
|
if (statsResponse.data['success'] == true) {
|
|
stats.value = statsResponse.data['data'];
|
|
userRole.value = statsResponse.data['data']['role'] ?? '';
|
|
}
|
|
|
|
// Fetch Recent Activity
|
|
final activityResponse = await _dio.get('/dashboard/recent-activity');
|
|
if (activityResponse.data['success'] == true) {
|
|
recentActivities.value = activityResponse.data['data'];
|
|
}
|
|
} on DioException catch (e) {
|
|
AppLogger.error('Dashboard Data Fetch Error', e);
|
|
if (e.response?.statusCode == 401 || e.response?.statusCode == 403) {
|
|
await logout();
|
|
} else {
|
|
AppSnackbar.showError(
|
|
'خطأ', 'فشل في جلب البيانات. الرجاء التحقق من اتصالك بالإنترنت.');
|
|
}
|
|
} catch (e) {
|
|
AppLogger.error('Unexpected error fetching dashboard', e);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
await _storage.clearAll();
|
|
Get.offAllNamed(AppRoutes.PHONE_INPUT);
|
|
}
|
|
|
|
void startVoiceAssistant() {
|
|
final textController = TextEditingController();
|
|
|
|
Get.bottomSheet(
|
|
Container(
|
|
padding: const EdgeInsets.all(24),
|
|
decoration: BoxDecoration(
|
|
color: Get.isDarkMode ? const Color(0xFF1E1E2E) : Colors.white,
|
|
borderRadius: const BorderRadius.only(
|
|
topLeft: Radius.circular(30), topRight: Radius.circular(30)),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 40,
|
|
height: 4,
|
|
margin: const EdgeInsets.bottom(20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.withOpacity(0.3),
|
|
borderRadius: BorderRadius.circular(2)),
|
|
),
|
|
Row(
|
|
children: [
|
|
const Icon(Icons.auto_awesome, color: Color(0xFF5EEAD4)),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
'المساعد الذكي (Grok-Beta)',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: Get.isDarkMode
|
|
? Colors.white
|
|
: const Color(0xFF0F4C81)),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
TextField(
|
|
autofocus: true,
|
|
controller: textController,
|
|
style: TextStyle(
|
|
color: Get.isDarkMode ? Colors.white : Colors.black),
|
|
decoration: InputDecoration(
|
|
hintText: 'اكتب أمرك هنا (مثلاً: أريد رؤية الفواتير)...',
|
|
hintStyle: const TextStyle(fontSize: 14, color: Colors.grey),
|
|
filled: true,
|
|
fillColor: Get.isDarkMode
|
|
? const Color(0xFF1A1A2E)
|
|
: const Color(0xFFF1F5F9),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
borderSide: BorderSide.none),
|
|
prefixIcon: const Icon(Icons.mic_none_rounded, color: Colors.grey),
|
|
suffixIcon: IconButton(
|
|
icon:
|
|
const Icon(Icons.send_rounded, color: Color(0xFF0F4C81)),
|
|
onPressed: () => _handleVoiceCommand(textController.text),
|
|
),
|
|
),
|
|
onSubmitted: (val) => _handleVoiceCommand(val),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'ملاحظة: جاري تفعيل ميزة التعرف الصوتي المباشر...',
|
|
style:
|
|
TextStyle(fontSize: 11, color: Colors.grey.withOpacity(0.7)),
|
|
),
|
|
const SizedBox(height: 20),
|
|
],
|
|
),
|
|
),
|
|
isScrollControlled: true,
|
|
);
|
|
}
|
|
|
|
Future<void> _handleVoiceCommand(String text) async {
|
|
if (text.trim().isEmpty) return;
|
|
|
|
Get.back(); // Close bottom sheet
|
|
AppSnackbar.showWarning('جاري التحليل...', 'يتم تحليل طلبك بواسطة Grok AI');
|
|
|
|
try {
|
|
final token = await _storage.getToken();
|
|
final response = await _dio.post(
|
|
'/voice/parse-intent-grok',
|
|
data: {'text': text},
|
|
options: Options(headers: {'Authorization': 'Bearer $token'}),
|
|
);
|
|
|
|
if (response.data['success'] == true) {
|
|
final action = response.data['data']['action'];
|
|
final params = response.data['data']['params'];
|
|
final confirmation = response.data['data']['confirmation'] ?? 'تم!';
|
|
|
|
AppSnackbar.showSuccess('تم!', confirmation);
|
|
_executeAction(action, params);
|
|
} else {
|
|
AppSnackbar.showError(
|
|
'خطأ', response.data['message'] ?? 'فشل تحليل الطلب');
|
|
}
|
|
} catch (e) {
|
|
AppLogger.error('Voice Assistant Error', e);
|
|
AppSnackbar.showError(
|
|
'خطأ', 'حدث خطأ أثناء التواصل مع خادم الذكاء الاصطناعي');
|
|
}
|
|
}
|
|
|
|
void _executeAction(String action, dynamic params) {
|
|
switch (action) {
|
|
case 'list_invoices':
|
|
Get.toNamed(AppRoutes.INVOICES);
|
|
break;
|
|
case 'open_scanner':
|
|
Get.toNamed(AppRoutes.SCANNER);
|
|
break;
|
|
case 'navigate':
|
|
final screen = params['screen']?.toString().toLowerCase();
|
|
if (screen == 'settings') Get.toNamed(AppRoutes.SETTINGS);
|
|
if (screen == 'dashboard') Get.back();
|
|
break;
|
|
default:
|
|
AppSnackbar.showWarning(
|
|
'تنبيه', 'الأمر مفهوم ولكن لم يتم ربط الأكشن برمجياً بعد.');
|
|
}
|
|
}
|
|
|
|
void refreshData() {
|
|
_loadDashboardData();
|
|
}
|
|
}
|