diff --git a/lib/features/front_hall/data/datasources/quick_links_datasource.dart b/lib/features/front_hall/data/datasources/quick_links_datasource.dart index f3eddcc..239955b 100644 --- a/lib/features/front_hall/data/datasources/quick_links_datasource.dart +++ b/lib/features/front_hall/data/datasources/quick_links_datasource.dart @@ -8,27 +8,35 @@ part 'quick_links_datasource.g.dart'; /// Data source for quick link operations. /// -/// Provides CRUD operations for Front Hall quick links via Core API. +/// Provides CRUD operations for Front Hall quick links via Core API +/// dashboard/quick-links endpoint. class QuickLinksDatasource { QuickLinksDatasource(this._dio); final Dio _dio; - static const _basePath = '/front-hall/quick-links'; + static const _basePath = '/dashboard/quick-links'; /// Gets all quick links. + /// + /// API returns: {"links": [...], "total": N} Future> getQuickLinks() async { - final response = await _dio.get>(_basePath); - final data = response.data ?? []; + final response = await _dio.get>(_basePath); + final data = response.data; - return data + if (data == null) { + return []; + } + + final links = data['links'] as List? ?? []; + return links .map((json) => QuickLinkModel.fromJson(json as Map)) .map((model) => model.toEntity()) .toList(); } /// Gets a single quick link by ID. - Future getQuickLink(String id) async { + Future getQuickLink(int id) async { final response = await _dio.get>('$_basePath/$id'); final data = response.data; @@ -44,7 +52,7 @@ class QuickLinksDatasource { final model = QuickLinkModel.fromEntity(link); final response = await _dio.post>( _basePath, - data: model.toJson(), + data: model.toCreateJson(), ); final data = response.data; @@ -58,9 +66,10 @@ class QuickLinksDatasource { /// Updates an existing quick link. Future updateQuickLink(QuickLink link) async { final model = QuickLinkModel.fromEntity(link); + final id = int.tryParse(link.id) ?? 0; final response = await _dio.put>( - '$_basePath/${link.id}', - data: model.toJson(), + '$_basePath/$id', + data: model.toCreateJson(), ); final data = response.data; @@ -72,15 +81,17 @@ class QuickLinksDatasource { } /// Deletes a quick link. - Future deleteQuickLink(String id) async { + Future deleteQuickLink(int id) async { await _dio.delete('$_basePath/$id'); } - /// Reorders quick links by updating sort orders. - Future reorderQuickLinks(List orderedIds) async { - await _dio.put( + /// Reorders quick links by updating positions. + /// + /// API expects: {"link_ids": [1, 2, 3]} + Future reorderQuickLinks(List orderedIds) async { + await _dio.post( '$_basePath/reorder', - data: {'ids': orderedIds}, + data: {'link_ids': orderedIds}, ); } } diff --git a/lib/features/front_hall/data/models/quick_link_model.dart b/lib/features/front_hall/data/models/quick_link_model.dart index d7c1976..b02aeab 100644 --- a/lib/features/front_hall/data/models/quick_link_model.dart +++ b/lib/features/front_hall/data/models/quick_link_model.dart @@ -5,17 +5,25 @@ part 'quick_link_model.freezed.dart'; part 'quick_link_model.g.dart'; /// Quick link data model for API serialization. +/// +/// Maps to Core API dashboard/quick-links endpoint: +/// - title (API) ↔ name (Flutter entity) +/// - icon (API) ↔ iconName (Flutter entity) +/// - position (API) ↔ sortOrder (Flutter entity) +/// - is_visible (API) ↔ isActive (Flutter entity) @freezed sealed class QuickLinkModel with _$QuickLinkModel { const factory QuickLinkModel({ - required String id, - required String name, + required int id, + required String title, required String url, - @JsonKey(name: 'icon_name') required String iconName, + String? icon, + String? description, String? category, - @Default('iframe') String type, - @JsonKey(name: 'sort_order') @Default(0) int sortOrder, - @JsonKey(name: 'is_active') @Default(true) bool isActive, + @Default(0) int position, + @JsonKey(name: 'is_visible') @Default(true) bool isVisible, + String? color, + @JsonKey(name: 'background_color') String? backgroundColor, }) = _QuickLinkModel; const QuickLinkModel._(); @@ -26,36 +34,42 @@ sealed class QuickLinkModel with _$QuickLinkModel { /// Converts to domain entity. QuickLink toEntity() { return QuickLink( - id: id, - name: name, + id: id.toString(), + name: title, url: url, - iconName: iconName, + iconName: icon ?? 'link', category: category, - type: _parseType(type), - sortOrder: sortOrder, - isActive: isActive, + type: QuickLinkType.iframe, + sortOrder: position, + isActive: isVisible, ); } /// Creates model from domain entity. factory QuickLinkModel.fromEntity(QuickLink entity) { return QuickLinkModel( - id: entity.id, - name: entity.name, + id: int.tryParse(entity.id) ?? 0, + title: entity.name, url: entity.url, - iconName: entity.iconName, + icon: entity.iconName, category: entity.category, - type: entity.type == QuickLinkType.iframe ? 'iframe' : 'new_tab', - sortOrder: entity.sortOrder, - isActive: entity.isActive, + position: entity.sortOrder, + isVisible: entity.isActive, ); } - QuickLinkType _parseType(String type) { - return switch (type.toLowerCase()) { - 'iframe' => QuickLinkType.iframe, - 'new_tab' || 'newtab' => QuickLinkType.newTab, - _ => QuickLinkType.iframe, + /// Creates a JSON map for creating a new quick link (without id). + Map toCreateJson() { + return { + 'title': title, + 'url': url, + if (icon != null) 'icon': icon, + if (description != null) 'description': description, + if (category != null) 'category': category, + 'position': position, + 'is_visible': isVisible, + if (color != null) 'color': color, + if (backgroundColor != null) 'background_color': backgroundColor, }; } } diff --git a/lib/features/front_hall/presentation/providers/quick_links_provider.dart b/lib/features/front_hall/presentation/providers/quick_links_provider.dart index 3ffc1c7..b3d3067 100644 --- a/lib/features/front_hall/presentation/providers/quick_links_provider.dart +++ b/lib/features/front_hall/presentation/providers/quick_links_provider.dart @@ -33,7 +33,11 @@ Future quickLink(Ref ref, String id) async { // If not in defaults, try API try { final datasource = ref.watch(quickLinksDatasourceProvider); - return await datasource.getQuickLink(id); + final intId = int.tryParse(id); + if (intId == null) { + throw Exception('Invalid link ID: $id'); + } + return await datasource.getQuickLink(intId); } catch (_) { throw Exception('Link not found: $id'); } @@ -45,49 +49,82 @@ class QuickLinkActions extends _$QuickLinkActions { @override AsyncValue build() => const AsyncValue.data(null); + /// Safely sets state, ignoring disposal errors. + void _safeSetState(AsyncValue newState) { + try { + state = newState; + } catch (_) { + // Provider was disposed - ignore + } + } + + /// Safely invalidates providers after async operations. + void _safeInvalidate(List providers) { + try { + for (final provider in providers) { + ref.invalidate(provider); + } + } catch (_) { + // Provider was disposed - ignore + } + } + /// Creates a new quick link. Future create(QuickLink link) async { - state = const AsyncValue.loading(); QuickLink? result; - state = await AsyncValue.guard(() async { + try { + _safeSetState(const AsyncValue.loading()); final datasource = ref.read(quickLinksDatasourceProvider); result = await datasource.createQuickLink(link); - ref.invalidate(quickLinksProvider); - }); + _safeInvalidate([quickLinksProvider]); + _safeSetState(const AsyncValue.data(null)); + } catch (e, st) { + _safeSetState(AsyncValue.error(e, st)); + } return result; } /// Updates an existing quick link. Future update(QuickLink link) async { - state = const AsyncValue.loading(); QuickLink? result; - state = await AsyncValue.guard(() async { + try { + _safeSetState(const AsyncValue.loading()); final datasource = ref.read(quickLinksDatasourceProvider); result = await datasource.updateQuickLink(link); - ref.invalidate(quickLinksProvider); - ref.invalidate(quickLinkProvider(link.id)); - }); + _safeInvalidate([quickLinksProvider, quickLinkProvider(link.id)]); + _safeSetState(const AsyncValue.data(null)); + } catch (e, st) { + _safeSetState(AsyncValue.error(e, st)); + } return result; } /// Deletes a quick link. Future delete(String id) async { - state = const AsyncValue.loading(); - state = await AsyncValue.guard(() async { + try { + _safeSetState(const AsyncValue.loading()); final datasource = ref.read(quickLinksDatasourceProvider); - await datasource.deleteQuickLink(id); - ref.invalidate(quickLinksProvider); - }); + final intId = int.tryParse(id) ?? 0; + await datasource.deleteQuickLink(intId); + _safeInvalidate([quickLinksProvider]); + _safeSetState(const AsyncValue.data(null)); + } catch (e, st) { + _safeSetState(AsyncValue.error(e, st)); + } } /// Reorders quick links. Future reorder(List orderedIds) async { - state = const AsyncValue.loading(); - state = await AsyncValue.guard(() async { + try { + _safeSetState(const AsyncValue.loading()); final datasource = ref.read(quickLinksDatasourceProvider); - await datasource.reorderQuickLinks(orderedIds); - ref.invalidate(quickLinksProvider); - }); + final intIds = orderedIds.map((id) => int.tryParse(id) ?? 0).toList(); + await datasource.reorderQuickLinks(intIds); + _safeInvalidate([quickLinksProvider]); + _safeSetState(const AsyncValue.data(null)); + } catch (e, st) { + _safeSetState(AsyncValue.error(e, st)); + } } }