diff --git a/app/lib/models/schedule.dart b/app/lib/models/schedule.dart index 0611c17..3f61262 100644 --- a/app/lib/models/schedule.dart +++ b/app/lib/models/schedule.dart @@ -116,52 +116,54 @@ class Schedule { final String title; final String date; final String? time; - final String? endDate; - final String? endTime; - final String? description; + final int? categoryId; final String? categoryName; final String? categoryColor; - final String? memberNames; - final String? sourceUrl; + final List members; final String? sourceName; + final String? sourceUrl; Schedule({ required this.id, required this.title, required this.date, this.time, - this.endDate, - this.endTime, - this.description, + this.categoryId, this.categoryName, this.categoryColor, - this.memberNames, - this.sourceUrl, + this.members = const [], this.sourceName, + this.sourceUrl, }); factory Schedule.fromJson(Map json) { + // category 중첩 객체 파싱 + final category = json['category'] as Map?; + + // members 배열 파싱 + final membersList = (json['members'] as List?) + ?.map((m) => m is String ? m : m.toString()) + .toList() ?? []; + + // source 중첩 객체 파싱 + final source = json['source'] as Map?; + return Schedule( - id: json['id'] as int, + id: json['id'] is String ? int.parse(json['id']) : json['id'] as int, title: json['title'] as String, date: json['date'] as String, time: json['time'] as String?, - endDate: json['end_date'] as String?, - endTime: json['end_time'] as String?, - description: json['description'] as String?, - categoryName: json['category_name'] as String?, - categoryColor: json['category_color'] as String?, - memberNames: json['member_names'] as String?, - sourceUrl: json['source_url'] as String?, - sourceName: json['source_name'] as String?, + categoryId: category?['id'] as int?, + categoryName: category?['name'] as String?, + categoryColor: category?['color'] as String?, + members: membersList, + sourceName: source?['name'] as String?, + sourceUrl: source?['url'] as String?, ); } /// 멤버 리스트 반환 - List get memberList { - if (memberNames == null || memberNames!.isEmpty) return []; - return memberNames!.split(',').map((n) => n.trim()).where((n) => n.isNotEmpty).toList(); - } + List get memberList => members; /// 시간 포맷 (HH:mm) String? get formattedTime { diff --git a/app/lib/services/schedules_service.dart b/app/lib/services/schedules_service.dart index 8639061..fe12b5e 100644 --- a/app/lib/services/schedules_service.dart +++ b/app/lib/services/schedules_service.dart @@ -17,8 +17,9 @@ Future> getSchedules(int year, int month) async { 'year': year.toString(), 'month': month.toString(), }); - final List data = response.data; - return data.map((json) => Schedule.fromJson(json)).toList(); + final Map data = response.data; + final List schedulesJson = data['schedules'] ?? []; + return schedulesJson.map((json) => Schedule.fromJson(json)).toList(); } /// 다가오는 일정 N개 조회 (오늘 이후) - 웹과 동일 @@ -28,8 +29,9 @@ Future> getUpcomingSchedules(int limit) async { 'startDate': todayStr, 'limit': limit.toString(), }); - final List data = response.data; - return data.map((json) => Schedule.fromJson(json)).toList(); + final Map data = response.data; + final List schedulesJson = data['schedules'] ?? []; + return schedulesJson.map((json) => Schedule.fromJson(json)).toList(); } /// 일정 검색 결과 diff --git a/app/lib/views/home/home_view.dart b/app/lib/views/home/home_view.dart index 3aa7918..b7fcf99 100644 --- a/app/lib/views/home/home_view.dart +++ b/app/lib/views/home/home_view.dart @@ -637,13 +637,11 @@ class _HomeViewState extends ConsumerState with TickerProviderStateMix maxLines: 2, overflow: TextOverflow.ellipsis, ), - // mt-2(8px) text-xs(12px) text-gray-400 + // mt-2(8px) text-xs(12px) text-gray-400 - 시간 + 카테고리 const SizedBox(height: 8), Row( children: [ - // gap-3(12px) 사이 if (schedule.formattedTime != null) ...[ - // gap-1(4px) _buildIcon('clock', 12, Colors.grey[400]!), const SizedBox(width: 4), Text( @@ -662,6 +660,20 @@ class _HomeViewState extends ConsumerState with TickerProviderStateMix ], ], ), + // 소스 (별도 줄) + if (schedule.sourceName != null && schedule.sourceName!.isNotEmpty) ...[ + const SizedBox(height: 6), + Row( + children: [ + _buildIcon('link', 12, Colors.grey[400]!), + const SizedBox(width: 4), + Text( + schedule.sourceName!, + style: TextStyle(fontSize: 12, color: Colors.grey[400]), + ), + ], + ), + ], // mt-2(8px) if (memberList.isNotEmpty) ...[ const SizedBox(height: 8), @@ -704,6 +716,7 @@ class _HomeViewState extends ConsumerState with TickerProviderStateMix const icons = { 'clock': '', 'tag': '', + 'link': '', 'chevron-right': '', }; diff --git a/frontend/src/components/mobile/schedule/ScheduleCard.jsx b/frontend/src/components/mobile/schedule/ScheduleCard.jsx index 5ae1578..7c1bdc8 100644 --- a/frontend/src/components/mobile/schedule/ScheduleCard.jsx +++ b/frontend/src/components/mobile/schedule/ScheduleCard.jsx @@ -58,8 +58,8 @@ const ScheduleCard = memo(function ScheduleCard({ schedule, onClick, className =

{decodeHtmlEntities(schedule.title)}

- {/* 시간 + 카테고리 + 소스 */} -
+ {/* 시간 + 카테고리 */} +
{timeStr && ( @@ -72,13 +72,14 @@ const ScheduleCard = memo(function ScheduleCard({ schedule, onClick, className = {categoryInfo.name} )} - {sourceName && ( - - - {sourceName} - - )}
+ {/* 소스 */} + {sourceName && ( +
+ + {sourceName} +
+ )} {/* 멤버 */} {displayMembers.length > 0 && (