fix: correct learning history data handling and model parsing

This commit is contained in:
Naresh Pratista 2024-10-23 15:44:33 +07:00
parent e1e9ad37da
commit 210c40812b
7 changed files with 139 additions and 76 deletions

View File

@ -320,10 +320,19 @@ class DioClient {
} }
} }
Future<Response> getLearningHistory(String sectionId, String token) async { Future<Response> getLearningHistory(
String sectionId,
String token, {
int page = 1,
int limit = 5,
}) async {
try { try {
final response = await _dio.get( final response = await _dio.get(
'/learningHistory/section/$sectionId', '/learningHistory/section/$sectionId',
queryParameters: {
'page': page,
'limit': limit,
},
options: Options( options: Options(
headers: { headers: {
'Authorization': 'Bearer $token', 'Authorization': 'Bearer $token',

View File

@ -8,24 +8,33 @@ class HistoryRepository {
HistoryRepository(this._dioClient); HistoryRepository(this._dioClient);
Future<List<LearningHistory>> getLearningHistory( Future<List<LearningHistory>> getLearningHistory(
String sectionId, String token) async { String sectionId,
String token,
) async {
try { try {
final response = await _dioClient.getLearningHistory(sectionId, token); final response = await _dioClient.getLearningHistory(
sectionId,
token,
page: 1,
limit: 1000,
);
if (response.statusCode == 200 && response.data != null) { if (response.statusCode == 200 && response.data != null) {
if (response.data['payload'] != null) { // Perbaikan disini: langsung mengakses ['payload']['history']
final List<dynamic> historyData = response.data['payload']; final List<dynamic> historyData = response.data['payload']['history'];
return historyData if (historyData.isEmpty) {
.map((json) => LearningHistory.fromJson(json)) return []; // Mengembalikan list kosong jika tidak ada data
.toList();
} else {
throw Exception('No history data available');
} }
return historyData
.map((json) => LearningHistory.fromJson(json))
.toList();
} else { } else {
throw Exception( throw Exception(
'Failed to load learning history: ${response.statusMessage}'); 'Failed to load learning history: ${response.statusMessage}');
} }
} on DioException catch (e) { } on DioException catch (e) {
if (e.response != null) { if (e.response?.statusCode == 404) {
return [];
} else if (e.response != null) {
throw Exception('Server error: ${e.response?.statusMessage}'); throw Exception('Server error: ${e.response?.statusMessage}');
} else { } else {
throw Exception('Network error: ${e.message}'); throw Exception('Network error: ${e.message}');

View File

@ -19,6 +19,7 @@ class AppColors {
static const Color secondaryColor = Color(0xFF5674ED); static const Color secondaryColor = Color(0xFF5674ED);
static const Color primaryColor = Color(0xFF34C3F9); static const Color primaryColor = Color(0xFF34C3F9);
static const Color yellowButtonColor = Color(0xFFFACC15); static const Color yellowButtonColor = Color(0xFFFACC15);
static const Color greenColor = Color(0xFF00BC65);
static LinearGradient get gradientTheme => const LinearGradient( static LinearGradient get gradientTheme => const LinearGradient(
colors: [secondaryColor, primaryColor], colors: [secondaryColor, primaryColor],

View File

@ -1,12 +1,13 @@
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
class LearningHistory { class LearningHistory {
final int score; final int? score;
final String currentLevel; final String currentLevel;
final String? nextLevel; final String? nextLevel;
final DateTime? studentFinish; final DateTime? studentFinish;
final String topicName; final String topicName;
final String sectionName; final String sectionName;
final bool isPass;
LearningHistory({ LearningHistory({
required this.score, required this.score,
@ -15,6 +16,7 @@ class LearningHistory {
required this.studentFinish, required this.studentFinish,
required this.topicName, required this.topicName,
required this.sectionName, required this.sectionName,
required this.isPass,
}); });
factory LearningHistory.fromJson(Map<String, dynamic> json) { factory LearningHistory.fromJson(Map<String, dynamic> json) {
@ -27,6 +29,7 @@ class LearningHistory {
: null, : null,
topicName: json['TOPIC_NAME'] ?? 'Unknown Topic', topicName: json['TOPIC_NAME'] ?? 'Unknown Topic',
sectionName: json['SECTION_NAME'] ?? 'Unknown Section', sectionName: json['SECTION_NAME'] ?? 'Unknown Section',
isPass: json['IS_PASS'] == 1,
); );
} }

View File

@ -27,7 +27,10 @@ class HistoryProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<void> fetchLearningHistory(String token) async { Future<void> fetchLearningHistory(
String token, {
bool refresh = false,
}) async {
if (_sectionProvider.sections.isEmpty) { if (_sectionProvider.sections.isEmpty) {
_error = 'No sections available'; _error = 'No sections available';
notifyListeners(); notifyListeners();
@ -40,8 +43,12 @@ class HistoryProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
try { try {
final history = await _repository.getLearningHistory(sectionId, token); final history = await _repository.getLearningHistory(
sectionId,
token,
);
_learningHistory = history; _learningHistory = history;
_error = null;
} catch (e) { } catch (e) {
_error = 'Error fetching learning history: ${e.toString()}'; _error = 'Error fetching learning history: ${e.toString()}';
} finally { } finally {
@ -50,12 +57,20 @@ class HistoryProvider with ChangeNotifier {
} }
} }
Color getColorForLevels(dynamic currentLevel, dynamic nextLevel) { Color getColorForLevels(
dynamic currentLevel,
dynamic nextLevel,
bool isPass,
) {
if (isPass) {
return AppColors.greenColor;
}
int? current = _parseLevel(currentLevel); int? current = _parseLevel(currentLevel);
int? next = _parseLevel(nextLevel); int? next = _parseLevel(nextLevel);
if (current == null || next == null) { if (current == null || next == null) {
return AppColors.blackColor; // Default color if parsing fails return AppColors.blackColor;
} }
if (current == next) { if (current == next) {

View File

@ -19,6 +19,17 @@ class HistoryScreen extends StatefulWidget {
} }
class _HistoryScreenState extends State<HistoryScreen> { class _HistoryScreenState extends State<HistoryScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final historyProvider =
Provider.of<HistoryProvider>(context, listen: false);
final userProvider = Provider.of<UserProvider>(context, listen: false);
historyProvider.fetchLearningHistory(userProvider.jwtToken!);
});
}
bool isNotFoundError(String error) { bool isNotFoundError(String error) {
return error.toLowerCase().contains('no learning history found') || return error.toLowerCase().contains('no learning history found') ||
error.toLowerCase().contains('not found'); error.toLowerCase().contains('not found');
@ -52,68 +63,12 @@ class _HistoryScreenState extends State<HistoryScreen> {
), ),
child: Column( child: Column(
children: [ children: [
Container( _buildHeader(),
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Your Exercise History!',
style: AppTextStyles.blueTextStyle.copyWith(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'Track your progress with a personalized overview of all your workouts.',
style: AppTextStyles.greyTextStyle.copyWith(
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
],
),
),
const SizedBox(height: 8), const SizedBox(height: 8),
const CustomTabBar(), const CustomTabBar(),
const SizedBox(height: 8), const SizedBox(height: 8),
Expanded( Expanded(
child: Consumer<HistoryProvider>( child: _buildContent(historyProvider),
builder: (context, historyProvider, child) {
if (historyProvider.isLoading) {
return const Center(
child: CircularProgressIndicator());
} else if (historyProvider.error != null) {
if (isNotFoundError(historyProvider.error!)) {
return _buildEmptyState(context);
} else {
return _buildErrorState(historyProvider.error!);
}
} else if (historyProvider.learningHistory.isEmpty) {
return _buildEmptyState(context);
} else {
return ListView.builder(
itemCount: historyProvider.learningHistory.length,
itemBuilder: (context, index) {
return Column(
children: [
ExerciseHistoryCard(
exercise:
historyProvider.learningHistory[index],
),
const SizedBox(height: 8.0),
],
);
},
);
}
},
),
), ),
], ],
), ),
@ -124,6 +79,72 @@ class _HistoryScreenState extends State<HistoryScreen> {
}); });
} }
Widget _buildContent(HistoryProvider historyProvider) {
if (historyProvider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (historyProvider.error != null) {
return isNotFoundError(historyProvider.error!)
? _buildEmptyState(context)
: _buildErrorState(historyProvider.error!);
}
if (historyProvider.learningHistory.isEmpty) {
return _buildEmptyState(context);
}
return RefreshIndicator(
onRefresh: () async {
final userProvider = Provider.of<UserProvider>(context, listen: false);
await historyProvider.fetchLearningHistory(userProvider.jwtToken!);
},
child: ListView.builder(
itemCount: historyProvider.learningHistory.length,
itemBuilder: (context, index) {
return Column(
children: [
ExerciseHistoryCard(
exercise: historyProvider.learningHistory[index],
),
const SizedBox(height: 8.0),
],
);
},
),
);
}
Widget _buildHeader() {
return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Your Exercise History!',
style: AppTextStyles.blueTextStyle.copyWith(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'Track your progress with a personalized overview of all your workouts.',
style: AppTextStyles.greyTextStyle.copyWith(
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
Widget _buildErrorState(String error) { Widget _buildErrorState(String error) {
return Center( return Center(
child: Column( child: Column(

View File

@ -17,7 +17,10 @@ class ExerciseHistoryCard extends StatelessWidget {
final historyProvider = final historyProvider =
Provider.of<HistoryProvider>(context, listen: false); Provider.of<HistoryProvider>(context, listen: false);
final color = historyProvider.getColorForLevels( final color = historyProvider.getColorForLevels(
exercise.currentLevel, exercise.nextLevel); exercise.currentLevel,
exercise.nextLevel,
exercise.isPass,
);
return Card( return Card(
color: AppColors.whiteColor, color: AppColors.whiteColor,
@ -54,7 +57,9 @@ class ExerciseHistoryCard extends StatelessWidget {
style: AppTextStyles.blackTextStyle.copyWith(), style: AppTextStyles.blackTextStyle.copyWith(),
), ),
TextSpan( TextSpan(
text: '${exercise.nextLevel}', text: exercise.isPass
? 'Topic Finished'
: '${exercise.nextLevel}',
style: AppTextStyles.blackTextStyle.copyWith( style: AppTextStyles.blackTextStyle.copyWith(
color: color, color: color,
), ),