TIF_E41222181/lib/feature_student/dashboard/presentation/dashboard_page.dart

466 lines
19 KiB
Dart

import 'package:absensi_qr/app_routes.dart';
import 'package:absensi_qr/constant/app_color.dart';
import 'package:absensi_qr/constant/app_font_style.dart';
import 'package:absensi_qr/constant/spacing_size.dart';
import 'package:absensi_qr/core/widgets/card_attendance_daily_status.dart';
import 'package:absensi_qr/core/widgets/shimmer_load_card.dart';
import 'package:absensi_qr/domain/enum/attendance_status_enum.dart';
import 'package:absensi_qr/feature_student/dashboard/presentation/dashboard_controller.dart';
import 'package:absensi_qr/feature_student/dashboard/presentation/widgets/card_attendace_history.dart';
import 'package:absensi_qr/feature_student/dashboard/presentation/widgets/subject_preview_card.dart';
import 'package:absensi_qr/feature_student/dashboard/presentation/widgets/upcoming_attendance_card.dart';
import 'package:absensi_qr/features/widgets/button_primary_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:lottie/lottie.dart';
class DashboardPage extends StatelessWidget {
const DashboardPage({super.key});
@override
Widget build(BuildContext context) {
DashboardController controller = Get.find<DashboardController>();
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: AppColor.colorHeader,
statusBarIconBrightness: Brightness.dark));
return SizedBox(
width: double.infinity,
child: RefreshIndicator(
onRefresh: () async {
controller.dateNow = DateTime.now();
// DateTime dateNow = DateTime(2026, 6, 25, 8, 0, 0); // testing only - Thursday
await Future.wait([
controller.getHistoryAttendance(),
controller.getAttendanceHistoryDaily(),
controller.getHistoryAttendanceByClass(),
]);
await controller.getUpcomingOrOngoingAttendance();
},
child: SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// header section
_sectionHeader(controller),
SpacingSize.spacingBaseHeight,
/// content section
// weekend animation section
Visibility(
visible: controller.isWeekend,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_weekendAnimationSection(),
SpacingSize.spacingLGHeight,
],
),
),
// normal content if not weekend
Visibility(
visible: !controller.isWeekend,
// visible: !controller.isWeekend,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// upcoming schedule section
Obx(() {
if (controller
.isLoadingUpcomingOrOngoingAttendance.value) {
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20),
child: ShimmerLoadCard(
shimmerItemCount: 1,
),
);
}
if (controller.upcomingOrOngoingAttendanceResult ==
null) {
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16),
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(
horizontal: 20, vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Text(
'Tidak ada absensi yang sedang berlangsung atau akan datang',
style: AppFontStyle.titleText,
),
),
);
}
final item =
controller.upcomingOrOngoingAttendanceResult!;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: UpcomingAttendanceCard(
timeStart: item.schedule.startTime,
timeEnd: item.schedule.endTime,
subjectName: item.schedule.subject?.name ??
'Nama Mata Pelajaran',
teacherName:
item.schedule.teacher?.name ?? 'Nama Guru',
attendanceStatus: item.attendanceStatus ??
AttendanceStatusEnum.none,
),
);
}),
SpacingSize.spacingLGHeight,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text('Mapel hari ini',
style: AppFontStyle.subTitleText),
),
// all attending schedule today
Obx(() {
if (controller.isLoadingAttendanceHistory.value) {
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20),
child: ShimmerLoadCard(
shimmerItemCount: 3,
),
);
}
if (controller.attendanceHistoryResult.isEmpty) {
return Padding(
padding: EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
child: Text('Tidak ada riwayat absensi hari ini',
style: AppFontStyle.subTitleText),
);
}
// show list of attendance items
return Padding(
padding: const EdgeInsets.only(
left: 16,
right: 16,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: controller.attendanceHistoryResult
.map((item) {
final resolvedStatusAttendance =
item.attendance != null
? item.attendance!.status
: AttendanceStatusEnum.none;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SubjectPreviewCard(
subjectName:
item.schedule.subject?.name ??
'Nama Mata Pelajaran',
startedAt: item.schedule.startTime,
endedAt: item.schedule.endTime,
badgeInfo: resolvedStatusAttendance,
),
if (item !=
controller.attendanceHistoryResult.last)
SpacingSize.spacingSMHeight,
],
);
}).toList(),
),
);
}),
SpacingSize.spacingBaseHeight,
Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: ButtonPrimaryWidget(
borderRadius: 20,
title: "Lihat Semua Jadwal",
callback: () {
Get.toNamed(AppRoutes.schedule);
}),
),
SpacingSize.spacingLGHeight,
Obx(() {
if (controller.isLoadingAttendanceDaily.value) {
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20),
child: ShimmerLoadCard(
shimmerItemCount: 1,
),
);
}
return _attendanceDailiesSection(controller);
}),
SpacingSize.spacingLGHeight,
_attendanceHistoriesSection(controller),
],
)),
SpacingSize.spacingHugeHeight,
]),
),
),
);
}
// section header
Widget _sectionHeader(DashboardController controller) {
return Container(
width: double.infinity,
padding: const EdgeInsets.only(left: 20, right: 20, top: 4, bottom: 20),
decoration: BoxDecoration(
color: AppColor.colorHeader,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
),
child: Column(
children: [
Row(
children: [
GestureDetector(
onTap: () {
controller.navigationController.changePage(3);
},
child: Obx(() => CircleAvatar(
radius: 16,
backgroundColor: AppColor.softColorPrimary,
child: ClipOval(
child: Image.network(
controller.profileImageURL.value,
width: 32,
height: 32,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
final String initials =
controller.name.value.isNotEmpty
? controller.name.value
.trim()
.split(' ')
.map((e) => e[0])
.take(2)
.join()
: '?';
return Text(
initials,
style: TextStyle(
color: AppColor.primaryColor,
fontSize: 10,
fontWeight: FontWeight.bold,
),
);
},
),
),
)),
),
SpacingSize.spacingSMWidth,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Text('Halo, ${controller.firstName.value}!',
style: AppFontStyle.titleText)),
Text(controller.dateNowFormatted,
style: AppFontStyle.subTitleText
.copyWith(fontWeight: FontWeight.normal)),
],
),
),
GestureDetector(
onTap: () {
Get.toNamed(AppRoutes.notificationStudent);
},
child: Icon(
Icons.notifications,
color: AppColor.primaryColor,
size: 30,
),
),
],
),
SpacingSize.spacingBaseHeight,
// location
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: () => controller.getLocation(),
child: const Icon(Icons.location_on,
size: 16, color: AppColor.primaryColor),
),
SpacingSize.spacingSMWidth,
Expanded(
child: Obx(() => Text(
controller.placemarkVillage != 'No Data'
? '${controller.placemarkStreet}, ${controller.placemarkVillage}'
: '-',
style: AppFontStyle.blueInfoText,
maxLines: 2,
overflow: TextOverflow.ellipsis))),
],
),
)
],
),
);
}
Widget _attendanceDailiesSection(DashboardController controller) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text('Absensi Harian',
style: AppFontStyle.primaryText
.copyWith(fontWeight: FontWeight.bold)),
SpacingSize.spacingSMHeight,
CardAttendanceDailyStatus(
attendance: controller.attendanceDailyResult.value,
),
],
),
);
}
Widget _attendanceHistoriesSection(DashboardController controller) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: Text('Riwayat Absensi Hari Ini',
style: AppFontStyle.primaryText
.copyWith(fontWeight: FontWeight.bold)),
),
SpacingSize.spacingXSWidth,
GestureDetector(
onTap: () => controller.getHistoryAttendanceByClass(),
child: Icon(Icons.replay,
size: 20, color: AppColor.primaryColor)),
SpacingSize.spacingXSWidth,
],
),
SpacingSize.spacingSMHeight,
Obx(() {
if (controller.isLoadingAttendanceByClassHistory.value) {
return ShimmerLoadCard(
shimmerItemCount: 3,
);
}
if (controller.attendanceByClassHistoryResult.isEmpty) {
return Text('Tidak ada riwayat absensi hari ini',
style: AppFontStyle.subTitleText);
}
return Column(
mainAxisSize: MainAxisSize.min,
children: controller.attendanceByClassHistoryResult.map((item) {
final allowedAttendanceFilter = item.attendances
.where((attendanceItem) =>
attendanceItem.attendanceStatus ==
AttendanceStatusEnum.valid ||
attendanceItem.attendanceStatus ==
AttendanceStatusEnum.invalid ||
attendanceItem.attendanceStatus ==
AttendanceStatusEnum.permission ||
attendanceItem.attendanceStatus ==
AttendanceStatusEnum.sick ||
attendanceItem.attendanceStatus ==
AttendanceStatusEnum.dispensation)
.toList();
return Column(
mainAxisSize: MainAxisSize.min,
children: [
CardAttendaceHistory(
subjectTitle:
item.schedule.subject?.name ?? 'Nama Mata Pelajaran',
classTitle: item.schedule.classData?.name ?? 'Nama Kelas',
attendanceRecords: allowedAttendanceFilter,
),
if (item != controller.attendanceByClassHistoryResult.last)
SpacingSize.spacingSMHeight,
],
);
}).toList(),
);
}),
SpacingSize.spacingMDHeight,
],
),
);
}
// section weekend animation
Widget _weekendAnimationSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
SpacingSize.spacingLGHeight,
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20),
child: LottieBuilder.asset(
'assets/animations/weekend.json',
fit: BoxFit.contain,
),
),
SpacingSize.spacingBaseHeight,
Text('Yay! Hari ini libur akhir pekan 🎉',
style:
AppFontStyle.primaryText.copyWith(fontWeight: FontWeight.bold)),
SpacingSize.spacingBaseHeight,
// button lihat semua jadwal
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: ButtonPrimaryWidget(
borderRadius: 20,
title: "Lihat Semua Jadwal",
callback: () {
Get.toNamed(AppRoutes.schedule);
}),
)
],
);
}
}