TIF_NGANJUK_E41210753/lib/app/views/student/page_student_dashboard.dart

1255 lines
51 KiB
Dart

import 'dart:convert';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' as rootBundle;
import 'package:flutter/services.dart';
import 'package:forward_chaining_man_app/app/controllers/developer_controller.dart';
import 'package:forward_chaining_man_app/app/views/about/page_about.dart';
import 'package:forward_chaining_man_app/app/views/developer/page/page_developer_viewer.dart';
import 'package:forward_chaining_man_app/app/views/page_intro.dart';
import 'package:forward_chaining_man_app/app/views/page_login.dart';
import 'package:forward_chaining_man_app/app/views/page_profile.dart';
import 'package:forward_chaining_man_app/app/views/student/feature/quiz/view/page_select_major.dart';
import 'package:forward_chaining_man_app/app/views/student/feature/recomendation_screen/view/page_recmendation_detail_screen.dart';
import 'package:forward_chaining_man_app/app/views/student/feature/recomendation_screen/view/page_recomendation_screen_history.dart';
import 'package:forward_chaining_man_app/app/views/student/model/data_student.dart';
import 'package:get/get.dart';
import 'dart:math' as math;
import 'package:intl/intl.dart' as intl;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
import 'feature/quiz/view/page_select_economy.dart';
class DevDataViewerController extends GetxController {
final RxList<ProgramStudi> programStudiKerja = <ProgramStudi>[].obs;
final RxList<ProgramStudi> programStudiKuliah = <ProgramStudi>[].obs;
final RxString currentView =
'overview'.obs; // overview, kerja, kuliah, rules, ugm
final RxBool isLoading = true.obs;
final RxString loadingError = ''.obs;
final RxList<Map<String, dynamic>> rulesData = <Map<String, dynamic>>[].obs;
// Data for UGM tuition fees
final RxList<Map<String, dynamic>> biayaKuliahD4UGM =
<Map<String, dynamic>>[].obs;
final RxList<Map<String, dynamic>> biayaKuliahS1UGM =
<Map<String, dynamic>>[].obs;
@override
void onInit() {
super.onInit();
loadAllData();
}
/// Load all data for analysis
void loadAllData() async {
isLoading.value = true;
loadingError.value = '';
try {
// Load Kerja data
await loadProgramData(true, programStudiKerja);
// Load Kuliah data
await loadProgramData(false, programStudiKuliah);
// Load UGM tuition fee data
await loadUGMTuitionData();
// Generate sample rules for analysis
generateSampleRules();
isLoading.value = false;
} catch (e) {
loadingError.value = e.toString();
isLoading.value = false;
}
}
void setCurrentView(String view) {
currentView.value = view;
}
/// Memuat data ProgramStudi dari file JSON (Sains + Teknik) tergantung Kerja/Kuliah
Future<void> loadProgramData(
bool isKerja, RxList<ProgramStudi> target) async {
// Tentukan file sains
final sainsFile = isKerja
? 'assets/ipa_sains_kerja.json'
: 'assets/ipa_sains_kuliah.json';
// File teknik
final teknikFile = isKerja
? 'assets/ipa_teknik_kerja.json'
: 'assets/ipa_teknik_kuliah.json';
// Baca JSON sains
final sainsString = await rootBundle.rootBundle.loadString(sainsFile);
final sainsMap = json.decode(sainsString) as Map<String, dynamic>;
// Baca JSON teknik
final teknikString = await rootBundle.rootBundle.loadString(teknikFile);
final teknikMap = json.decode(teknikString) as Map<String, dynamic>;
// Ubah ke list ProgramStudi
final programs = <ProgramStudi>[];
// Parsing sains
for (var entry in sainsMap.entries) {
programs.add(ProgramStudi.fromJson(entry.value));
}
// Parsing teknik
for (var entry in teknikMap.entries) {
programs.add(ProgramStudi.fromJson(entry.value));
}
target.value = programs;
}
/// Load UGM tuition fee data
Future<void> loadUGMTuitionData() async {
try {
// Load D4 data
final d4String = await rootBundle.rootBundle
.loadString('assets/biaya_kuliah_d4_ugm.json');
final d4List = json.decode(d4String) as List<dynamic>;
biayaKuliahD4UGM.value = d4List.cast<Map<String, dynamic>>();
// Load S1 data
final s1String = await rootBundle.rootBundle
.loadString('assets/biaya_kuliah_s1_ugm.json');
final s1List = json.decode(s1String) as List<dynamic>;
biayaKuliahS1UGM.value = s1List.cast<Map<String, dynamic>>();
} catch (e) {
print('Error loading UGM data: $e');
// Continue even if UGM data fails to load
}
}
/// Generate sample rules untuk analisis
void generateSampleRules() {
final rules = <Map<String, dynamic>>[];
// Flatten pertanyaan dari programStudiKerja untuk contoh rules
int counter = 1;
for (var prog in programStudiKerja) {
for (var minatEntry in prog.minat.entries) {
final minatKey = minatEntry.key;
final minatVal = minatEntry.value;
for (var p in minatVal.pertanyaan) {
final bobot = extractBobot(p);
final cleaned = cleanPertanyaan(p);
final qId = 'Q$counter';
counter++;
rules.add({
'id': 'R$counter',
'type': 'Forward Chaining Rule',
'condition': 'IF $qId = Yes',
'action': 'THEN Score("${prog.name}|$minatKey") += $bobot',
'question': cleaned,
'weight': bobot,
'programName': prog.name,
'minatKey': minatKey,
});
}
}
}
rulesData.value = rules;
}
/// Get total question count
int getTotalQuestions() {
int count = 0;
// Count questions from kerja
for (var prog in programStudiKerja) {
for (var minat in prog.minat.values) {
count += minat.pertanyaan.length;
}
}
// Count questions from kuliah
for (var prog in programStudiKuliah) {
for (var minat in prog.minat.values) {
count += minat.pertanyaan.length;
}
}
return count;
}
/// Count total minat
int getTotalMinat() {
int kerjaMinat =
programStudiKerja.fold(0, (sum, prog) => sum + prog.minat.length);
int kuliahMinat =
programStudiKuliah.fold(0, (sum, prog) => sum + prog.minat.length);
return kerjaMinat + kuliahMinat;
}
/// Count total jurusan
int getTotalJurusan() {
Set<String> allJurusan = {};
// Collect unique jurusan from Kerja
for (var prog in programStudiKerja) {
for (var minat in prog.minat.values) {
allJurusan.addAll(minat.jurusanTerkait);
}
}
// Collect unique jurusan from Kuliah
for (var prog in programStudiKuliah) {
for (var minat in prog.minat.values) {
allJurusan.addAll(minat.jurusanTerkait);
}
}
return allJurusan.length;
}
/// Count total karir
int getTotalKarir() {
Set<String> allKarir = {};
// Collect unique karir from Kerja
for (var prog in programStudiKerja) {
for (var minat in prog.minat.values) {
allKarir.addAll(minat.karir);
}
}
// Collect unique karir from Kuliah
for (var prog in programStudiKuliah) {
for (var minat in prog.minat.values) {
allKarir.addAll(minat.karir);
}
}
return allKarir.length;
}
}
class PageStudentDashboard extends StatelessWidget {
const PageStudentDashboard({Key? key}) : super(key: key);
Future<DocumentSnapshot> _findStudentInAllSchools(String? userId) async {
if (userId == null) {
// Return an empty document that won't exist
return FirebaseFirestore.instance.collection('dummy').doc('dummy').get();
}
final schoolsSnapshot =
await FirebaseFirestore.instance.collection('schools').get();
for (var schoolDoc in schoolsSnapshot.docs) {
final studentDoc =
await schoolDoc.reference.collection('students').doc(userId).get();
if (studentDoc.exists) {
// Save the school ID for future use
final prefs = await SharedPreferences.getInstance();
await prefs.setString('school_id', schoolDoc.id);
return studentDoc;
}
}
// If student not found in any school, return an empty document
return FirebaseFirestore.instance.collection('dummy').doc('dummy').get();
}
@override
Widget build(BuildContext context) {
final User? currentUser = FirebaseAuth.instance.currentUser;
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.blue.shade800,
Colors.indigo.shade900,
],
),
),
// Use a ListView as the main container instead of Column + SingleChildScrollView
child: SafeArea(
bottom: false,
child: ListView(
padding:
const EdgeInsets.symmetric(horizontal: 24.0, vertical: 8.0),
children: [
// Top bar with buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Profile button
GestureDetector(
onTap: () {
Get.to(() => const ProfilePage());
},
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
border: Border.all(
color: Colors.indigo.shade100,
width: 1.5,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
currentUser?.photoURL != null
? CircleAvatar(
radius: 14,
backgroundImage:
NetworkImage(currentUser!.photoURL!),
)
: CircleAvatar(
radius: 14,
backgroundColor: Colors.indigo.shade100,
child: Icon(
Icons.person,
color: Colors.indigo.shade700,
size: 16,
),
),
const SizedBox(width: 6),
Text(
"Profil",
style: TextStyle(
color: Colors.indigo.shade700,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
// About button
GestureDetector(
onTap: () {
Get.to(() => AboutPage());
},
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
border: Border.all(
color: Colors.indigo.shade100,
width: 1.5,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.info_outline_rounded,
color: Colors.indigo.shade700,
size: 18,
),
const SizedBox(width: 6),
Text(
"Tentang",
style: TextStyle(
color: Colors.indigo.shade700,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
],
),
const SizedBox(height: 20),
// App Logo with Hero animation - centered
Center(
child: Hero(
tag: 'app_logo',
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(25),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 15,
offset: const Offset(0, 8),
),
],
),
child: const Center(
child: Icon(
Icons.psychology,
size: 60,
color: Colors.indigo,
),
),
),
),
),
const SizedBox(height: 20),
// App Title - centered
Center(
child: TweenAnimationBuilder(
tween: Tween<double>(begin: 0.8, end: 1),
duration: const Duration(milliseconds: 800),
curve: Curves.easeOutQuad,
builder: (context, double value, child) {
return Transform.scale(
scale: value,
child: child,
);
},
child: Column(
children: [
const Text(
'EduGuide',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 1.2,
),
),
const SizedBox(height: 4),
const Text(
'Sistem Rekomendasi Karir & Kuliah',
style: TextStyle(
fontSize: 15,
color: Colors.white70,
letterSpacing: 0.5,
),
),
],
),
),
),
const SizedBox(height: 24),
// User welcome section
FutureBuilder<String>(
// First, get the school ID from SharedPreferences
future: () async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('school_id') ?? '';
}(),
builder: (context, schoolSnapshot) {
if (schoolSnapshot.connectionState ==
ConnectionState.waiting) {
return const CircularProgressIndicator();
}
// Once we have the school ID (or not), proceed
String schoolId = schoolSnapshot.data ?? '';
return FutureBuilder<DocumentSnapshot>(
future: schoolId.isNotEmpty
// If we have a school ID, try to get the student directly
? FirebaseFirestore.instance
.collection('schools')
.doc(schoolId)
.collection('students')
.doc(currentUser?.uid)
.get()
// If no school ID, find the student in all schools
: _findStudentInAllSchools(currentUser?.uid),
builder: (context, snapshot) {
String userName = "Siswa";
String userClass = "";
if (snapshot.hasData && snapshot.data!.exists) {
final userData =
snapshot.data!.data() as Map<String, dynamic>;
userName = userData['name'] ?? "Siswa";
userClass = userData['class'] ?? "";
}
return Container(
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 20),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
borderRadius: BorderRadius.circular(15),
),
child: Row(
children: [
Icon(
Icons.waving_hand_rounded,
color: Colors.amber.shade300,
size: 24,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Hai, $userName!',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
if (userClass.isNotEmpty)
Text(
'Kelas $userClass',
style: const TextStyle(
fontSize: 14,
color: Colors.white70,
),
),
],
),
),
],
),
);
},
);
},
),
const SizedBox(height: 16),
// User Recommendation History
if (currentUser != null) ...[
// Recommendation History Section
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(15),
),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.history,
color: Colors.amber.shade200,
size: 20,
),
const SizedBox(width: 8),
const Text(
'Riwayat Rekomendasi',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const Spacer(),
TextButton(
onPressed: () {
Get.to(() => const RecommendationHistoryPage());
},
style: TextButton.styleFrom(
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 6),
backgroundColor: Colors.blue.withOpacity(0.2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: const Text(
'Lihat Semua',
style: TextStyle(fontSize: 12),
),
)
],
),
const SizedBox(height: 8),
SizedBox(
height: 170, // Fixed height for the history list
child: FutureBuilder<String>(
// First get the school ID
future: () async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('school_id') ?? '';
}(),
builder: (context, schoolSnapshot) {
if (schoolSnapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white70),
strokeWidth: 2,
),
);
}
if (schoolSnapshot.hasError ||
!schoolSnapshot.hasData ||
schoolSnapshot.data!.isEmpty) {
// If we can't get school ID, search in all schools
return _buildAllSchoolsStreamBuilder(
currentUser.uid);
}
final schoolId = schoolSnapshot.data!;
// Now we have the school ID, use it to query the subcollection
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('schools')
.doc(schoolId)
.collection('recommendation_history')
.where('userId', isEqualTo: currentUser.uid)
.orderBy('timestamp', descending: true)
.limit(5)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white70),
strokeWidth: 2,
),
);
}
if (snapshot.hasError) {
return Center(
child: Text(
'Error: ${snapshot.error}',
style: const TextStyle(
color: Colors.white70),
),
);
}
if (!snapshot.hasData ||
snapshot.data!.docs.isEmpty) {
return Container(
height: 170,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'Belum ada riwayat rekomendasi.\nMulai aplikasi untuk mendapatkan rekomendasi.',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white70,
fontSize: 13,
),
),
),
);
}
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
final doc = snapshot.data!.docs[index];
final data =
doc.data() as Map<String, dynamic>;
// Extract recommendation data
String questionMode =
data['questionMode'] ??
'Tidak diketahui';
final timestamp =
data['timestamp'] as Timestamp?;
final formattedDate = timestamp != null
? intl.DateFormat('dd/MM/yyyy HH:mm')
.format(timestamp.toDate())
: 'Tidak ada tanggal';
// Get top recommendation if available
String topRecommendation =
'Tidak ada rekomendasi';
if (data['recommendations'] != null &&
(data['recommendations'] as List)
.isNotEmpty) {
final recommendations =
data['recommendations'] as List;
if (recommendations.isNotEmpty) {
topRecommendation = recommendations[0]
['title'] ??
'Tidak ada judul';
}
}
return GestureDetector(
onTap: () {
// Navigate to recommendation detail with school ID
Get.to(() => RecommendationDetailPage(
data: data,
documentId: doc.id,
));
},
child: Container(
margin:
const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius:
BorderRadius.circular(12),
border: Border.all(
color:
Colors.white.withOpacity(0.1),
width: 1,
),
),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: questionMode
.contains('Karir')
? Colors.orange
.withOpacity(0.2)
: Colors.green
.withOpacity(0.2),
borderRadius:
BorderRadius.circular(8),
),
child: Center(
child: Icon(
questionMode.contains('Karir')
? Icons.work
: Icons.school,
color: questionMode
.contains('Karir')
? Colors.orange.shade300
: Colors.green.shade300,
size: 20,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
topRecommendation,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight:
FontWeight.bold,
),
maxLines: 1,
overflow:
TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'$questionMode$formattedDate',
style: TextStyle(
color: Colors.white
.withOpacity(0.7),
fontSize: 12,
),
),
],
),
),
Icon(
Icons.chevron_right,
color:
Colors.white.withOpacity(0.5),
size: 20,
),
],
),
),
);
},
);
},
);
},
),
),
],
),
),
const SizedBox(height: 16),
],
// Main Content Area - Now directly in ListView, no nested scrolling
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 12),
// Welcome message with visually distinct styling
Center(
child: Text(
'Selamat Datang!',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.indigo.shade800,
),
),
),
const SizedBox(height: 12),
// Subtitle with improved styling
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: const Text(
'Aplikasi ini akan membantumu menemukan program studi dan karir yang paling sesuai dengan minatmu.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
color: Colors.black54,
height: 1.4,
),
),
),
const SizedBox(height: 24),
// Visual indicator to show content continues - arrow indicator
Center(
child: Icon(
Icons.keyboard_double_arrow_down,
color: Colors.indigo.shade200,
size: 28,
),
),
const SizedBox(height: 16),
// User tips card with action indicator
Stack(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: Colors.blue.shade100,
width: 1,
),
),
child: Column(
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Icons.lightbulb_outline,
size: 20,
color: Colors.blue.shade800,
),
),
const SizedBox(width: 12),
Text(
'Tips Penggunaan',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue.shade800,
),
),
],
),
const SizedBox(height: 12),
const Text(
'Jawab pertanyaan dengan jujur untuk mendapatkan rekomendasi karir dan program studi yang paling sesuai dengan minat dan bakatmu.',
style: TextStyle(
fontSize: 13,
color: Colors.black54,
height: 1.4,
),
),
],
),
),
// Subtle indicator to show this is important
Positioned(
top: 8,
right: 8,
child: Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: Colors.blue.shade400,
shape: BoxShape.circle,
),
),
),
],
),
const SizedBox(height: 24),
// Action Buttons - with enhanced styling and visual cues
// Primary Button
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.indigo.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: ElevatedButton(
onPressed: () =>
Get.to(() => const MajorPreferencePage()),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.indigo.shade800,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 16),
minimumSize: const Size(double.infinity, 56),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.play_arrow_rounded, size: 24),
SizedBox(width: 12),
Text(
'Mulai Aplikasi',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
),
),
],
),
),
),
const SizedBox(height: 16),
const SizedBox(height: 16),
// Footer attribution
Center(
child: Text(
'v1.0.0',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade400,
),
),
),
const SizedBox(height: 8),
],
),
),
// Bottom spacing
const SizedBox(height: 24),
],
),
),
),
);
}
Widget _buildAllSchoolsStreamBuilder(String userId) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('schools').snapshots(),
builder: (context, schoolsSnapshot) {
if (schoolsSnapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white70),
strokeWidth: 2,
),
);
}
if (schoolsSnapshot.hasError ||
!schoolsSnapshot.hasData ||
schoolsSnapshot.data!.docs.isEmpty) {
return Container(
height: 170,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'Tidak dapat menemukan data sekolah',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white70,
fontSize: 13,
),
),
),
);
}
// Use FutureBuilder to find the right school and its recommendation history
return FutureBuilder<List<QueryDocumentSnapshot>>(
future: _findUserRecommendations(userId, schoolsSnapshot.data!.docs),
builder: (context, recommendationsSnapshot) {
if (recommendationsSnapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white70),
strokeWidth: 2,
),
);
}
if (recommendationsSnapshot.hasError ||
!recommendationsSnapshot.hasData ||
recommendationsSnapshot.data!.isEmpty) {
return Container(
height: 170,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'Belum ada riwayat rekomendasi.\nMulai aplikasi untuk mendapatkan rekomendasi.',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white70,
fontSize: 13,
),
),
),
);
}
List<QueryDocumentSnapshot> recommendations =
recommendationsSnapshot.data!;
// Sort recommendations by timestamp
recommendations.sort((a, b) {
final aTimestamp =
(a.data() as Map<String, dynamic>)['timestamp'] as Timestamp?;
final bTimestamp =
(b.data() as Map<String, dynamic>)['timestamp'] as Timestamp?;
if (aTimestamp == null) return 1;
if (bTimestamp == null) return -1;
return bTimestamp.compareTo(aTimestamp);
});
// Limit to 5 recommendations
if (recommendations.length > 5) {
recommendations = recommendations.sublist(0, 5);
}
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: recommendations.length,
itemBuilder: (context, index) {
final doc = recommendations[index];
final data = doc.data() as Map<String, dynamic>;
final schoolId = doc.reference.parent.parent!.id;
// Extract recommendation data
String questionMode = data['questionMode'] ?? 'Tidak diketahui';
final timestamp = data['timestamp'] as Timestamp?;
final formattedDate = timestamp != null
? intl.DateFormat('dd/MM/yyyy HH:mm')
.format(timestamp.toDate())
: 'Tidak ada tanggal';
// Get top recommendation if available
String topRecommendation = 'Tidak ada rekomendasi';
if (data['recommendations'] != null &&
(data['recommendations'] as List).isNotEmpty) {
final recommendations = data['recommendations'] as List;
if (recommendations.isNotEmpty) {
topRecommendation =
recommendations[0]['title'] ?? 'Tidak ada judul';
}
}
return GestureDetector(
onTap: () {
// Save the found school ID for future use
SharedPreferences.getInstance().then((prefs) {
prefs.setString('school_id', schoolId);
});
// Navigate to recommendation detail
Get.to(() => RecommendationDetailPage(
data: data,
documentId: doc.id,
));
},
child: Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.white.withOpacity(0.1),
width: 1,
),
),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: questionMode.contains('Karir')
? Colors.orange.withOpacity(0.2)
: Colors.green.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Icon(
questionMode.contains('Karir')
? Icons.work
: Icons.school,
color: questionMode.contains('Karir')
? Colors.orange.shade300
: Colors.green.shade300,
size: 20,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
topRecommendation,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'$questionMode$formattedDate',
style: TextStyle(
color: Colors.white.withOpacity(0.7),
fontSize: 12,
),
),
],
),
),
Icon(
Icons.chevron_right,
color: Colors.white.withOpacity(0.5),
size: 20,
),
],
),
),
);
},
);
},
);
},
);
}
// Helper function to find user recommendations across all schools
Future<List<QueryDocumentSnapshot>> _findUserRecommendations(
String userId, List<QueryDocumentSnapshot> schools) async {
List<QueryDocumentSnapshot> results = [];
for (var schoolDoc in schools) {
try {
QuerySnapshot recommendationsSnapshot = await schoolDoc.reference
.collection('recommendation_history')
.where('userId', isEqualTo: userId)
.orderBy('timestamp', descending: true)
.limit(5)
.get();
if (recommendationsSnapshot.docs.isNotEmpty) {
results.addAll(recommendationsSnapshot.docs);
// Save the school ID for future use if we found recommendations
if (recommendationsSnapshot.docs.isNotEmpty) {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('school_id', schoolDoc.id);
}
}
} catch (e) {
print('Error fetching recommendations from school ${schoolDoc.id}: $e');
}
}
return results;
}
}