Fixed unable to edit Team
This commit is contained in:
parent
15ce7b7a10
commit
35eae623d8
|
|
@ -6,7 +6,8 @@ import '../../models/team.dart';
|
|||
import '../../providers/teams_provider.dart';
|
||||
import '../../providers/profile_provider.dart';
|
||||
import '../../providers/tickets_provider.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import '../../providers/supabase_provider.dart';
|
||||
import '../../utils/supabase_response.dart';
|
||||
import 'package:tasq/widgets/multi_select_picker.dart';
|
||||
import '../../theme/app_surfaces.dart';
|
||||
import '../../widgets/tasq_adaptive_list.dart';
|
||||
|
|
@ -395,7 +396,7 @@ class _TeamsScreenState extends ConsumerState<TeamsScreen> {
|
|||
selectedMembers = [leaderId!, ...selectedMembers];
|
||||
}
|
||||
|
||||
final client = Supabase.instance.client;
|
||||
final client = ref.read(supabaseClientProvider);
|
||||
try {
|
||||
if (isEdit) {
|
||||
// update team row
|
||||
|
|
@ -407,38 +408,24 @@ class _TeamsScreenState extends ConsumerState<TeamsScreen> {
|
|||
'office_ids': selectedOffices,
|
||||
})
|
||||
.eq('id', team.id);
|
||||
if (upRes['error'] != null) {
|
||||
final err = upRes['error'];
|
||||
throw Exception(
|
||||
err is Map ? (err['message'] ?? err.toString()) : err.toString(),
|
||||
);
|
||||
}
|
||||
final upErr = extractSupabaseError(upRes);
|
||||
if (upErr != null) throw Exception(upErr);
|
||||
|
||||
// replace members for the team
|
||||
final delRes = await client
|
||||
.from('team_members')
|
||||
.delete()
|
||||
.eq('team_id', team.id);
|
||||
if (delRes['error'] != null) {
|
||||
final err = delRes['error'];
|
||||
throw Exception(
|
||||
err is Map ? (err['message'] ?? err.toString()) : err.toString(),
|
||||
);
|
||||
}
|
||||
final delErr = extractSupabaseError(delRes);
|
||||
if (delErr != null) throw Exception(delErr);
|
||||
|
||||
if (selectedMembers.isNotEmpty) {
|
||||
final rows = selectedMembers
|
||||
.map((u) => {'team_id': team.id, 'user_id': u})
|
||||
.toList();
|
||||
final memRes = await client.from('team_members').insert(rows);
|
||||
if (memRes is Map && memRes['error'] != null) {
|
||||
final err = memRes['error'];
|
||||
throw Exception(
|
||||
err is Map
|
||||
? (err['message'] ?? err.toString())
|
||||
: err.toString(),
|
||||
);
|
||||
}
|
||||
final memErr = extractSupabaseError(memRes);
|
||||
if (memErr != null) throw Exception(memErr);
|
||||
|
||||
// verify members persisted (handle Map or List response)
|
||||
final dynamic checkRes = await client
|
||||
|
|
@ -471,18 +458,12 @@ class _TeamsScreenState extends ConsumerState<TeamsScreen> {
|
|||
|
||||
// normalize inserted row to extract id reliably across client response shapes
|
||||
dynamic insertedRow;
|
||||
final insertResValue = insertRes;
|
||||
final dynamic insertResValue = insertRes;
|
||||
if (insertResValue is List && insertResValue.isNotEmpty) {
|
||||
insertedRow = (insertResValue as List).first;
|
||||
insertedRow = insertResValue.first;
|
||||
} else {
|
||||
if (insertResValue['error'] != null) {
|
||||
final err = insertResValue['error'];
|
||||
throw Exception(
|
||||
err is Map
|
||||
? (err['message'] ?? err.toString())
|
||||
: err.toString(),
|
||||
);
|
||||
}
|
||||
final insertErr = extractSupabaseError(insertResValue);
|
||||
if (insertErr != null) throw Exception(insertErr);
|
||||
final dataField = insertResValue['data'];
|
||||
if (dataField is List && dataField.isNotEmpty) {
|
||||
insertedRow = dataField.first;
|
||||
|
|
@ -503,14 +484,8 @@ class _TeamsScreenState extends ConsumerState<TeamsScreen> {
|
|||
.map((u) => {'team_id': teamId, 'user_id': u})
|
||||
.toList();
|
||||
final memRes = await client.from('team_members').insert(rows);
|
||||
if (memRes is Map && memRes['error'] != null) {
|
||||
final err = memRes['error'];
|
||||
throw Exception(
|
||||
err is Map
|
||||
? (err['message'] ?? err.toString())
|
||||
: err.toString(),
|
||||
);
|
||||
}
|
||||
final memErr2 = extractSupabaseError(memRes);
|
||||
if (memErr2 != null) throw Exception(memErr2);
|
||||
|
||||
// verify members persisted (handle Map or List response)
|
||||
final dynamic checkRes = await client
|
||||
|
|
@ -656,27 +631,21 @@ class _TeamsScreenState extends ConsumerState<TeamsScreen> {
|
|||
if (confirmed != true) return;
|
||||
|
||||
try {
|
||||
final delMembersRes = await Supabase.instance.client
|
||||
final delMembersRes = await ref
|
||||
.read(supabaseClientProvider)
|
||||
.from('team_members')
|
||||
.delete()
|
||||
.eq('team_id', teamId);
|
||||
if (delMembersRes is Map && delMembersRes['error'] != null) {
|
||||
final err = delMembersRes['error'];
|
||||
throw Exception(
|
||||
err is Map ? (err['message'] ?? err.toString()) : err.toString(),
|
||||
);
|
||||
}
|
||||
final delMembersErr = extractSupabaseError(delMembersRes);
|
||||
if (delMembersErr != null) throw Exception(delMembersErr);
|
||||
|
||||
final delTeamRes = await Supabase.instance.client
|
||||
final delTeamRes = await ref
|
||||
.read(supabaseClientProvider)
|
||||
.from('teams')
|
||||
.delete()
|
||||
.eq('id', teamId);
|
||||
if (delTeamRes is Map && delTeamRes['error'] != null) {
|
||||
final err = delTeamRes['error'];
|
||||
throw Exception(
|
||||
err is Map ? (err['message'] ?? err.toString()) : err.toString(),
|
||||
);
|
||||
}
|
||||
final delTeamErr = extractSupabaseError(delTeamRes);
|
||||
if (delTeamErr != null) throw Exception(delTeamErr);
|
||||
|
||||
ref.invalidate(teamsProvider);
|
||||
ref.invalidate(teamMembersProvider);
|
||||
|
|
|
|||
34
lib/utils/supabase_response.dart
Normal file
34
lib/utils/supabase_response.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/// Utilities for normalizing Supabase/PostgREST response shapes.
|
||||
///
|
||||
/// Supabase Dart responses can appear as Maps (legacy wrapper) or
|
||||
/// PostgrestResponse-like objects (have `.error`, `.status`, `.statusText`).
|
||||
/// Helpers here provide a single place to extract an error message safely so
|
||||
/// callers don't accidentally call `[]` on non-Map objects.
|
||||
|
||||
String? extractSupabaseError(dynamic res) {
|
||||
if (res == null) return null;
|
||||
if (res is Map) {
|
||||
final err = res['error'];
|
||||
if (err != null) {
|
||||
return err is Map ? (err['message'] ?? err.toString()) : err.toString();
|
||||
}
|
||||
if (res['status'] != null && res['status'] is int && res['status'] >= 400) {
|
||||
return res['message']?.toString() ?? 'Request failed with status ${res['status']}';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try PostgrestResponse-like fields via dynamic access (safe within try/catch).
|
||||
try {
|
||||
final err = (res as dynamic).error;
|
||||
if (err != null) return err is Map ? (err['message'] ?? err.toString()) : err.toString();
|
||||
} catch (_) {}
|
||||
try {
|
||||
final status = (res as dynamic).status;
|
||||
if (status != null && status >= 400) {
|
||||
final statusText = (res as dynamic).statusText;
|
||||
return statusText ?? 'Request failed with status $status';
|
||||
}
|
||||
} catch (_) {}
|
||||
return null;
|
||||
}
|
||||
34
test/supabase_response_test.dart
Normal file
34
test/supabase_response_test.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:tasq/utils/supabase_response.dart';
|
||||
|
||||
class _FakePostgrestLike {
|
||||
final dynamic error;
|
||||
final int? status;
|
||||
final String? statusText;
|
||||
_FakePostgrestLike({this.error, this.status, this.statusText});
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('extractSupabaseError returns null for null/ok responses', () {
|
||||
expect(extractSupabaseError(null), isNull);
|
||||
expect(extractSupabaseError({'data': []}), isNull);
|
||||
});
|
||||
|
||||
test('extractSupabaseError extracts message from Map-shaped error', () {
|
||||
final res = {
|
||||
'error': {'message': 'boom'},
|
||||
};
|
||||
expect(extractSupabaseError(res), 'boom');
|
||||
|
||||
final res2 = {'error': 'simple-error'};
|
||||
expect(extractSupabaseError(res2), 'simple-error');
|
||||
});
|
||||
|
||||
test('extractSupabaseError extracts from Postgrest-like response', () {
|
||||
final r1 = _FakePostgrestLike(error: {'message': 'bad'});
|
||||
expect(extractSupabaseError(r1), 'bad');
|
||||
|
||||
final r2 = _FakePostgrestLike(status: 500, statusText: 'server error');
|
||||
expect(extractSupabaseError(r2), 'server error');
|
||||
});
|
||||
}
|
||||
|
|
@ -136,4 +136,40 @@ void main() {
|
|||
expect(find.text('Team Name'), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('Edit Team dialog: Save button present and Cancel closes', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await tester.binding.setSurfaceSize(const Size(1024, 800));
|
||||
addTearDown(() async => await tester.binding.setSurfaceSize(null));
|
||||
|
||||
final team = Team(
|
||||
id: 'team-1',
|
||||
name: 'Support',
|
||||
leaderId: 'user-2',
|
||||
officeIds: ['office-1'],
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
...baseOverrides(),
|
||||
teamsProvider.overrideWith((ref) => Stream.value([team])),
|
||||
],
|
||||
child: const MaterialApp(home: Scaffold(body: TeamsScreen())),
|
||||
),
|
||||
);
|
||||
|
||||
// Tap edit and verify dialog shows Save button
|
||||
await tester.tap(find.byIcon(Icons.edit));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Save'), findsOneWidget);
|
||||
|
||||
// Cancel closes dialog
|
||||
await tester.tap(find.text('Cancel'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Save'), findsNothing);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user