enum GroupRole { manager, member } extension GroupRoleExtension on GroupRole { String get label { switch (this) { case GroupRole.manager: return 'Manager'; case GroupRole.member: return 'Member'; } } String get apiValue => name; } class GroupMemberModel { final String userId; final String groupId; final GroupRole role; /// Denormalized username for display — populated from local user cache final String? username; final DateTime? joinedAt; const GroupMemberModel({ required this.userId, required this.groupId, required this.role, this.username, this.joinedAt, }); factory GroupMemberModel.fromJson(Map json) { return GroupMemberModel( userId: json['user_id'] as String, groupId: json['group_id'] as String, role: GroupRole.values.firstWhere( (r) => r.name == (json['role'] as String), orElse: () => GroupRole.member, ), username: json['username'] as String?, joinedAt: json['joined_at'] != null ? DateTime.tryParse(json['joined_at'] as String) : null, ); } Map toJson() => { 'user_id': userId, 'group_id': groupId, 'role': role.apiValue, if (username != null) 'username': username, if (joinedAt != null) 'joined_at': joinedAt!.toIso8601String(), }; GroupMemberModel copyWith({ String? userId, String? groupId, GroupRole? role, String? username, DateTime? joinedAt, }) { return GroupMemberModel( userId: userId ?? this.userId, groupId: groupId ?? this.groupId, role: role ?? this.role, username: username ?? this.username, joinedAt: joinedAt ?? this.joinedAt, ); } }