diff --git a/backend/routes/todos.js b/backend/routes/todos.js index fe74127..b0e422e 100644 --- a/backend/routes/todos.js +++ b/backend/routes/todos.js @@ -23,7 +23,18 @@ router.get("/", async (req, res) => { router.get("/today", async (req, res) => { try { const { start, end } = getDayRange(); - const todos = await Todo.find({ dueDate: { $gte: start, $lte: end } }).sort({ + // Include: + // 1. Tasks due today + // 2. Overdue tasks (due before today) that are not completed + // 3. Tasks with no due date that are not completed + const todos = await Todo.find({ + $or: [ + { dueDate: { $gte: start, $lte: end } }, + { dueDate: { $lt: start }, completed: false }, + { dueDate: { $exists: false }, completed: false }, + { dueDate: null, completed: false } + ] + }).sort({ dueDate: 1, createdAt: -1, }); diff --git a/backend/routes/weather.js b/backend/routes/weather.js index f16e761..2e25174 100644 --- a/backend/routes/weather.js +++ b/backend/routes/weather.js @@ -45,25 +45,22 @@ router.get("/", async (req, res) => { const currentData = currentResponse.data; - // 3. Process Forecast for Today's Min/Max (Seoul Time: UTC+9) - // OpenWeatherMap returns timestamps in UTC. - // Seoul is UTC+9. - const SeoulOffset = 9 * 60 * 60 * 1000; - const nowKST = new Date(Date.now() + SeoulOffset); - const todayStr = nowKST.toISOString().split("T")[0]; // YYYY-MM-DD in KST + // 3. Process Forecast for Today's Min/Max (Rolling 24-hour window for stability) + // We use the first 8 segments (3h * 8 = 24h) to ensure we always have a full + // day-night cycle of temperatures, providing a stable daily range. + const forecastList = forecastResponse.data.list; + if (forecastList && forecastList.length > 0) { + const next24h = forecastList.slice(0, 8); - const todayItems = forecastResponse.data.list.filter((item) => { - const itemDateKST = new Date(item.dt * 1000 + SeoulOffset); - const itemDateStr = itemDateKST.toISOString().split("T")[0]; - return itemDateStr === todayStr; - }); + // Calculate min/max from forecast + let minTemp = Math.min(...next24h.map((item) => item.main.temp_min)); + let maxTemp = Math.max(...next24h.map((item) => item.main.temp_max)); - if (todayItems.length > 0) { - // Find min and max from the 3-hour segments - const minTemp = Math.min(...todayItems.map((item) => item.main.temp_min)); - const maxTemp = Math.max(...todayItems.map((item) => item.main.temp_max)); + // Also compare with current temperature to ensure the range covers current state + const currentTemp = currentData.main.temp; + minTemp = Math.min(minTemp, currentTemp); + maxTemp = Math.max(maxTemp, currentTemp); - // Update the response structure currentData.main.temp_min = minTemp; currentData.main.temp_max = maxTemp; } diff --git a/flutter_app/lib/screens/admin/admin_screen.dart b/flutter_app/lib/screens/admin/admin_screen.dart index 6f155d5..76a4771 100644 --- a/flutter_app/lib/screens/admin/admin_screen.dart +++ b/flutter_app/lib/screens/admin/admin_screen.dart @@ -1559,10 +1559,14 @@ class _TodoManagerTabState extends State { ), ), subtitle: todo.dueDate != null - ? Text( - todo.dueDate!.toIso8601String().split('T')[0], - style: - const TextStyle(fontSize: 12, color: Colors.grey), + ? Builder( + builder: (context) { + final dueDate = todo.dueDate!; + return Text( + dueDate.toIso8601String().split('T')[0], + style: const TextStyle(fontSize: 12, color: Colors.grey), + ); + }, ) : null, trailing: Row( diff --git a/flutter_app/lib/services/photo_service.dart b/flutter_app/lib/services/photo_service.dart index dfd45ad..de44a95 100644 --- a/flutter_app/lib/services/photo_service.dart +++ b/flutter_app/lib/services/photo_service.dart @@ -78,13 +78,13 @@ class PhotoService { } Future getGoogleAuthUrl() async { - final data = await _client.get("${ApiConfig.photos}/auth/url"); + final data = await _client.getMap("${ApiConfig.photos}/auth/url"); return data["url"] as String; } Future getGoogleStatus() async { try { - final data = await _client.get("${ApiConfig.photos}/status"); + final data = await _client.getMap("${ApiConfig.photos}/status"); return data["connected"] as bool? ?? false; } catch (e) { return false; @@ -92,6 +92,6 @@ class PhotoService { } Future disconnectGoogle() async { - await _client.get("${ApiConfig.photos}/disconnect"); + await _client.getMap("${ApiConfig.photos}/disconnect"); } } diff --git a/flutter_app/lib/services/todo_service.dart b/flutter_app/lib/services/todo_service.dart index 7c250df..6e663fa 100644 --- a/flutter_app/lib/services/todo_service.dart +++ b/flutter_app/lib/services/todo_service.dart @@ -20,14 +20,23 @@ class TodoService { Future> fetchTodayTodos() async { if (ApiConfig.useMockData) { - final today = DateTime.now(); - return MockDataStore.todos.where((todo) => todo.dueDate != null).where(( - todo, - ) { - final date = todo.dueDate!; - return date.year == today.year && - date.month == today.month && - date.day == today.day; + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final tomorrow = today.add(const Duration(days: 1)); + + return MockDataStore.todos.where((todo) { + if (todo.completed) { + // If completed, only show if it was due today + if (todo.dueDate == null) return false; + return todo.dueDate!.isAfter(today.subtract(const Duration(milliseconds: 1))) && + todo.dueDate!.isBefore(tomorrow); + } + + // If not completed: + // 1. No due date -> show + // 2. Due today or before -> show + if (todo.dueDate == null) return true; + return todo.dueDate!.isBefore(tomorrow); }).toList(); } final data = await _client.getList("${ApiConfig.todos}/today"); diff --git a/flutter_app/lib/widgets/schedule_list_widget.dart b/flutter_app/lib/widgets/schedule_list_widget.dart index f60da80..7bb5cdf 100644 --- a/flutter_app/lib/widgets/schedule_list_widget.dart +++ b/flutter_app/lib/widgets/schedule_list_widget.dart @@ -112,8 +112,8 @@ class _ScheduleListWidgetState extends State { final endOfWeek = startOfWeek.add(const Duration(days: 6, hours: 23, minutes: 59, seconds: 59)); final filteredSchedules = _schedules.where((item) { - // Overlap check: schedule starts before week ends AND schedule ends after week starts - return item.startDate.isBefore(endOfWeek) && item.endDate.isAfter(startOfWeek); + // Overlap check: schedule starts before week ends AND schedule ends after or on today + return item.startDate.isBefore(endOfWeek) && (item.endDate.isAtSameMomentAs(today) || item.endDate.isAfter(today)); }).toList(); if (filteredSchedules.isEmpty) { diff --git a/flutter_app/lib/widgets/todo_list_widget.dart b/flutter_app/lib/widgets/todo_list_widget.dart index 22e0b8d..4cc8abc 100644 --- a/flutter_app/lib/widgets/todo_list_widget.dart +++ b/flutter_app/lib/widgets/todo_list_widget.dart @@ -219,12 +219,7 @@ class _TodoListWidgetState extends State { decorationColor: Colors.white54, ), ), - subtitle: todo.dueDate != null && (todo.dueDate!.hour != 0 || todo.dueDate!.minute != 0) - ? Text( - DateFormat('HH:mm').format(todo.dueDate!), - style: const TextStyle(color: Colors.white54, fontSize: 12), - ) - : null, + subtitle: null, trailing: Checkbox( value: todo.completed, onChanged: (val) async {