permission
This commit is contained in:
parent
31defb7eff
commit
ad5cd35f8f
|
|
@ -28,6 +28,15 @@ def generate_captcha(db: Session = Depends(get_db)) -> dict:
|
|||
})
|
||||
|
||||
|
||||
@router.get("/me", summary="Get current user info")
|
||||
def get_current_user_info(
|
||||
request: Request,
|
||||
ctx: AuthContext = Depends(get_current_user)
|
||||
) -> dict:
|
||||
"""دریافت اطلاعات کاربر کنونی"""
|
||||
return success_response(ctx.to_dict(), request)
|
||||
|
||||
|
||||
@router.post("/register", summary="Register new user")
|
||||
def register(request: Request, payload: RegisterRequest, db: Session = Depends(get_db)) -> dict:
|
||||
user_id = register_user(
|
||||
|
|
|
|||
|
|
@ -1,18 +1,26 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
class AuthStore with ChangeNotifier {
|
||||
static const _kApiKey = 'auth_api_key';
|
||||
static const _kDeviceId = 'device_id';
|
||||
static const _kAppPermissions = 'app_permissions';
|
||||
static const _kIsSuperAdmin = 'is_superadmin';
|
||||
|
||||
final FlutterSecureStorage _secure = const FlutterSecureStorage();
|
||||
String? _apiKey;
|
||||
String? _deviceId;
|
||||
Map<String, dynamic>? _appPermissions;
|
||||
bool _isSuperAdmin = false;
|
||||
|
||||
String? get apiKey => _apiKey;
|
||||
String get deviceId => _deviceId ?? '';
|
||||
Map<String, dynamic>? get appPermissions => _appPermissions;
|
||||
bool get isSuperAdmin => _isSuperAdmin;
|
||||
|
||||
Future<void> load() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
|
@ -28,9 +36,56 @@ class AuthStore with ChangeNotifier {
|
|||
_apiKey = await _secure.read(key: _kApiKey);
|
||||
_apiKey ??= prefs.getString(_kApiKey);
|
||||
}
|
||||
|
||||
// بارگذاری دسترسیهای اپلیکیشن
|
||||
await _loadAppPermissions();
|
||||
|
||||
// اگر API key موجود است اما دسترسیها نیست، از سرور دریافت کن
|
||||
if (_apiKey != null && _apiKey!.isNotEmpty && (_appPermissions == null || _appPermissions!.isEmpty)) {
|
||||
await _fetchPermissionsFromServer();
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _loadAppPermissions() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
if (kIsWeb) {
|
||||
final permissionsJson = prefs.getString(_kAppPermissions);
|
||||
|
||||
if (permissionsJson != null) {
|
||||
try {
|
||||
_appPermissions = Map<String, dynamic>.from(
|
||||
const JsonDecoder().convert(permissionsJson)
|
||||
);
|
||||
} catch (e) {
|
||||
_appPermissions = null;
|
||||
}
|
||||
} else {
|
||||
_appPermissions = null;
|
||||
}
|
||||
_isSuperAdmin = prefs.getBool(_kIsSuperAdmin) ?? false;
|
||||
} else {
|
||||
try {
|
||||
final permissionsJson = await _secure.read(key: _kAppPermissions);
|
||||
|
||||
if (permissionsJson != null) {
|
||||
_appPermissions = Map<String, dynamic>.from(
|
||||
const JsonDecoder().convert(permissionsJson)
|
||||
);
|
||||
} else {
|
||||
_appPermissions = null;
|
||||
}
|
||||
final superAdminStr = await _secure.read(key: _kIsSuperAdmin);
|
||||
_isSuperAdmin = superAdminStr == 'true';
|
||||
} catch (e) {
|
||||
_appPermissions = null;
|
||||
_isSuperAdmin = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveApiKey(String? key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_apiKey = key;
|
||||
|
|
@ -43,6 +98,8 @@ class AuthStore with ChangeNotifier {
|
|||
} catch (_) {}
|
||||
await prefs.remove(_kApiKey);
|
||||
}
|
||||
// پاک کردن دسترسیها هنگام خروج
|
||||
await _clearAppPermissions();
|
||||
} else {
|
||||
if (kIsWeb) {
|
||||
await prefs.setString(_kApiKey, key);
|
||||
|
|
@ -55,6 +112,87 @@ class AuthStore with ChangeNotifier {
|
|||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> saveAppPermissions(Map<String, dynamic>? permissions, bool isSuperAdmin) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_appPermissions = permissions;
|
||||
_isSuperAdmin = isSuperAdmin;
|
||||
|
||||
if (permissions == null) {
|
||||
await _clearAppPermissions();
|
||||
} else {
|
||||
final permissionsJson = const JsonEncoder().convert(permissions);
|
||||
|
||||
if (kIsWeb) {
|
||||
await prefs.setString(_kAppPermissions, permissionsJson);
|
||||
await prefs.setBool(_kIsSuperAdmin, isSuperAdmin);
|
||||
} else {
|
||||
try {
|
||||
await _secure.write(key: _kAppPermissions, value: permissionsJson);
|
||||
await _secure.write(key: _kIsSuperAdmin, value: isSuperAdmin.toString());
|
||||
} catch (_) {
|
||||
// Fallback to SharedPreferences
|
||||
await prefs.setString(_kAppPermissions, permissionsJson);
|
||||
await prefs.setBool(_kIsSuperAdmin, isSuperAdmin);
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _clearAppPermissions() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_appPermissions = null;
|
||||
_isSuperAdmin = false;
|
||||
|
||||
if (kIsWeb) {
|
||||
await prefs.remove(_kAppPermissions);
|
||||
await prefs.remove(_kIsSuperAdmin);
|
||||
} else {
|
||||
try {
|
||||
await _secure.delete(key: _kAppPermissions);
|
||||
await _secure.delete(key: _kIsSuperAdmin);
|
||||
} catch (_) {}
|
||||
await prefs.remove(_kAppPermissions);
|
||||
await prefs.remove(_kIsSuperAdmin);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchPermissionsFromServer() async {
|
||||
if (_apiKey == null || _apiKey!.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final apiClient = ApiClient();
|
||||
final response = await apiClient.get('/api/v1/auth/me');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
if (data is Map<String, dynamic>) {
|
||||
final user = data['user'] as Map<String, dynamic>?;
|
||||
if (user != null) {
|
||||
final appPermissions = user['app_permissions'] as Map<String, dynamic>?;
|
||||
final isSuperAdmin = appPermissions?['superadmin'] == true;
|
||||
|
||||
if (appPermissions != null) {
|
||||
await saveAppPermissions(appPermissions, isSuperAdmin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silent fail - permissions will be loaded from storage
|
||||
}
|
||||
}
|
||||
|
||||
bool hasAppPermission(String permission) {
|
||||
if (_isSuperAdmin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return _appPermissions?[permission] == true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
72
hesabixUI/hesabix_ui/lib/core/permission_guard.dart
Normal file
72
hesabixUI/hesabix_ui/lib/core/permission_guard.dart
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'auth_store.dart';
|
||||
|
||||
class PermissionGuard {
|
||||
static bool checkSuperAdminAccess(AuthStore authStore) {
|
||||
return authStore.isSuperAdmin;
|
||||
}
|
||||
|
||||
static bool checkAppPermission(AuthStore authStore, String permission) {
|
||||
return authStore.hasAppPermission(permission);
|
||||
}
|
||||
|
||||
static Widget buildAccessDeniedPage() {
|
||||
return Builder(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('دسترسی غیرمجاز'),
|
||||
backgroundColor: Colors.red[50],
|
||||
foregroundColor: Colors.red[800],
|
||||
),
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.block,
|
||||
size: 80,
|
||||
color: Colors.red[400],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'دسترسی غیرمجاز',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red[800],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'شما دسترسی لازم برای مشاهده این صفحه را ندارید.',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => context.go('/user/profile/dashboard'),
|
||||
icon: const Icon(Icons.home),
|
||||
label: const Text('بازگشت به داشبورد'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red[600],
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -46,6 +46,8 @@
|
|||
"logoutConfirmMessage": "Are you sure you want to sign out?",
|
||||
"menu": "Menu"
|
||||
,
|
||||
"systemSettings": "System Settings",
|
||||
"adminTools": "Admin Tools",
|
||||
"ok": "OK",
|
||||
"cancel": "Cancel",
|
||||
"columnSettings": "Column Settings",
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@
|
|||
"support": "پشتیبانی",
|
||||
"changePassword": "تغییر کلمه عبور",
|
||||
"marketing": "بازاریابی",
|
||||
"systemSettings": "تنظیمات سیستم",
|
||||
"adminTools": "ابزارهای مدیریتی",
|
||||
"ok": "تایید",
|
||||
"cancel": "انصراف",
|
||||
"columnSettings": "تنظیمات ستونها",
|
||||
|
|
|
|||
|
|
@ -338,6 +338,18 @@ abstract class AppLocalizations {
|
|||
/// **'Menu'**
|
||||
String get menu;
|
||||
|
||||
/// No description provided for @systemSettings.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'System Settings'**
|
||||
String get systemSettings;
|
||||
|
||||
/// No description provided for @adminTools.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Admin Tools'**
|
||||
String get adminTools;
|
||||
|
||||
/// No description provided for @ok.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -130,6 +130,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get menu => 'Menu';
|
||||
|
||||
@override
|
||||
String get systemSettings => 'System Settings';
|
||||
|
||||
@override
|
||||
String get adminTools => 'Admin Tools';
|
||||
|
||||
@override
|
||||
String get ok => 'OK';
|
||||
|
||||
|
|
|
|||
|
|
@ -130,6 +130,12 @@ class AppLocalizationsFa extends AppLocalizations {
|
|||
@override
|
||||
String get menu => 'منو';
|
||||
|
||||
@override
|
||||
String get systemSettings => 'تنظیمات سیستم';
|
||||
|
||||
@override
|
||||
String get adminTools => 'ابزارهای مدیریتی';
|
||||
|
||||
@override
|
||||
String get ok => 'تایید';
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import 'pages/profile/businesses_page.dart';
|
|||
import 'pages/profile/support_page.dart';
|
||||
import 'pages/profile/change_password_page.dart';
|
||||
import 'pages/profile/marketing_page.dart';
|
||||
import 'pages/system_settings_page.dart';
|
||||
import 'package:hesabix_ui/l10n/app_localizations.dart';
|
||||
import 'core/locale_controller.dart';
|
||||
import 'core/calendar_controller.dart';
|
||||
|
|
@ -18,6 +19,7 @@ import 'core/api_client.dart';
|
|||
import 'theme/theme_controller.dart';
|
||||
import 'theme/app_theme.dart';
|
||||
import 'core/auth_store.dart';
|
||||
import 'core/permission_guard.dart';
|
||||
|
||||
void main() {
|
||||
// Use path-based routing instead of hash routing
|
||||
|
|
@ -217,6 +219,21 @@ class _MyAppState extends State<MyApp> {
|
|||
),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/user/profile/system-settings',
|
||||
builder: (context, state) => const Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
SizedBox(height: 16),
|
||||
Text('Loading...'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Catch-all route برای هر URL دیگر
|
||||
GoRoute(
|
||||
path: '/:path(.*)',
|
||||
|
|
@ -339,6 +356,21 @@ class _MyAppState extends State<MyApp> {
|
|||
name: 'profile_change_password',
|
||||
builder: (context, state) => const ChangePasswordPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/user/profile/system-settings',
|
||||
name: 'profile_system_settings',
|
||||
builder: (context, state) {
|
||||
// بررسی دسترسی SuperAdmin
|
||||
if (_authStore == null) {
|
||||
return PermissionGuard.buildAccessDeniedPage();
|
||||
}
|
||||
|
||||
if (!_authStore!.isSuperAdmin) {
|
||||
return PermissionGuard.buildAccessDeniedPage();
|
||||
}
|
||||
return const SystemSettingsPage();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -239,10 +239,19 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
|||
final apiKey = data != null ? data['api_key']?.toString() : null;
|
||||
if (apiKey != null && apiKey.isNotEmpty) {
|
||||
await widget.authStore.saveApiKey(apiKey);
|
||||
}
|
||||
|
||||
// ذخیره کد بازاریابی کاربر برای صفحه Marketing
|
||||
final user = data?['user'] as Map<String, dynamic>?;
|
||||
final String? myRef = user != null ? user['referral_code']?.toString() : null;
|
||||
unawaited(ReferralStore.saveUserReferralCode(myRef));
|
||||
|
||||
// ذخیره دسترسیهای اپلیکیشن
|
||||
final appPermissions = user?['app_permissions'] as Map<String, dynamic>?;
|
||||
final isSuperAdmin = appPermissions?['superadmin'] == true;
|
||||
|
||||
if (appPermissions != null) {
|
||||
await widget.authStore.saveAppPermissions(appPermissions, isSuperAdmin);
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
|
|
@ -327,10 +336,19 @@ class _LoginPageState extends State<LoginPage> with SingleTickerProviderStateMix
|
|||
if (apiKey != null && apiKey.isNotEmpty) {
|
||||
await widget.authStore.saveApiKey(apiKey);
|
||||
}
|
||||
|
||||
// ذخیره کد بازاریابی کاربر
|
||||
final user = data?['user'] as Map<String, dynamic>?;
|
||||
final String? myRef = user != null ? user['referral_code'] as String? : null;
|
||||
unawaited(ReferralStore.saveUserReferralCode(myRef));
|
||||
|
||||
// ذخیره دسترسیهای اپلیکیشن
|
||||
final appPermissions = user?['app_permissions'] as Map<String, dynamic>?;
|
||||
final isSuperAdmin = appPermissions?['superadmin'] == true;
|
||||
|
||||
if (appPermissions != null) {
|
||||
await widget.authStore.saveAppPermissions(appPermissions, isSuperAdmin);
|
||||
}
|
||||
_showSnack(t.registerSuccess);
|
||||
// پاکسازی کد معرف پس از ثبتنام موفق
|
||||
unawaited(ReferralStore.clearReferrer());
|
||||
|
|
|
|||
|
|
@ -25,6 +25,17 @@ class ProfileShell extends StatefulWidget {
|
|||
class _ProfileShellState extends State<ProfileShell> {
|
||||
int _hoverIndex = -1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// اضافه کردن listener برای AuthStore
|
||||
widget.authStore.addListener(() {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
|
|
@ -47,16 +58,27 @@ class _ProfileShellState extends State<ProfileShell> {
|
|||
_Dest(t.changePassword, Icons.password, Icons.password, '/user/profile/change-password'),
|
||||
];
|
||||
|
||||
// اضافه کردن منوی تنظیمات سیستم برای ادمینها
|
||||
final adminDestinations = <_Dest>[
|
||||
_Dest(t.systemSettings, Icons.admin_panel_settings, Icons.admin_panel_settings, '/user/profile/system-settings'),
|
||||
];
|
||||
|
||||
// ترکیب منوهای عادی و ادمین
|
||||
final allDestinations = <_Dest>[
|
||||
...destinations,
|
||||
if (widget.authStore.isSuperAdmin) ...adminDestinations,
|
||||
];
|
||||
|
||||
int selectedIndex = 0;
|
||||
for (int i = 0; i < destinations.length; i++) {
|
||||
if (location.startsWith(destinations[i].path)) {
|
||||
for (int i = 0; i < allDestinations.length; i++) {
|
||||
if (location.startsWith(allDestinations[i].path)) {
|
||||
selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onSelect(int index) async {
|
||||
final path = destinations[index].path;
|
||||
final path = allDestinations[index].path;
|
||||
if (GoRouterState.of(context).uri.toString() != path) {
|
||||
context.go(path);
|
||||
}
|
||||
|
|
@ -150,9 +172,9 @@ class _ProfileShellState extends State<ProfileShell> {
|
|||
color: sideBg,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: destinations.length,
|
||||
itemCount: allDestinations.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final d = destinations[i];
|
||||
final d = allDestinations[i];
|
||||
final bool isHovered = i == _hoverIndex;
|
||||
final bool isSelected = i == selectedIndex;
|
||||
final bool active = isSelected || isHovered;
|
||||
|
|
@ -216,9 +238,9 @@ class _ProfileShellState extends State<ProfileShell> {
|
|||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
children: [
|
||||
for (int i = 0; i < destinations.length; i++) ...[
|
||||
for (int i = 0; i < allDestinations.length; i++) ...[
|
||||
Builder(builder: (ctx) {
|
||||
final d = destinations[i];
|
||||
final d = allDestinations[i];
|
||||
final bool active = i == selectedIndex;
|
||||
return ListTile(
|
||||
leading: Icon(d.selectedIcon, color: active ? activeFg : sideFg),
|
||||
|
|
|
|||
233
hesabixUI/hesabix_ui/lib/pages/system_settings_page.dart
Normal file
233
hesabixUI/hesabix_ui/lib/pages/system_settings_page.dart
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hesabix_ui/l10n/app_localizations.dart';
|
||||
|
||||
class SystemSettingsPage extends StatelessWidget {
|
||||
const SystemSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.systemSettings),
|
||||
backgroundColor: colorScheme.surface,
|
||||
foregroundColor: colorScheme.onSurface,
|
||||
elevation: 0,
|
||||
),
|
||||
body: Container(
|
||||
color: colorScheme.surface,
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.admin_panel_settings,
|
||||
size: 32,
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
t.systemSettings,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'تنظیمات پیشرفته سیستم - فقط برای ادمینها',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onPrimaryContainer.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Settings Cards
|
||||
Expanded(
|
||||
child: GridView.count(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 16,
|
||||
mainAxisSpacing: 16,
|
||||
childAspectRatio: 1.5,
|
||||
children: [
|
||||
_buildSettingCard(
|
||||
context,
|
||||
icon: Icons.people,
|
||||
title: 'مدیریت کاربران',
|
||||
subtitle: 'مدیریت کاربران سیستم',
|
||||
color: Colors.blue,
|
||||
),
|
||||
_buildSettingCard(
|
||||
context,
|
||||
icon: Icons.business,
|
||||
title: 'مدیریت کسب و کارها',
|
||||
subtitle: 'مدیریت کسب و کارهای ثبت شده',
|
||||
color: Colors.green,
|
||||
),
|
||||
_buildSettingCard(
|
||||
context,
|
||||
icon: Icons.security,
|
||||
title: 'امنیت سیستم',
|
||||
subtitle: 'تنظیمات امنیتی و دسترسیها',
|
||||
color: Colors.orange,
|
||||
),
|
||||
_buildSettingCard(
|
||||
context,
|
||||
icon: Icons.analytics,
|
||||
title: 'گزارشگیری',
|
||||
subtitle: 'گزارشهای سیستم و آمار',
|
||||
color: Colors.purple,
|
||||
),
|
||||
_buildSettingCard(
|
||||
context,
|
||||
icon: Icons.backup,
|
||||
title: 'پشتیبانگیری',
|
||||
subtitle: 'مدیریت پشتیبانها',
|
||||
color: Colors.teal,
|
||||
),
|
||||
_buildSettingCard(
|
||||
context,
|
||||
icon: Icons.tune,
|
||||
title: 'تنظیمات پیشرفته',
|
||||
subtitle: 'تنظیمات تخصصی سیستم',
|
||||
color: Colors.indigo,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Warning Message
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.amber.withOpacity(0.1),
|
||||
border: Border.all(color: Colors.amber.withOpacity(0.3)),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.warning_amber,
|
||||
color: Colors.amber[700],
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'توجه: این بخش فقط برای ادمینهای سیستم قابل دسترسی است. تغییرات در این بخش میتواند بر عملکرد کل سیستم تأثیر بگذارد.',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: Colors.amber[700],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSettingCard(
|
||||
BuildContext context, {
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required Color color,
|
||||
}) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () {
|
||||
// TODO: Navigate to specific setting
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('$title - در حال توسعه'),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: color,
|
||||
size: 32,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
title,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
subtitle,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurface.withOpacity(0.7),
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: colorScheme.onSurface.withOpacity(0.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue