import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import './todo_api.dart'; import './todo_model.dart'; import './todo_detail.dart'; import './todo_form.dart'; class TodoManager extends StatefulWidget { final String currentUserId; const TodoManager({this.currentUserId = 'admin', super.key}); @override State createState() => _TodoManagerState(); } class _TodoManagerState extends State { late TodoApiService _apiService; late Future> _todosFuture; // UI 狀態控制 int _selectedDateIndex = 3; // 預設選中 index 3 (即「今天」) String _selectedFilter = 'All'; // All, To do, In Progress, Done late List _dynamicDates; // 儲存動態產生的日期 final Color primaryPurple = const Color(0xFF6542D0); final Color bgLight = const Color(0xFFF8F9FA); @override void initState() { super.initState(); _apiService = TodoApiService(currentUserId: widget.currentUserId); _todosFuture = _apiService.fetchTodos(); _generateDates(); } // 產生前3天到後1天的日期區間 void _generateDates() { DateTime today = DateTime.now(); _dynamicDates = List.generate(5, (index) { return today.subtract(Duration(days: 3 - index)); }); } void _refreshTodos() { setState(() { _todosFuture = _apiService.fetchTodos(); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: bgLight, body: SafeArea( child: Column( children: [ _buildCustomHeader(), _buildDateSelector(), _buildFilterChips(), Expanded( child: _buildTodoListBody(), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const TodoForm())); }, backgroundColor: primaryPurple, elevation: 4, shape: const CircleBorder(), child: const Icon(Icons.add, color: Colors.white, size: 28), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, bottomNavigationBar: _buildBottomNav(), ); } // --- UI 元件區塊 --- Widget _buildCustomHeader() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black87), onPressed: () => Navigator.pop(context), ), const Text( "Today's Tasks", style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87), ), // 移除小鈴鐺圖示後,保留一個佔位符號以確保標題能完美置中 const SizedBox(width: 48), ], ), ); } Widget _buildDateSelector() { return SizedBox( height: 90, child: ListView.builder( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: _dynamicDates.length, itemBuilder: (context, index) { final date = _dynamicDates[index]; final isSelected = index == _selectedDateIndex; return GestureDetector( onTap: () => setState(() => _selectedDateIndex = index), child: AnimatedContainer( duration: const Duration(milliseconds: 300), margin: const EdgeInsets.symmetric(horizontal: 8), width: 65, decoration: BoxDecoration( color: isSelected ? primaryPurple : Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ if (!isSelected) BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 5)) ], ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(DateFormat('MMM').format(date), style: TextStyle(color: isSelected ? Colors.white70 : Colors.grey, fontSize: 12)), const SizedBox(height: 4), Text(DateFormat('dd').format(date), style: TextStyle(color: isSelected ? Colors.white : Colors.black, fontSize: 20, fontWeight: FontWeight.bold)), const SizedBox(height: 4), Text(DateFormat('EEE').format(date), style: TextStyle(color: isSelected ? Colors.white70 : Colors.grey, fontSize: 12)), ], ), ), ); }, ), ); } Widget _buildFilterChips() { final filters = ['All', 'To do', 'In Progress', 'Done']; return SizedBox( height: 60, child: ListView.builder( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), itemCount: filters.length, itemBuilder: (context, index) { final isSelected = _selectedFilter == filters[index]; return Padding( padding: const EdgeInsets.only(right: 12), child: ChoiceChip( label: Text(filters[index]), selected: isSelected, onSelected: (bool selected) { if (selected) setState(() => _selectedFilter = filters[index]); }, selectedColor: primaryPurple, backgroundColor: const Color(0xFFF0EFFF), labelStyle: TextStyle( color: isSelected ? Colors.white : primaryPurple, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: BorderSide.none), ), ); }, ), ); } Widget _buildTodoListBody() { return FutureBuilder>( future: _todosFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return Center(child: Text('載入失敗: ${snapshot.error}')); } else if (snapshot.hasData) { // 取得目前選取的時間 DateTime selectedDate = _dynamicDates[_selectedDateIndex]; List filteredTodos = snapshot.data!.where((todo) { // 1. 狀態過濾邏輯 bool statusMatch = true; if (_selectedFilter != 'All') { String status = (todo.status ?? '').toUpperCase(); if (_selectedFilter == 'Done') statusMatch = status == 'DONE'; else if (_selectedFilter == 'In Progress') statusMatch = status == 'WIP'; else if (_selectedFilter == 'To do') statusMatch = status != 'DONE' && status != 'WIP'; } // 2. 日期過濾邏輯 (比對 年、月、日) bool dateMatch = false; if (todo.endDate != null) { dateMatch = (todo.endDate!.year == selectedDate.year) && (todo.endDate!.month == selectedDate.month) && (todo.endDate!.day == selectedDate.day); } return statusMatch && dateMatch; }).toList(); if (filteredTodos.isEmpty) { return Center( child: Text('此日期沒有「$_selectedFilter」狀態的任務。', style: const TextStyle(color: Colors.grey, fontSize: 16)), ); } return RefreshIndicator( onRefresh: () async => _refreshTodos(), child: ListView.builder( padding: const EdgeInsets.fromLTRB(20, 10, 20, 80), itemCount: filteredTodos.length, itemBuilder: (context, index) => _buildTaskCard(filteredTodos[index]), ), ); } else { return const Center(child: Text('目前沒有待辦事項。工作很輕鬆!')); } }, ); } Widget _buildTaskCard(Todo item) { String displayStatus = 'To-do'; Color chipColor = Colors.blueGrey; Color chipBg = Colors.blueGrey.withOpacity(0.1); if (item.status?.toUpperCase() == 'DONE') { displayStatus = 'Done'; chipColor = primaryPurple; chipBg = primaryPurple.withOpacity(0.1); } else if (item.status?.toUpperCase() == 'WIP') { displayStatus = 'In Progress'; chipColor = Colors.orange; chipBg = Colors.orange.withOpacity(0.1); } return Container( margin: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.08), blurRadius: 15, offset: const Offset(0, 5))], ), child: InkWell( onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => TodoDetail(todo: item))), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(item.className, style: const TextStyle(color: Colors.grey, fontSize: 13, fontWeight: FontWeight.w500)), Icon(Icons.work_outline, color: primaryPurple.withOpacity(0.5), size: 18), ], ), const SizedBox(height: 8), Text(item.taskName, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87)), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ const Icon(Icons.access_time_filled, color: Colors.grey, size: 16), const SizedBox(width: 6), Text(item.formattedEndDate, style: const TextStyle(color: Colors.grey, fontSize: 13, fontWeight: FontWeight.w500)), ], ), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration(color: chipBg, borderRadius: BorderRadius.circular(20)), child: Text(displayStatus, style: TextStyle(color: chipColor, fontSize: 12, fontWeight: FontWeight.w700)), ) ], ) ], ), ), ); } // 移除了內部的圖示,但保留了 BottomAppBar 的挖空設計與高度 Widget _buildBottomNav() { return const BottomAppBar( shape: CircularNotchedRectangle(), notchMargin: 8.0, color: Color(0xFFF0EFFF), elevation: 0, child: SizedBox( height: 60, // 維持底部導覽列的高度,留給 FAB 空間 ), ); } }