224 lines
7.4 KiB
Dart
224 lines
7.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import '../../core/theme.dart';
|
|
import '../../models/presensi_model.dart';
|
|
import '../atoms/bouncy_tap.dart';
|
|
import '../molecules/presensi_card.dart';
|
|
|
|
class PresensiSection extends StatelessWidget {
|
|
final PresensiModel? presensiToday;
|
|
final VoidCallback onPresensi;
|
|
final bool isLoading;
|
|
|
|
const PresensiSection({
|
|
Key? key,
|
|
this.presensiToday,
|
|
required this.onPresensi,
|
|
this.isLoading = false,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final now = DateTime.now();
|
|
final dateStr = "${now.day} ${_monthName(now.month)} ${now.year}";
|
|
|
|
final shift = presensiToday?.shift ?? "-";
|
|
final jamMasuk = presensiToday?.jamMasuk;
|
|
final jamPulang = presensiToday?.jamPulang;
|
|
|
|
final statusMasuk = _getStatusMasuk(now);
|
|
final statusPulang = _getStatusPulang(now);
|
|
|
|
final sudahMasuk = presensiToday?.sudahAbsenMasuk ?? false;
|
|
final sudahPulang = presensiToday?.sudahAbsenPulang ?? false;
|
|
|
|
String buttonText;
|
|
bool buttonDisabled = false;
|
|
|
|
if (!sudahMasuk) {
|
|
buttonText = "Absen Masuk";
|
|
} else if (!sudahPulang) {
|
|
buttonText = "Absen Pulang";
|
|
} else {
|
|
buttonText = "Selesai ✓";
|
|
buttonDisabled = true;
|
|
}
|
|
|
|
return Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text("Presensi Hari ini", style: AppTheme.heading3),
|
|
const SizedBox(height: 4),
|
|
Text(dateStr, style: AppTheme.bodySmall, overflow: TextOverflow.ellipsis),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: AppTheme.bgCard,
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusFull),
|
|
),
|
|
child: Text(
|
|
shift,
|
|
style: AppTheme.labelMedium.copyWith(color: AppTheme.textPrimary),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: AppTheme.spacingMd),
|
|
|
|
if (presensiToday == null)
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 24),
|
|
decoration: BoxDecoration(
|
|
color: AppTheme.bgCard,
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusMd),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Icon(Icons.event_busy_outlined, size: 44, color: AppTheme.textTertiary.withOpacity(0.5)),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
"Tidak ada jadwal kerja hari ini",
|
|
style: AppTheme.bodyMedium.copyWith(color: AppTheme.textSecondary, fontWeight: FontWeight.w500),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
"Hubungi admin jika Anda merasa ini salah",
|
|
style: AppTheme.bodySmall.copyWith(color: AppTheme.textTertiary),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
)
|
|
else ...[
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: PresensiCard(
|
|
type: PresensiType.datang,
|
|
time: jamMasuk,
|
|
status: statusMasuk,
|
|
statusText: presensiToday?.statusMasuk,
|
|
),
|
|
),
|
|
const SizedBox(width: AppTheme.spacingMd),
|
|
Expanded(
|
|
child: PresensiCard(
|
|
type: PresensiType.pulang,
|
|
time: jamPulang,
|
|
status: statusPulang,
|
|
statusText: presensiToday?.statusPulang,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: AppTheme.spacingLg),
|
|
|
|
BouncyTap(
|
|
onPressed: buttonDisabled || isLoading ? null : () {
|
|
HapticFeedback.lightImpact();
|
|
onPresensi();
|
|
},
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 300),
|
|
width: double.infinity,
|
|
height: 50,
|
|
decoration: BoxDecoration(
|
|
color: buttonDisabled
|
|
? AppTheme.statusGreen.withOpacity(0.5)
|
|
: (sudahMasuk ? AppTheme.primaryOrange : AppTheme.primaryDark),
|
|
borderRadius: BorderRadius.circular(AppTheme.radiusFull),
|
|
boxShadow: buttonDisabled ? [] : AppTheme.glowPrimary,
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
if (isLoading)
|
|
const SizedBox(
|
|
width: 20, height: 20,
|
|
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2),
|
|
)
|
|
else ...[
|
|
Icon(sudahMasuk ? Icons.logout : Icons.fingerprint, color: Colors.white, size: 24),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
buttonText,
|
|
style: AppTheme.labelLarge.copyWith(color: Colors.white),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
|
|
PresensiStatus _getStatusMasuk(DateTime now) {
|
|
if (presensiToday == null) return PresensiStatus.belumWaktunya;
|
|
|
|
if (presensiToday!.sudahAbsenMasuk) {
|
|
final status = presensiToday!.statusMasuk?.toLowerCase() ?? '';
|
|
if (status.contains('terlambat')) {
|
|
return PresensiStatus.adaMasalah;
|
|
}
|
|
return PresensiStatus.sudahAbsen;
|
|
}
|
|
|
|
return _isNearTime(now, presensiToday!.jadwalJamMasuk)
|
|
? PresensiStatus.waktunya
|
|
: PresensiStatus.belumWaktunya;
|
|
}
|
|
|
|
PresensiStatus _getStatusPulang(DateTime now) {
|
|
if (presensiToday == null) return PresensiStatus.belumWaktunya;
|
|
|
|
if (!presensiToday!.sudahAbsenMasuk) return PresensiStatus.belumWaktunya;
|
|
|
|
if (presensiToday!.sudahAbsenPulang) {
|
|
final status = presensiToday!.statusPulang?.toLowerCase() ?? '';
|
|
if (status.contains('awal') || status.contains('cepat')) {
|
|
return PresensiStatus.adaMasalah;
|
|
}
|
|
return PresensiStatus.sudahAbsen;
|
|
}
|
|
|
|
return _isNearTime(now, presensiToday!.jadwalJamPulang)
|
|
? PresensiStatus.waktunya
|
|
: PresensiStatus.belumWaktunya;
|
|
}
|
|
|
|
bool _isNearTime(DateTime now, String? timeStr) {
|
|
if (timeStr == null) return false;
|
|
try {
|
|
final parts = timeStr.split(':');
|
|
final hour = int.parse(parts[0]);
|
|
final minute = int.parse(parts[1]);
|
|
final target = DateTime(now.year, now.month, now.day, hour, minute);
|
|
final diff = now.difference(target).inMinutes;
|
|
return diff >= -30;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
String _monthName(int month) {
|
|
const months = [
|
|
"Januari", "Februari", "Maret", "April", "Mei", "Juni",
|
|
"Juli", "Agustus", "September", "Oktober", "November", "Desember"
|
|
];
|
|
return months[month - 1];
|
|
}
|
|
}
|