fix: correct learning history data handling and model parsing
This commit is contained in:
parent
e1e9ad37da
commit
210c40812b
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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'];
|
||||||
|
if (historyData.isEmpty) {
|
||||||
|
return []; // Mengembalikan list kosong jika tidak ada data
|
||||||
|
}
|
||||||
return historyData
|
return historyData
|
||||||
.map((json) => LearningHistory.fromJson(json))
|
.map((json) => LearningHistory.fromJson(json))
|
||||||
.toList();
|
.toList();
|
||||||
} else {
|
|
||||||
throw Exception('No history data available');
|
|
||||||
}
|
|
||||||
} 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}');
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,7 +63,60 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
_buildHeader(),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const CustomTabBar(),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Expanded(
|
||||||
|
child: _buildContent(historyProvider),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
padding: const EdgeInsets.all(16.0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.whiteColor,
|
color: AppColors.whiteColor,
|
||||||
|
|
@ -78,50 +142,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
const CustomTabBar(),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Consumer<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),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildErrorState(String error) {
|
Widget _buildErrorState(String error) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user