Skip to content

Commit 5b94b29

Browse files
committed
✨ feat: mejoras de ux en las academias
2 parents 748ef9e + b02e3f3 commit 5b94b29

6 files changed

Lines changed: 176 additions & 10 deletions

File tree

lib/features/dashboard/viewmodels/bulk_upload_viewmodel.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class BulkUploadViewModel extends ChangeNotifier {
164164

165165
} catch (e) {
166166
_errorMessage = "Error: $e";
167-
print("Error BulkUpload: $e");
167+
debugPrint("Error BulkUpload: $e");
168168
} finally {
169169
_isLoading = false;
170170
notifyListeners();

lib/features/dashboard/views/academy_home_view.dart

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,137 @@ class AcademyHomeView extends StatelessWidget {
130130
),
131131
],
132132
),
133+
<<<<<<< HEAD
134+
=======
135+
const Divider(height: 1, indent: 16, endIndent: 16),
136+
Padding(
137+
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
138+
child: TextButton.icon(
139+
icon: const Icon(Icons.add_circle_outline, size: 18),
140+
label: const Text("Asignar Materia"),
141+
style: TextButton.styleFrom(foregroundColor: AppTheme.bluePrimary),
142+
onPressed: onAssign,
143+
),
144+
)
145+
]),
146+
);
147+
}
148+
}
149+
150+
class _AssignmentForm extends StatefulWidget {
151+
final UserModel student;
152+
const _AssignmentForm({required this.student});
153+
154+
@override
155+
State<_AssignmentForm> createState() => _AssignmentFormState();
156+
}
157+
158+
class _AssignmentFormState extends State<_AssignmentForm> {
159+
final _scheduleCtrl = TextEditingController();
160+
final _salonCtrl = TextEditingController();
161+
SubjectModel? _selectedSubject;
162+
ProfessorModel? _selectedProfessor;
163+
bool _isSaving = false;
164+
165+
@override
166+
void initState() {
167+
super.initState();
168+
// Auto-select the subject if there's only one available
169+
final vm = context.read<AcademyViewModel>();
170+
if (vm.availableSubjectsForStudent.length == 1) {
171+
_selectedSubject = vm.availableSubjectsForStudent.first;
172+
}
173+
}
174+
175+
@override
176+
void dispose() {
177+
_scheduleCtrl.dispose();
178+
_salonCtrl.dispose();
179+
super.dispose();
180+
}
181+
182+
void _submit() async {
183+
final vm = context.read<AcademyViewModel>();
184+
if (_selectedSubject == null || _selectedProfessor == null || _salonCtrl.text.isEmpty) {
185+
if(mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Todos los campos son obligatorios")));
186+
return;
187+
}
188+
189+
setState(() => _isSaving = true);
190+
final success = await vm.assignSubject(
191+
studentId: widget.student.id,
192+
subjectName: _selectedSubject!.name,
193+
professorName: _selectedProfessor!.name,
194+
schedule: _selectedProfessor!.schedule,
195+
salon: _salonCtrl.text,
196+
);
197+
198+
if (mounted && success){
199+
Navigator.pop(context);
200+
} else if(mounted) {
201+
setState(() => _isSaving = false);
202+
}
203+
}
204+
205+
@override
206+
Widget build(BuildContext context) {
207+
final vm = context.watch<AcademyViewModel>();
208+
final bottomPadding = MediaQuery.of(context).viewInsets.bottom;
209+
210+
return SingleChildScrollView(
211+
padding: EdgeInsets.fromLTRB(24, 24, 24, 24 + bottomPadding),
212+
child: Column(
213+
mainAxisSize: MainAxisSize.min,
214+
crossAxisAlignment: CrossAxisAlignment.start,
215+
children: [
216+
Text("Asignar Materia y Profesor", style: Theme.of(context).textTheme.titleLarge),
217+
Text("Alumno: ${widget.student.name}", style: const TextStyle(color: Colors.grey)),
218+
const SizedBox(height: 20),
219+
220+
// --- FIX: Use the filtered list of subjects ---
221+
DropdownButtonFormField<SubjectModel>(
222+
key: ValueKey(_selectedSubject),
223+
initialValue: _selectedSubject,
224+
decoration: const InputDecoration(labelText: "Materia", border: OutlineInputBorder()),
225+
items: vm.availableSubjectsForStudent.map((s) => DropdownMenuItem(value: s, child: Text(s.name))).toList(),
226+
onChanged: (val) => setState(() {
227+
_selectedSubject = val;
228+
_selectedProfessor = null;
229+
_scheduleCtrl.clear();
230+
}),
231+
),
232+
const SizedBox(height: 15),
233+
234+
DropdownButtonFormField<ProfessorModel>(
235+
key: ValueKey(_selectedProfessor),
236+
decoration: const InputDecoration(labelText: "Profesor", border: OutlineInputBorder()),
237+
items: _selectedSubject?.professors.map((p) => DropdownMenuItem(value: p, child: Text(p.name))).toList(),
238+
onChanged: (val) => setState(() {
239+
_selectedProfessor = val;
240+
_scheduleCtrl.text = val?.schedule ?? '';
241+
}),
242+
),
243+
const SizedBox(height: 15),
244+
245+
Row(children: [
246+
Expanded(child: TextField(controller: _scheduleCtrl, readOnly: true, decoration: const InputDecoration(labelText: "Horario", border: OutlineInputBorder()))),
247+
const SizedBox(width: 10),
248+
Expanded(child: TextField(controller: _salonCtrl, decoration: const InputDecoration(labelText: "Salón", hintText: "Ej. A-04", border: OutlineInputBorder()))),
249+
]),
250+
const SizedBox(height: 25),
251+
SizedBox(
252+
width: double.infinity,
253+
height: 50,
254+
child: _isSaving
255+
? const Center(child: CircularProgressIndicator())
256+
: ElevatedButton(
257+
style: ElevatedButton.styleFrom(backgroundColor: AppTheme.bluePrimary, foregroundColor: Colors.white),
258+
onPressed: _submit,
259+
child: const Text("Guardar Asignación"),
260+
),
261+
)
262+
],
263+
>>>>>>> b02e3f38a199391d13e8c793264fe648935ec0f9
133264
),
134265
);
135266
}

lib/features/dashboard/views/student_list_view.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class StudentListView extends StatelessWidget {
3030
return Card(
3131
margin: const EdgeInsets.only(bottom: 12),
3232
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
33+
<<<<<<< HEAD
3334
elevation: 1,
3435
child: ListTile(
3536
leading: CircleAvatar(
@@ -43,6 +44,40 @@ class StudentListView extends StatelessWidget {
4344
Navigator.push(
4445
context,
4546
MaterialPageRoute(builder: (context) => StudentDetailView(student: student)),
47+
=======
48+
clipBehavior: Clip.antiAlias,
49+
child: ExpansionTile(
50+
key: PageStorageKey(academy),
51+
leading: const Icon(Icons.school_outlined, color: AppTheme.blueDark, size: 28),
52+
title: Text(academy, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18, color: AppTheme.blueDark)),
53+
backgroundColor: AppTheme.blueSoft.withValues(alpha:0.2),
54+
children: subjects.entries.map((subjectEntry) {
55+
final subject = subjectEntry.key;
56+
final studentList = subjectEntry.value;
57+
58+
return ExpansionTile(
59+
key: PageStorageKey('$academy-$subject'),
60+
tilePadding: const EdgeInsets.only(left: 48, right: 24),
61+
leading: const Icon(Icons.menu_book_outlined, color: AppTheme.bluePrimary),
62+
title: Text(subject, style: TextStyle(color: Colors.grey.shade800, fontWeight: FontWeight.w600, fontSize: 16)),
63+
backgroundColor: AppTheme.baseLight.withValues(alpha:0.5),
64+
children: studentList.map((student) {
65+
// --- FIX: Remove toUpperCase() ---
66+
return ListTile(
67+
contentPadding: const EdgeInsets.only(left: 56, right: 16, top: 4, bottom: 4),
68+
leading: const Icon(Icons.person_outline, color: Colors.grey),
69+
title: Text(student.name),
70+
subtitle: Text('Boleta: ${student.boleta}'),
71+
trailing: const Icon(Icons.chevron_right, size: 18),
72+
onTap: () {
73+
Navigator.push(
74+
context,
75+
MaterialPageRoute(builder: (context) => StudentDetailView(student: student)),
76+
);
77+
},
78+
);
79+
}).toList(),
80+
>>>>>>> b02e3f38a199391d13e8c793264fe648935ec0f9
4681
);
4782
},
4883
),

lib/features/login/views/forgot_password_view.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ class ForgotPasswordView extends StatelessWidget {
200200
try {
201201
for (var userData in usersToCreate) {
202202
try {
203-
print("Procesando: ${userData['email']}...");
203+
debugPrint("Procesando: ${userData['email']}...");
204204

205205
// 2. Crear usuario en la instancia SECUNDARIA
206206
// Esto loguea al usuario en 'tempAuth', pero NO en la app principal.
@@ -223,15 +223,15 @@ class ForgotPasswordView extends StatelessWidget {
223223
'createdAt': FieldValue.serverTimestamp(),
224224
});
225225
createdCount++;
226-
print("--> ÉXITO: ${userData['email']}");
226+
debugPrint("--> ÉXITO: ${userData['email']}");
227227

228228
// Ya no necesitamos signOut() porque 'tempAuth' no afecta la UI
229229
// Pero lo hacemos por limpieza interna de la instancia temporal
230230
await tempAuth.signOut();
231231
}
232232

233233
} catch (e) {
234-
print("--> ERROR en ${userData['email']}: $e");
234+
debugPrint("--> ERROR en ${userData['email']}: $e");
235235
// Si el error es "email-already-in-use", podríamos intentar
236236
// actualizar solo el Firestore buscando el UID por email,
237237
// pero eso requiere Admin SDK. Aquí solo saltamos.

pubspec.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -402,10 +402,10 @@ packages:
402402
dependency: "direct dev"
403403
description:
404404
name: flutter_launcher_icons
405-
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
405+
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
406406
url: "https://pub.dev"
407407
source: hosted
408-
version: "0.13.1"
408+
version: "0.14.4"
409409
flutter_lints:
410410
dependency: "direct dev"
411411
description:
@@ -729,10 +729,10 @@ packages:
729729
dependency: "direct main"
730730
description:
731731
name: smooth_page_indicator
732-
sha256: b21ebb8bc39cf72d11c7cfd809162a48c3800668ced1c9da3aade13a32cf6c1c
732+
sha256: "4b497e9898d095de40d246db943371183fa7482492a88391cfa8415ef94d57ba"
733733
url: "https://pub.dev"
734734
source: hosted
735-
version: "1.2.1"
735+
version: "2.0.1"
736736
source_gen:
737737
dependency: transitive
738738
description:

pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ dependencies:
2020
provider: ^6.1.2
2121
intl: ^0.20.2
2222
url_launcher: ^6.3.0
23-
smooth_page_indicator: ^1.1.0
23+
smooth_page_indicator: ^2.0.1
2424

2525
collection: ^1.19.0
2626

@@ -36,7 +36,7 @@ dependencies:
3636
dev_dependencies:
3737
flutter_test:
3838
sdk: flutter
39-
flutter_launcher_icons: "^0.13.1"
39+
flutter_launcher_icons: ^0.14.4
4040
flutter_lints: ^6.0.0
4141
mockito: ^5.4.4
4242
build_runner: ^2.4.8

0 commit comments

Comments
 (0)