MIF_E31230910_MP-HRIS-MOBILE/lib/screens/schedule/schedule_screen.dart

434 lines
17 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:intl/intl.dart';
import '../../core/theme.dart';
import '../../providers/calendar_provider.dart';
import '../../models/schedule_model.dart';
import '../../widgets/organisms/schedule_shimmer.dart';
class ScheduleScreen extends StatefulWidget {
const ScheduleScreen({super.key});
@override
State<ScheduleScreen> createState() => _ScheduleScreenState();
}
class _ScheduleScreenState extends State<ScheduleScreen> {
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
@override
void initState() {
super.initState();
_selectedDay = DateTime.now();
WidgetsBinding.instance.addPostFrameCallback((_) {
final now = DateTime.now();
context.read<CalendarProvider>().fetchMonthlySchedule(now.month, now.year);
});
}
Color _parseColor(String hexString) {
try {
final buffer = StringBuffer();
if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
buffer.write(hexString.replaceFirst('#', ''));
return Color(int.parse(buffer.toString(), radix: 16));
} catch (e) {
return AppTheme.textTertiary;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.bgLight,
appBar: AppBar(
title: Text("Jadwal Kerja", style: AppTheme.heading3),
backgroundColor: AppTheme.bgLight,
elevation: 0,
centerTitle: true,
automaticallyImplyLeading: false,
actions: [
IconButton(
icon: const Icon(Icons.refresh, color: AppTheme.textPrimary),
onPressed: () {
context.read<CalendarProvider>().fetchMonthlySchedule(_focusedDay.month, _focusedDay.year);
},
)
],
),
body: SafeArea(
child: Consumer<CalendarProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return const ScheduleShimmer();
}
if (provider.errorMessage != null) {
return Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.wifi_off, size: 50, color: AppTheme.statusRed.withValues(alpha: 0.6)),
const SizedBox(height: 12),
Text(
"Gagal memuat data",
style: AppTheme.heading3.copyWith(color: AppTheme.textSecondary),
),
const SizedBox(height: 8),
Text(
provider.errorMessage!,
textAlign: TextAlign.center,
style: AppTheme.bodySmall.copyWith(color: AppTheme.textTertiary),
),
const SizedBox(height: 20),
ElevatedButton.icon(
onPressed: () => provider.fetchMonthlySchedule(_focusedDay.month, _focusedDay.year),
icon: const Icon(Icons.refresh, size: 18),
label: const Text("Coba Lagi"),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.primaryBlue,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppTheme.radiusMd),
),
),
)
],
),
),
);
}
return RefreshIndicator(
onRefresh: () => provider.fetchMonthlySchedule(_focusedDay.month, _focusedDay.year),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
children: [
Container(
margin: const EdgeInsets.all(AppTheme.spacingMd),
decoration: BoxDecoration(
color: AppTheme.bgWhite,
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
boxShadow: AppTheme.shadowSm,
),
child: TableCalendar<ScheduleModel>(
firstDay: DateTime.utc(2024, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
focusedDay: _focusedDay,
calendarFormat: CalendarFormat.month,
headerStyle: HeaderStyle(
formatButtonVisible: false,
titleCentered: true,
titleTextStyle: AppTheme.labelLarge.copyWith(fontSize: 16),
leftChevronIcon: const Icon(Icons.chevron_left, color: AppTheme.primaryDark),
rightChevronIcon: const Icon(Icons.chevron_right, color: AppTheme.primaryDark),
),
calendarStyle: CalendarStyle(
selectedDecoration: const BoxDecoration(color: AppTheme.primaryDark, shape: BoxShape.circle),
todayDecoration: BoxDecoration(
border: Border.all(color: AppTheme.primaryDark, width: 2),
shape: BoxShape.circle,
),
todayTextStyle: AppTheme.bodyMedium.copyWith(color: AppTheme.primaryDark, fontWeight: FontWeight.bold),
weekendTextStyle: AppTheme.bodyMedium.copyWith(color: AppTheme.statusRed.withValues(alpha: 0.6)),
outsideDaysVisible: false,
markerDecoration: const BoxDecoration(color: AppTheme.primaryDark, shape: BoxShape.circle),
),
daysOfWeekStyle: DaysOfWeekStyle(
weekdayStyle: AppTheme.bodySmall.copyWith(fontWeight: FontWeight.w600, color: AppTheme.textSecondary),
weekendStyle: AppTheme.bodySmall.copyWith(fontWeight: FontWeight.w600, color: AppTheme.statusRed.withValues(alpha: 0.6)),
),
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
onPageChanged: (focusedDay) {
_focusedDay = focusedDay;
provider.fetchMonthlySchedule(focusedDay.month, focusedDay.year);
},
eventLoader: (day) {
final dateKey = DateFormat('yyyy-MM-dd').format(day);
return provider.schedules.where((s) => s.tanggal == dateKey).toList();
},
calendarBuilders: CalendarBuilders(
markerBuilder: (context, date, events) {
if (events.isNotEmpty) {
final schedule = events.first;
return Positioned(
bottom: 6,
child: Container(
width: 8.0,
height: 8.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _parseColor(schedule.warnaKalender),
),
),
);
}
return null;
},
defaultBuilder: (context, day, focusedDay) {
final dateKey = DateFormat('yyyy-MM-dd').format(day);
final holidays = provider.schedules.where((s) => s.tanggal == dateKey && s.isHariLibur);
if (holidays.isNotEmpty) {
return Center(
child: Text(
'${day.day}',
style: AppTheme.bodyMedium.copyWith(color: AppTheme.statusRed, fontWeight: FontWeight.bold),
),
);
}
return null;
},
),
),
),
const SizedBox(height: AppTheme.spacingSm),
_buildDetailSection(provider),
const SizedBox(height: 24),
],
),
),
);
},
),
),
);
}
Widget _buildDetailSection(CalendarProvider provider) {
if (_selectedDay == null) return const SizedBox();
final dateKey = DateFormat('yyyy-MM-dd').format(_selectedDay!);
ScheduleModel? selectedSchedule;
try {
final found = provider.schedules.where((s) => s.tanggal == dateKey);
if (found.isNotEmpty) selectedSchedule = found.first;
} catch (e) {
selectedSchedule = null;
}
if (selectedSchedule != null && selectedSchedule.isHariLibur) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppTheme.bgWhite,
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
boxShadow: [
BoxShadow(
color: AppTheme.statusRed.withValues(alpha: 0.1),
blurRadius: 12,
spreadRadius: 2,
offset: const Offset(0, 4),
),
],
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
DateFormat('EEEE, d MMM yyyy', 'id_ID').format(_selectedDay!),
style: AppTheme.labelLarge.copyWith(fontSize: 15),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: AppTheme.statusRed.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(AppTheme.radiusFull),
),
child: Text(
'Hari Libur',
style: AppTheme.labelMedium.copyWith(color: AppTheme.statusRed, fontWeight: FontWeight.bold, fontSize: 11),
),
),
],
),
Divider(height: 24, color: AppTheme.bgCard),
Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
decoration: BoxDecoration(
color: AppTheme.statusRed.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(AppTheme.radiusMd),
),
child: Row(
children: [
Icon(Icons.celebration, size: 32, color: AppTheme.statusRed.withValues(alpha: 0.6)),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
selectedSchedule.keteranganLibur ?? 'Hari Libur',
style: AppTheme.labelLarge.copyWith(color: AppTheme.statusRed),
),
const SizedBox(height: 4),
Text(
'Tidak ada jadwal kerja pada hari ini',
style: AppTheme.bodySmall.copyWith(color: AppTheme.textTertiary),
),
],
),
),
],
),
),
],
),
),
);
}
if (selectedSchedule == null) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.calendar_today_outlined, size: 40, color: AppTheme.textTertiary.withValues(alpha: 0.4)),
const SizedBox(height: 10),
Text("Tidak ada informasi jadwal", style: AppTheme.bodyMedium.copyWith(color: AppTheme.textTertiary)),
],
),
);
}
bool isCustom = selectedSchedule.statusPoin != null;
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppTheme.bgWhite,
borderRadius: BorderRadius.circular(AppTheme.radiusLg),
boxShadow: isCustom
? [
BoxShadow(
color: AppTheme.statusOrange.withValues(alpha: 0.1),
blurRadius: 12,
spreadRadius: 2,
offset: const Offset(0, 4),
),
]
: AppTheme.shadowSm,
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
DateFormat('EEEE, d MMM yyyy', 'id_ID').format(_selectedDay!),
style: AppTheme.labelLarge.copyWith(fontSize: 15),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
if (isCustom)
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: AppTheme.statusOrange.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(AppTheme.radiusFull),
),
child: Text(
selectedSchedule.statusPoin!,
style: AppTheme.labelMedium.copyWith(color: AppTheme.statusOrange, fontWeight: FontWeight.bold, fontSize: 11),
),
),
],
),
Divider(height: 24, color: AppTheme.bgCard),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildTimeInfo("Jam Masuk", selectedSchedule.jamMasuk, isCustom),
Container(width: 1, height: 40, color: AppTheme.bgCard),
_buildTimeInfo("Jam Pulang", selectedSchedule.jamPulang, isCustom),
],
),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: BoxDecoration(
color: AppTheme.bgLight,
borderRadius: BorderRadius.circular(AppTheme.radiusMd),
),
child: Row(
children: [
Icon(Icons.work_history_outlined, size: 20, color: AppTheme.textSecondary),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Shift: ${selectedSchedule.shiftNama}",
style: AppTheme.labelLarge.copyWith(fontSize: 13),
overflow: TextOverflow.ellipsis),
Text("Status Absen: ${selectedSchedule.statusPresensi ?? 'Belum Absen'}",
style: AppTheme.bodySmall.copyWith(color: AppTheme.textTertiary),
overflow: TextOverflow.ellipsis),
],
),
),
],
),
)
],
),
),
);
}
Widget _buildTimeInfo(String label, String time, bool highlight) {
bool isModified = highlight && time.contains(":");
return Column(
children: [
Text(label, style: AppTheme.bodySmall.copyWith(color: AppTheme.textTertiary)),
const SizedBox(height: 6),
Text(
time,
style: AppTheme.heading2.copyWith(
fontSize: 22,
color: isModified ? AppTheme.statusOrange : AppTheme.textPrimary,
),
),
],
);
}
}