import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:tugas_akhir_supabase/utils/date_formatter.dart'; import 'package:table_calendar/table_calendar.dart'; import 'package:tugas_akhir_supabase/screens/calendar/add_daily_log_dialog.dart'; import 'package:intl/intl.dart'; class ScheduleDetailScreen extends StatefulWidget { final String scheduleId; final int initialTabIndex; final DateTime? initialSelectedDate; const ScheduleDetailScreen({ super.key, required this.scheduleId, this.initialTabIndex = 0, this.initialSelectedDate, }); @override _ScheduleDetailScreenState createState() => _ScheduleDetailScreenState(); } class _ScheduleDetailScreenState extends State with SingleTickerProviderStateMixin { bool _isLoading = true; Map? _schedule; final List> _activities = []; List> _dailyLogs = []; Map> _events = {}; // Calendar properties CalendarFormat _calendarFormat = CalendarFormat.month; DateTime _focusedDay = DateTime.now(); DateTime? _selectedDay; late TabController _tabController; @override void initState() { super.initState(); // Use initialSelectedDate if provided, otherwise use current date _focusedDay = widget.initialSelectedDate ?? DateTime.now(); _selectedDay = widget.initialSelectedDate ?? _focusedDay; _tabController = TabController( length: 2, vsync: this, initialIndex: widget.initialTabIndex, ); _fetchScheduleDetails(); _fetchDailyLogs(); } @override void dispose() { _tabController.dispose(); super.dispose(); } Future _fetchScheduleDetails() async { try { // Fetch schedule details using crop_schedules debugPrint('INFO: Fetching schedule detail for ID: ${widget.scheduleId}'); final scheduleResponse = await Supabase.instance.client .from('crop_schedules') .select('*, fields!crop_schedules_field_id_fkey(name)') .eq('id', widget.scheduleId) .single(); debugPrint('INFO: Schedule detail response: $scheduleResponse'); if (mounted) { setState(() { _schedule = scheduleResponse; _isLoading = false; }); } } catch (e) { debugPrint('Error fetching schedule details: $e'); if (mounted) { setState(() => _isLoading = false); } } } Future _fetchDailyLogs() async { try { debugPrint( 'INFO: Fetching daily logs for schedule: ${widget.scheduleId}', ); final response = await Supabase.instance.client .from('daily_logs') .select('*') .eq('schedule_id', widget.scheduleId) .order('date', ascending: true); debugPrint('INFO: Daily logs response type: ${response.runtimeType}'); debugPrint('INFO: Raw daily logs response: $response'); final logs = response is List ? response.map((item) => item).toList() : >[]; debugPrint('INFO: Found ${logs.length} daily logs'); final events = >{}; for (final log in logs) { final date = DateTime.parse(log['date']).toLocal(); final dateKey = DateTime(date.year, date.month, date.day); if (events[dateKey] != null) { events[dateKey]!.add(log); } else { events[dateKey] = [log]; } } if (mounted) { setState(() { _dailyLogs = logs; _events = events; }); } } catch (e) { debugPrint('ERROR: Error fetching daily logs: $e'); } } List _getEventsForDay(DateTime day) { final dateKey = DateTime(day.year, day.month, day.day); return _events[dateKey] ?? []; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: _isLoading || _schedule == null ? const Text('Detail Jadwal') : Text( _schedule!['crop_name'] ?? 'Tanaman', style: GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w600, ), ), backgroundColor: const Color(0xFF056839), foregroundColor: Colors.white, elevation: 0, actions: [ IconButton( icon: const Icon(Icons.refresh), tooltip: 'Refresh Data', onPressed: () { setState(() => _isLoading = true); _fetchScheduleDetails(); _fetchDailyLogs(); }, ), ], bottom: TabBar( controller: _tabController, labelColor: Colors.white, indicatorColor: Colors.white, indicatorWeight: 2.5, labelStyle: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w500, ), tabs: [Tab(text: 'Info Jadwal'), Tab(text: 'Catatan Harian')], ), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : _schedule == null ? _buildErrorState() : TabBarView( controller: _tabController, children: [_buildScheduleDetails(), _buildDailyLogCalendar()], ), floatingActionButton: !_isLoading && _schedule != null && _tabController.index == 1 ? FloatingActionButton( onPressed: () => _showAddDailyLogDialog(_selectedDay ?? DateTime.now()), backgroundColor: const Color(0xFF056839), elevation: 2, child: const Icon(Icons.add, color: Colors.white, size: 22), ) : null, ); } Widget _buildErrorState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), const SizedBox(height: 16), Text( 'Jadwal tidak ditemukan', style: GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.grey[600], ), ), const SizedBox(height: 24), ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF056839), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text( 'Kembali', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, ), ), ), ], ), ); } Widget _buildScheduleDetails() { final cropName = _schedule!['crop_name'] ?? 'Tanaman'; final fieldName = _schedule!['fields!crop_schedules_field_id_fkey']?['name'] ?? 'Lahan'; final startDate = DateTime.parse(_schedule!['start_date']); final endDate = DateTime.parse(_schedule!['end_date']); final status = _schedule!['status'] ?? 'active'; final notes = _schedule!['notes'] ?? ''; // Calculate progress final totalDuration = endDate.difference(startDate).inDays; final elapsedDuration = DateTime.now().difference(startDate).inDays; double progress = elapsedDuration / totalDuration; progress = progress.clamp(0.0, 1.0); return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeaderCard( cropName, fieldName, startDate, endDate, status, progress, ), const SizedBox(height: 20), if (notes.isNotEmpty) ...[ _buildNotesSection(notes), const SizedBox(height: 20), ], _buildActivitiesSection(), ], ), ); } Widget _buildHeaderCard( String cropName, String fieldName, DateTime startDate, DateTime endDate, String status, double progress, ) { Color statusColor; String statusText; switch (status.toLowerCase()) { case 'active': statusColor = Colors.green; statusText = 'Aktif'; break; case 'completed': statusColor = Colors.blue; statusText = 'Selesai'; break; case 'cancelled': statusColor = Colors.red; statusText = 'Dibatalkan'; break; default: statusColor = Colors.orange; statusText = 'Pending'; } return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF056839), Color(0xFF0E8C51)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: const Color(0xFF056839).withOpacity(0.2), blurRadius: 8, offset: const Offset(0, 3), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( cropName, style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white, ), overflow: TextOverflow.ellipsis, ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4, ), decoration: BoxDecoration( color: statusColor, borderRadius: BorderRadius.circular(12), ), child: Text( statusText, style: GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.w500, color: Colors.white, ), ), ), ], ), const SizedBox(height: 10), Text( 'Lahan: $fieldName', style: GoogleFonts.poppins( fontSize: 13, color: Colors.white.withOpacity(0.9), ), ), const SizedBox(height: 12), Text( 'Periode', style: GoogleFonts.poppins( fontSize: 11, color: Colors.white.withOpacity(0.8), ), ), const SizedBox(height: 2), Text( '${formatDate(startDate)} - ${formatDate(endDate)}', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, color: Colors.white, ), ), const SizedBox(height: 12), Text( 'Progress', style: GoogleFonts.poppins( fontSize: 11, color: Colors.white.withOpacity(0.8), ), ), const SizedBox(height: 6), ClipRRect( borderRadius: BorderRadius.circular(6), child: LinearProgressIndicator( value: progress, backgroundColor: Colors.white.withOpacity(0.3), valueColor: const AlwaysStoppedAnimation(Colors.white), minHeight: 6, ), ), const SizedBox(height: 6), Text( '${(progress * 100).toInt()}%', style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white, ), ), ], ), ); } Widget _buildDailyLogCalendar() { // Ambil tanggal jadwal final startDate = _schedule != null ? DateTime.parse(_schedule!['start_date']) : DateTime.now(); final endDate = _schedule != null ? DateTime.parse(_schedule!['end_date']) : DateTime.now().add(const Duration(days: 90)); // Set _focusedDay to be within the range if it's not already if (_focusedDay.isBefore(startDate) || _focusedDay.isAfter(endDate)) { _focusedDay = startDate; } // Also set _selectedDay to be within range if needed if (_selectedDay == null || _selectedDay!.isBefore(startDate) || _selectedDay!.isAfter(endDate)) { _selectedDay = startDate; } return Column( children: [ // Tampilkan periode tanggal Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 3, offset: const Offset(0, 1), ), ], ), child: Column( children: [ Text( 'Periode Tanam Aktif', style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.grey[600], ), ), const SizedBox(height: 2), Text( '${DateFormat('dd MMM yyyy').format(startDate)} - ${DateFormat('dd MMM yyyy').format(endDate)}', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, color: const Color(0xFF056839), ), textAlign: TextAlign.center, ), ], ), ), _buildCalendarHeader(), _buildCalendar(), const SizedBox(height: 8), Expanded( child: _getEventsForDay(_selectedDay ?? DateTime.now()).isEmpty ? _buildEmptyDailyLogState() : _buildDailyLogsList( _getEventsForDay(_selectedDay ?? DateTime.now()), ), ), ], ); } Widget _buildCalendarHeader() { return Container( padding: const EdgeInsets.fromLTRB(14, 10, 14, 10), decoration: const BoxDecoration(color: Color(0xFF056839)), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ IconButton( icon: const Icon( Icons.chevron_left, color: Colors.white, size: 20, ), padding: EdgeInsets.zero, constraints: const BoxConstraints(), onPressed: () { setState(() { _focusedDay = DateTime( _focusedDay.year, _focusedDay.month - 1, ); }); }, ), const SizedBox(width: 4), Text( DateFormat('MMMM yyyy').format(_focusedDay), style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white, ), ), const SizedBox(width: 4), IconButton( icon: const Icon( Icons.chevron_right, color: Colors.white, size: 20, ), padding: EdgeInsets.zero, constraints: const BoxConstraints(), onPressed: () { setState(() { _focusedDay = DateTime( _focusedDay.year, _focusedDay.month + 1, ); }); }, ), ], ), Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(14), ), child: Row( children: [ GestureDetector( onTap: () { setState(() { _calendarFormat = CalendarFormat.month; }); }, child: Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), decoration: BoxDecoration( color: _calendarFormat == CalendarFormat.month ? Colors.white : Colors.transparent, borderRadius: BorderRadius.circular(14), ), child: Text( '2 weeks', style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: _calendarFormat == CalendarFormat.month ? const Color(0xFF056839) : Colors.white, ), ), ), ), ], ), ), ], ), ); } Widget _buildCalendar() { // Get start and end date from schedule for limiting calendar interaction final startDate = _schedule != null ? DateTime.parse( _schedule!['start_date'], ).subtract(const Duration(days: 1)) : DateTime.now().subtract(const Duration(days: 30)); final endDate = _schedule != null ? DateTime.parse( _schedule!['end_date'], ).add(const Duration(days: 1)) : DateTime.now().add(const Duration(days: 90)); // If initialSelectedDate is provided and within range, use it as focused day DateTime effectiveFocusedDay = _focusedDay; if (_focusedDay.isAfter(startDate) && _focusedDay.isBefore(endDate)) { effectiveFocusedDay = _focusedDay; } else { effectiveFocusedDay = DateTime.parse(_schedule!['start_date']); } return Container( width: double.infinity, decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 6, offset: const Offset(0, 3), ), ], ), child: TableCalendar( firstDay: startDate, lastDay: endDate, focusedDay: effectiveFocusedDay, calendarFormat: _calendarFormat, eventLoader: _getEventsForDay, selectedDayPredicate: (day) { return isSameDay(_selectedDay, day); }, // Enable only days within the schedule period enabledDayPredicate: (day) { return day.isAfter(startDate) && day.isBefore(endDate); }, onDaySelected: (selectedDay, focusedDay) { // Only allow selection within the valid range if (selectedDay.isAfter(startDate) && selectedDay.isBefore(endDate)) { setState(() { _selectedDay = selectedDay; _focusedDay = focusedDay; }); } }, onFormatChanged: (format) { if (_calendarFormat != format) { setState(() { _calendarFormat = format; }); } }, onPageChanged: (focusedDay) { setState(() { _focusedDay = focusedDay; }); }, calendarStyle: CalendarStyle( markersMaxCount: 3, markerDecoration: const BoxDecoration( color: Color(0xFF056839), shape: BoxShape.circle, ), markerSize: 6, selectedDecoration: const BoxDecoration( color: Color(0xFF4364CD), shape: BoxShape.circle, ), todayDecoration: BoxDecoration( color: const Color(0xFF4364CD).withOpacity(0.5), shape: BoxShape.circle, ), weekendTextStyle: TextStyle(color: Colors.red[300], fontSize: 12), defaultTextStyle: const TextStyle(fontSize: 12), outsideTextStyle: TextStyle(color: Colors.grey[400], fontSize: 12), disabledTextStyle: TextStyle(color: Colors.grey[300], fontSize: 12), selectedTextStyle: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ), todayTextStyle: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ), cellMargin: const EdgeInsets.all(0), cellPadding: const EdgeInsets.all(0), // Highlight days in the schedule period outsideDaysVisible: false, ), headerVisible: false, daysOfWeekStyle: DaysOfWeekStyle( weekdayStyle: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.black87, ), weekendStyle: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.red[300], ), decoration: BoxDecoration(color: Colors.grey[50]), ), daysOfWeekHeight: 28, rowHeight: 38, availableGestures: AvailableGestures.all, sixWeekMonthsEnforced: true, calendarBuilders: CalendarBuilders( // Custom day builder to highlight days in the range defaultBuilder: (context, day, focusedDay) { // Check if the day is within our schedule period final isInRange = day.isAfter(startDate) && day.isBefore(endDate); if (!isInRange) { // Days outside our range return Container( margin: const EdgeInsets.all(4), alignment: Alignment.center, decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Text( '${day.day}', style: TextStyle(fontSize: 12, color: Colors.grey[400]), ), ); } // Default day display for days in range return null; // Use default builder for in-range days }, ), ), ); } Widget _buildDailyLogsList(List dailyLogs) { return ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), itemCount: dailyLogs.length, itemBuilder: (context, index) { final log = dailyLogs[index] as Map; final date = DateTime.parse(log['date']); final note = log['note'] ?? ''; final cost = log['cost'] ?? 0.0; final imageUrl = log['image_url']; return Card( margin: const EdgeInsets.only(bottom: 8), elevation: 1, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), child: Container( padding: const EdgeInsets.all(10), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Jika ada gambar, tampilkan di sebelah kiri if (imageUrl != null) ClipRRect( borderRadius: BorderRadius.circular(6), child: Image.network( imageUrl, height: 60, width: 60, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( height: 60, width: 60, color: Colors.grey[300], child: const Icon(Icons.error_outline, size: 20), ); }, ), ) else Container( height: 60, width: 60, decoration: BoxDecoration( color: Colors.green.withOpacity(0.1), borderRadius: BorderRadius.circular(6), ), child: Icon(Icons.eco, color: Colors.green, size: 28), ), const SizedBox(width: 10), // Konten catatan Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ note.isNotEmpty ? Text( note, style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w500, ), ) : Text( 'Aktivitas', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 4), Text( 'Biaya: Rp ${NumberFormat('#,###').format(cost)}', style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.green[700], ), ), ], ), ), // Tombol hapus SizedBox( height: 32, width: 32, child: IconButton( padding: EdgeInsets.zero, iconSize: 18, icon: const Icon(Icons.delete, color: Colors.red), onPressed: () => _confirmDeleteLog(log['id']), ), ), ], ), ), ); }, ); } Future _confirmDeleteLog(String logId) async { final confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: Text( 'Hapus Catatan', style: GoogleFonts.poppins(fontWeight: FontWeight.bold), ), content: Text( 'Apakah Anda yakin ingin menghapus catatan ini?', style: GoogleFonts.poppins(), ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: Text('Batal', style: GoogleFonts.poppins()), ), TextButton( onPressed: () => Navigator.pop(context, true), child: Text( 'Hapus', style: GoogleFonts.poppins(color: Colors.red), ), ), ], ), ); if (confirm == true) { try { await Supabase.instance.client .from('daily_logs') .delete() .eq('id', logId); // Refresh data setelah menghapus _fetchDailyLogs(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Catatan berhasil dihapus')), ); } } catch (e) { debugPrint('ERROR: Failed to delete daily log: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Gagal menghapus catatan'), backgroundColor: Colors.red, ), ); } } } } Widget _buildEmptyDailyLogState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.event_note, size: 32, color: Colors.grey[350]), const SizedBox(height: 8), Text( 'Belum ada catatan harian', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[600], ), ), const SizedBox(height: 4), Text( 'Tambahkan catatan harian untuk tanggal ini', textAlign: TextAlign.center, style: GoogleFonts.poppins(fontSize: 11, color: Colors.grey[500]), ), ], ), ); } void _showAddDailyLogDialog(DateTime selectedDate) async { // Get schedule date range final startDate = _schedule != null ? DateTime.parse( _schedule!['start_date'], ).subtract(const Duration(days: 1)) : DateTime.now().subtract(const Duration(days: 30)); final endDate = _schedule != null ? DateTime.parse( _schedule!['end_date'], ).add(const Duration(days: 1)) : DateTime.now().add(const Duration(days: 90)); // Check if selected date is within the schedule period if (!selectedDate.isAfter(startDate) || !selectedDate.isBefore(endDate)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Tanggal di luar periode jadwal tanam'), backgroundColor: Colors.red, ), ); return; } final result = await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, useSafeArea: true, enableDrag: true, isDismissible: true, builder: (context) => AddDailyLogDialog( scheduleId: widget.scheduleId, date: selectedDate, ), ); if (result == true) { // Refresh daily logs data _fetchDailyLogs(); } } Widget _buildNotesSection(String notes) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Catatan', style: GoogleFonts.poppins(fontSize: 16, fontWeight: FontWeight.w600), ), const SizedBox(height: 8), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Text( notes, style: GoogleFonts.poppins(fontSize: 13, color: Colors.grey[800]), ), ), ], ); } Widget _buildActivitiesSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Aktivitas', style: GoogleFonts.poppins(fontSize: 16, fontWeight: FontWeight.w600), ), const SizedBox(height: 10), _dailyLogs.isEmpty ? _buildEmptyActivitiesState() : Column( children: _dailyLogs.map((log) { final activityName = log['note'] ?? 'Aktivitas'; final date = DateTime.parse(log['date']); final cost = log['cost'] ?? 0; final imageUrl = log['image_url']; return Container( margin: const EdgeInsets.only(bottom: 10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Padding( padding: const EdgeInsets.all(10), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Display image if available if (imageUrl != null) ClipRRect( borderRadius: BorderRadius.circular(6), child: Image.network( imageUrl, height: 60, width: 60, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( height: 60, width: 60, color: Colors.grey[300], child: const Icon( Icons.error_outline, size: 24, ), ); }, ), ) else Container( height: 60, width: 60, decoration: BoxDecoration( color: Colors.green.withOpacity(0.1), borderRadius: BorderRadius.circular(6), ), child: const Icon( Icons.eco, color: Colors.green, size: 30, ), ), const SizedBox(width: 10), // Activity details Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( activityName, style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 4), Text( 'Tanggal: ${formatDate(date)}', style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey[600], ), ), const SizedBox(height: 2), Text( 'Biaya: Rp ${NumberFormat('#,###').format(cost)}', style: GoogleFonts.poppins( fontSize: 12, color: Colors.green[700], fontWeight: FontWeight.w500, ), ), ], ), ), ], ), ), ); }).toList(), ), ], ); } Widget _buildEmptyActivitiesState() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Center( child: Column( children: [ Icon(Icons.event_busy, size: 36, color: Colors.grey[400]), const SizedBox(height: 8), Text( 'Belum ada aktivitas', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.grey[600], ), ), const SizedBox(height: 4), Text( 'Tambahkan aktivitas untuk jadwal ini', style: GoogleFonts.poppins(fontSize: 12, color: Colors.grey[500]), textAlign: TextAlign.center, ), ], ), ), ); } }