332 lines
11 KiB
Dart
332 lines
11 KiB
Dart
import 'package:dio/dio.dart';
|
|
import '../core/api_client.dart';
|
|
import '../models/business_dashboard_models.dart';
|
|
import 'business_dashboard_service.dart';
|
|
import 'support_service.dart';
|
|
|
|
/// سرویس داشبورد پروفایل کاربر
|
|
///
|
|
/// تلاش میکند از اندپوینتهای پروفایل استفاده کند. در صورت عدم وجود
|
|
/// (مثلاً 404)، برای MVP از fallback محلی استفاده میکند تا UI کار کند.
|
|
class ProfileDashboardService {
|
|
final ApiClient _apiClient;
|
|
final BusinessDashboardService _businessService;
|
|
|
|
ProfileDashboardService(this._apiClient)
|
|
: _businessService = BusinessDashboardService(_apiClient);
|
|
|
|
// --- تعاریف ویجتها ---
|
|
Future<DashboardDefinitionsResponse> getWidgetDefinitions() async {
|
|
try {
|
|
final res = await _apiClient.get<Map<String, dynamic>>(
|
|
'/api/v1/profile/dashboard/widgets/definitions',
|
|
);
|
|
final data = res.data?['data'] as Map<String, dynamic>? ?? const {};
|
|
final defs = DashboardDefinitionsResponse.fromJson(data);
|
|
if (defs.items.isEmpty || defs.columns.isEmpty) {
|
|
return _fallbackDefinitions();
|
|
}
|
|
return defs;
|
|
} on DioException catch (e) {
|
|
// اگر اندپوینت وجود ندارد یا هر خطایی بود، fallback
|
|
if (e.response?.statusCode == 404) {
|
|
return _fallbackDefinitions();
|
|
}
|
|
return _fallbackDefinitions();
|
|
} catch (_) {
|
|
return _fallbackDefinitions();
|
|
}
|
|
}
|
|
|
|
// --- پروفایل چیدمان ---
|
|
Future<DashboardLayoutProfile> getLayoutProfile({
|
|
required String breakpoint,
|
|
}) async {
|
|
try {
|
|
final res = await _apiClient.get<Map<String, dynamic>>(
|
|
'/api/v1/profile/dashboard/layout',
|
|
query: {'breakpoint': breakpoint},
|
|
);
|
|
final data = res.data?['data'] as Map<String, dynamic>? ?? const {};
|
|
final profile = DashboardLayoutProfile.fromJson(data);
|
|
if (profile.items.isEmpty) {
|
|
// از پیشفرض مبتنی بر تعاریف بساز
|
|
final defs = await getWidgetDefinitions();
|
|
return _buildDefaultLayout(defs, breakpoint);
|
|
}
|
|
return profile;
|
|
} on DioException catch (e) {
|
|
if (e.response?.statusCode == 404) {
|
|
final defs = await getWidgetDefinitions();
|
|
return _buildDefaultLayout(defs, breakpoint);
|
|
}
|
|
final defs = await getWidgetDefinitions();
|
|
return _buildDefaultLayout(defs, breakpoint);
|
|
} catch (_) {
|
|
final defs = await getWidgetDefinitions();
|
|
return _buildDefaultLayout(defs, breakpoint);
|
|
}
|
|
}
|
|
|
|
Future<DashboardLayoutProfile> putLayoutProfile({
|
|
required String breakpoint,
|
|
required List<DashboardLayoutItem> items,
|
|
}) async {
|
|
try {
|
|
final body = {
|
|
'breakpoint': breakpoint,
|
|
'items': items.map((e) => e.toJson()).toList(),
|
|
};
|
|
final res = await _apiClient.put<Map<String, dynamic>>(
|
|
'/api/v1/profile/dashboard/layout',
|
|
data: body,
|
|
);
|
|
final data = res.data?['data'] as Map<String, dynamic>? ?? const {};
|
|
return DashboardLayoutProfile.fromJson(data);
|
|
} catch (_) {
|
|
// اگر ذخیره نشد، همان ورودی را به عنوان حالت فعلی برگردان
|
|
return DashboardLayoutProfile(
|
|
breakpoint: breakpoint,
|
|
columns: _fallbackColumns()[breakpoint] ?? 8,
|
|
items: items,
|
|
version: 1,
|
|
updatedAt: '',
|
|
);
|
|
}
|
|
}
|
|
|
|
// --- دادهی ویجتها (Batch) ---
|
|
Future<Map<String, dynamic>> getWidgetsBatchData({
|
|
required List<String> widgetKeys,
|
|
Map<String, dynamic>? filters,
|
|
}) async {
|
|
// تلاش برای استفاده از اندپوینت پروفایل
|
|
try {
|
|
final response = await _apiClient.post<Map<String, dynamic>>(
|
|
'/api/v1/profile/dashboard/data',
|
|
data: {
|
|
'widget_keys': widgetKeys,
|
|
'filters': filters ?? const <String, dynamic>{},
|
|
},
|
|
);
|
|
if (response.data?['success'] == true) {
|
|
final data = response.data!['data'] as Map<String, dynamic>? ?? const {};
|
|
final out = Map<String, dynamic>.from(data);
|
|
// پر کردن دادهی ویجتهایی که سرور نداد با fallback
|
|
return _withFallbackData(out, widgetKeys);
|
|
}
|
|
} catch (_) {
|
|
// ادامه میدهیم تا fallback پر شود
|
|
}
|
|
return _fallbackBatchData(widgetKeys);
|
|
}
|
|
|
|
// --- Fallbacks ---
|
|
DashboardDefinitionsResponse _fallbackDefinitions() {
|
|
final columns = _fallbackColumns();
|
|
final items = <DashboardWidgetDefinition>[
|
|
DashboardWidgetDefinition(
|
|
key: 'profile_recent_businesses',
|
|
title: 'کسبوکارهای شما',
|
|
icon: 'business',
|
|
version: 1,
|
|
permissionsRequired: const <String>[],
|
|
defaults: {
|
|
'xs': {'colSpan': 1, 'rowSpan': 2},
|
|
'sm': {'colSpan': 2, 'rowSpan': 2},
|
|
'md': {'colSpan': 4, 'rowSpan': 2},
|
|
'lg': {'colSpan': 4, 'rowSpan': 2},
|
|
'xl': {'colSpan': 4, 'rowSpan': 2},
|
|
},
|
|
),
|
|
DashboardWidgetDefinition(
|
|
key: 'profile_announcements',
|
|
title: 'اعلانها',
|
|
icon: 'notifications',
|
|
version: 1,
|
|
permissionsRequired: const <String>[],
|
|
defaults: {
|
|
'xs': {'colSpan': 1, 'rowSpan': 2},
|
|
'sm': {'colSpan': 2, 'rowSpan': 2},
|
|
'md': {'colSpan': 4, 'rowSpan': 2},
|
|
'lg': {'colSpan': 4, 'rowSpan': 2},
|
|
'xl': {'colSpan': 4, 'rowSpan': 2},
|
|
},
|
|
),
|
|
DashboardWidgetDefinition(
|
|
key: 'profile_support_tickets',
|
|
title: 'تیکتهای پشتیبانی',
|
|
icon: 'support_agent',
|
|
version: 1,
|
|
permissionsRequired: const <String>[],
|
|
defaults: {
|
|
'xs': {'colSpan': 1, 'rowSpan': 2},
|
|
'sm': {'colSpan': 2, 'rowSpan': 2},
|
|
'md': {'colSpan': 4, 'rowSpan': 2},
|
|
'lg': {'colSpan': 4, 'rowSpan': 2},
|
|
'xl': {'colSpan': 4, 'rowSpan': 2},
|
|
},
|
|
),
|
|
DashboardWidgetDefinition(
|
|
key: 'profile_onboarding_checklist',
|
|
title: 'چکلیست شروع',
|
|
icon: 'checklist',
|
|
version: 1,
|
|
permissionsRequired: const <String>[],
|
|
defaults: {
|
|
'xs': {'colSpan': 1, 'rowSpan': 2},
|
|
'sm': {'colSpan': 2, 'rowSpan': 2},
|
|
'md': {'colSpan': 4, 'rowSpan': 2},
|
|
'lg': {'colSpan': 4, 'rowSpan': 2},
|
|
'xl': {'colSpan': 4, 'rowSpan': 2},
|
|
},
|
|
),
|
|
];
|
|
return DashboardDefinitionsResponse(columns: columns, items: items);
|
|
}
|
|
|
|
Map<String, int> _fallbackColumns() => const <String, int>{
|
|
'xs': 1,
|
|
'sm': 4,
|
|
'md': 8,
|
|
'lg': 12,
|
|
'xl': 12,
|
|
};
|
|
|
|
DashboardLayoutProfile _buildDefaultLayout(
|
|
DashboardDefinitionsResponse defs,
|
|
String breakpoint,
|
|
) {
|
|
final cols = defs.columns[breakpoint] ?? 8;
|
|
int order = 1;
|
|
final items = <DashboardLayoutItem>[];
|
|
for (final d in defs.items) {
|
|
final dflt = d.defaults[breakpoint] ?? const <String, int>{};
|
|
final colSpan =
|
|
(dflt['colSpan'] ?? (cols / 2).floor()).clamp(1, cols);
|
|
final rowSpan = dflt['rowSpan'] ?? 2;
|
|
items.add(DashboardLayoutItem(
|
|
key: d.key,
|
|
order: order++,
|
|
colSpan: colSpan,
|
|
rowSpan: rowSpan,
|
|
hidden: false,
|
|
));
|
|
}
|
|
return DashboardLayoutProfile(
|
|
breakpoint: breakpoint,
|
|
columns: cols,
|
|
items: items,
|
|
version: 1,
|
|
updatedAt: '',
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> _fallbackBatchData(List<String> keys) {
|
|
final out = <String, dynamic>{};
|
|
return _withFallbackData(out, keys);
|
|
}
|
|
|
|
Map<String, dynamic> _withFallbackData(
|
|
Map<String, dynamic> base,
|
|
List<String> keys,
|
|
) {
|
|
final out = Map<String, dynamic>.from(base);
|
|
for (final k in keys) {
|
|
if (out.containsKey(k)) continue;
|
|
if (k == 'profile_recent_businesses') {
|
|
out[k] = {
|
|
'items': <Map<String, dynamic>>[],
|
|
};
|
|
} else if (k == 'profile_announcements') {
|
|
out[k] = {
|
|
'items': <Map<String, dynamic>>[
|
|
{
|
|
'title': 'به حسابیکس خوش آمدید',
|
|
'body': 'بهزودی تجربه داشبورد شخصیسازیشده را خواهید داشت.',
|
|
'time': DateTime.now().toIso8601String(),
|
|
},
|
|
],
|
|
};
|
|
} else if (k == 'profile_support_tickets') {
|
|
out[k] = {
|
|
'items': <Map<String, dynamic>>[
|
|
{
|
|
'id': 1001,
|
|
'subject': 'سؤال درباره صدور فاکتور',
|
|
'status': 'باز',
|
|
'updated_at': DateTime.now().subtract(const Duration(hours: 3)).toIso8601String(),
|
|
},
|
|
{
|
|
'id': 1000,
|
|
'subject': 'مشکل ورود به حساب',
|
|
'status': 'بسته',
|
|
'updated_at': DateTime.now().subtract(const Duration(days: 2)).toIso8601String(),
|
|
},
|
|
],
|
|
};
|
|
} else if (k == 'profile_onboarding_checklist') {
|
|
out[k] = {
|
|
'items': <Map<String, dynamic>>[
|
|
{'key': 'create_business', 'title': 'ایجاد اولین کسبوکار', 'done': false},
|
|
{'key': 'add_person', 'title': 'افزودن اولین مخاطب', 'done': false},
|
|
{'key': 'issue_invoice', 'title': 'صدور اولین فاکتور', 'done': false},
|
|
],
|
|
};
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// کمکمتد برای تأمین داده واقعی برخی ویجتها (مثل لیست کسبوکارها)
|
|
Future<Map<String, dynamic>> hydrateSpecialWidgets(
|
|
Map<String, dynamic> currentData,
|
|
List<String> keys,
|
|
) async {
|
|
final out = Map<String, dynamic>.from(currentData);
|
|
if (keys.contains('profile_recent_businesses')) {
|
|
try {
|
|
final businesses = await _businessService.getUserBusinesses();
|
|
out['profile_recent_businesses'] = {
|
|
'items': businesses
|
|
.map((b) => {
|
|
'id': b.id,
|
|
'name': b.name,
|
|
'role': b.role,
|
|
'is_owner': b.isOwner,
|
|
})
|
|
.toList(),
|
|
};
|
|
} catch (_) {
|
|
// در سکوت ادامه میدهیم؛ دادهی موجود کافی است
|
|
}
|
|
}
|
|
if (keys.contains('profile_support_tickets')) {
|
|
try {
|
|
final support = SupportService(_apiClient);
|
|
final res = await support.searchUserTickets({
|
|
'page': 1,
|
|
'limit': 5,
|
|
'sort_by': 'updated_at',
|
|
'sort_desc': true,
|
|
});
|
|
out['profile_support_tickets'] = {
|
|
'items': res.items.map((t) {
|
|
return {
|
|
'id': t.id,
|
|
'subject': t.title,
|
|
'status': t.status?.name ?? '',
|
|
'updated_at': t.updatedAt.toIso8601String(),
|
|
};
|
|
}).toList(),
|
|
};
|
|
} catch (_) {
|
|
// fallback باقی میماند
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
}
|
|
|
|
|