From 9068d85c92aa93b00f342fe00f9494aed150196b Mon Sep 17 00:00:00 2001 From: "LAPTOP-EJ2PF6VA\\genie" Date: Mon, 16 Mar 2026 00:38:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E6=B7=BB=E5=84=AA=E5=8C=96=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/gradle.properties | 3 + lib/clockin/clock_in_api.dart | 2 +- lib/clockin/clock_in_manager.dart | 2 +- lib/clockin/clock_in_model.dart | 3 + lib/expense/expense_api.dart | 2 +- lib/expense/expense_detail.dart | 2 +- lib/expense/expense_model.dart | 3 + lib/leave/leave_api.dart | 53 ++++++++- lib/leave/leave_detail.dart | 188 ++++++++++++++++++++++-------- lib/leave/leave_form.dart | 2 + lib/leave/leave_model.dart | 6 +- lib/main.dart | 101 +++------------- 12 files changed, 223 insertions(+), 144 deletions(-) diff --git a/android/gradle.properties b/android/gradle.properties index fbee1d8..5f28e47 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,2 +1,5 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true + +kotlin.incremental=false +kotlin.incremental.android=false \ No newline at end of file diff --git a/lib/clockin/clock_in_api.dart b/lib/clockin/clock_in_api.dart index 5490081..656b8d3 100644 --- a/lib/clockin/clock_in_api.dart +++ b/lib/clockin/clock_in_api.dart @@ -25,7 +25,7 @@ class ClockInApiService { return await _apiService.fetchList( tableName: "hrs_ClockInRecord", pk: "ClockInId", - queryFilter: "1^100^ClockInDateTime^*^ClockInDateTime >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)^^ClockInUserId^$userId", + queryFilter: "1^100^ClockInDateTime^*^ClockInDateTime >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)^hrs_ClockInRecord^ClockInUserId^$userId", fromJson: (json) => ClockInRecord.fromJson(json), ); } diff --git a/lib/clockin/clock_in_manager.dart b/lib/clockin/clock_in_manager.dart index 3f097b0..5233014 100644 --- a/lib/clockin/clock_in_manager.dart +++ b/lib/clockin/clock_in_manager.dart @@ -241,7 +241,7 @@ class _ClockInManagerState extends State { return ListTile( leading: Icon(Icons.access_time, color: record.typeColor), title: Text("${record.type} - ${record.formattedDateTime}"), - subtitle: Text(record.storeId ?? ""), + subtitle: Text(record.StoreName ?? ""), ); } } \ No newline at end of file diff --git a/lib/clockin/clock_in_model.dart b/lib/clockin/clock_in_model.dart index 18667cd..eca5bcd 100644 --- a/lib/clockin/clock_in_model.dart +++ b/lib/clockin/clock_in_model.dart @@ -9,6 +9,7 @@ class ClockInRecord { final double? longitude; final String? type; final String? storeId; + final String? StoreName; ClockInRecord({ this.clockInId, @@ -18,6 +19,7 @@ class ClockInRecord { this.longitude, this.type, this.storeId, + this.StoreName, }); factory ClockInRecord.fromJson(Map json) { @@ -29,6 +31,7 @@ class ClockInRecord { longitude: double.tryParse(json['ClockInLongitude']?.toString() ?? '0'), type: json['ClockInType'] as String?, storeId: json['ClockInStoreId'] as String?, + StoreName: json['StoreName'] as String?, ); } diff --git a/lib/expense/expense_api.dart b/lib/expense/expense_api.dart index 594dfd6..f604866 100644 --- a/lib/expense/expense_api.dart +++ b/lib/expense/expense_api.dart @@ -23,7 +23,7 @@ class ExpenseApiService { // 1. 獲取費用申請清單 Future> fetchExpenses(String empNo) async { // 根據 index.js 邏輯,queryFilter 應符合後端 split('^') 的期待 - String queryFilter = "1^100^ApplyDate^*^EmpNo^$empNo"; + String queryFilter = "1^100^ApplyDate^*^^eipm24^EmpNo^$empNo^^"; return await _apiService.fetchList( tableName: "acc_ExpsApply", pk: "ApplyId", diff --git a/lib/expense/expense_detail.dart b/lib/expense/expense_detail.dart index e71e20b..33ab6d1 100644 --- a/lib/expense/expense_detail.dart +++ b/lib/expense/expense_detail.dart @@ -115,7 +115,7 @@ class ExpenseDetail extends StatelessWidget { children: [ _buildDetailRow(Icons.event, '費用日期', DateFormat('yyyy-MM-dd').format(expense.applyDate)), const Divider(height: 30), - _buildDetailRow(Icons.badge_outlined, '申請人工號', expense.empNo), + _buildDetailRow(Icons.badge_outlined, '申請人工號', '${expense.empNo} ${expense.personCName}'), const Divider(height: 30), _buildDetailRow(Icons.category, '費用類別', expense.expCName ?? expense.expId ?? '未分類'), const Divider(height: 30), diff --git a/lib/expense/expense_model.dart b/lib/expense/expense_model.dart index 96884ad..0fe2f02 100644 --- a/lib/expense/expense_model.dart +++ b/lib/expense/expense_model.dart @@ -25,6 +25,7 @@ class ExpenseApply { final int? applyId; final DateTime applyDate; final String empNo; + final String? personCName; // new final String? rowData; // 費用說明 final String? picPath; // 圖片檔名/路徑 final String? expId; // [新增] 類別 ID @@ -62,6 +63,7 @@ class ExpenseApply { this.applyId, required this.applyDate, required this.empNo, + this.personCName, //關聯擴充欄位 this.rowData, this.picPath, this.expId, @@ -76,6 +78,7 @@ class ExpenseApply { applyId: int.tryParse(json['ApplyId']?.toString() ?? ''), applyDate: json['ApplyDate'] != null ? DateTime.parse(json['ApplyDate']) : DateTime.now(), empNo: json['EmpNo'] as String? ?? '', + personCName: json['personCName'] as String? ?? '', // new rowData: json['RowData'] as String?, picPath: json['PicPath'] as String?, expId: json['ExpId'] as String? ?? '', // [新增] diff --git a/lib/leave/leave_api.dart b/lib/leave/leave_api.dart index e28fbe5..bbd591e 100644 --- a/lib/leave/leave_api.dart +++ b/lib/leave/leave_api.dart @@ -64,7 +64,7 @@ class LeaveApiService { // *** 關聯取得假別名稱,存到 Cache // 排序:按單據日期降冪 - String queryFilter = "1^100^billdate^*^start_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)^^personid^$personId"; + String queryFilter = "1^100^billdate^*^start_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 21 DAY) AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)^hrsm11^personid^$personId"; return await _apiService.fetchList( tableName: "hrs_leave", @@ -147,4 +147,55 @@ class LeaveApiService { ), ); } + + /// 上呈請假單 + /// 上呈請假單 + /// 對應 API 規格: bpmm02_spread + Future promoteLeave(String personId, String billNo) async { + // 依據標準規範,參數一律使用 para0x + final Map params = { + "para01": "eipm21", + "para02": personId, + "para03": billNo, + }; + + try { + // 使用與 sign_todo_api.dart 相同的 fetchProcedure 結構 + // 依據您提供的 url: ".../bpmm02_spread/1/",這裡將 endpoint 設為 "bpmm02_spread/1/" + // (若底層機制會自動補齊後方的 /1/,可自行改為 "bpmm02_spread") + await _apiService.fetchProcedure( + procedureEndpoint: "bpmm02_spread", + params: params, + fromJson: (json) => json, // 僅需確認執行成功,回傳值暫不處理 + ); + + // 只要 fetchProcedure 沒有拋出異常 (且底層已處理 code == 0),即視為成功 + return true; + } catch (e) { + // 統一的錯誤捕獲與日誌 + print("LeaveApiService.promoteLeave 異常: $e"); + return false; + } + } + + /// 取得簽核進度歷程 + /// 對應 API: bpm_sign_history/2/ + Future>> fetchSignHistory(String billNo) async { + final Map params = { + "para01": "eipm21", // 來源單別 functiontag + "para02": billNo, // 原單單號 query_id + }; + + try { + // 呼叫 SP,並將回傳的每一筆資料解析為 Map + return await _apiService.fetchProcedure>( + procedureEndpoint: "bpm_sign_history", + params: params, + fromJson: (json) => json as Map, + ); + } catch (e) { + print("LeaveApiService.fetchSignHistory 異常: $e"); + return []; + } + } } \ No newline at end of file diff --git a/lib/leave/leave_detail.dart b/lib/leave/leave_detail.dart index 14176e4..f59e750 100644 --- a/lib/leave/leave_detail.dart +++ b/lib/leave/leave_detail.dart @@ -13,30 +13,46 @@ class LeaveDetail extends StatefulWidget { class _LeaveDetailState extends State { final LeaveApiService _apiService = LeaveApiService(); - bool _isWithdrawing = false; + bool _isPromoting = false; - // 實作撤回邏輯 - Future _handleWithdraw() async { + // 實作上呈邏輯 + Future _handlePromote() async { final confirm = await showDialog( context: context, builder: (ctx) => AlertDialog( - title: const Text('確認撤回'), - content: const Text('您確定要撤回此張請假單嗎?'), + title: const Text('確認上呈'), + content: const Text('您確定要將此張請假單上呈簽核嗎?'), actions: [ - TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('取消')), - TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('確定', style: TextStyle(color: Colors.red))), + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text('取消', style: TextStyle(color: Colors.grey)) + ), + TextButton( + onPressed: () => Navigator.pop(ctx, true), + child: const Text('確定上呈', style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold)) + ), ], ), ); if (confirm == true) { - setState(() => _isWithdrawing = true); - final success = await _apiService.withdrawLeave(widget.leave.billNo); - setState(() => _isWithdrawing = false); + setState(() => _isPromoting = true); + + final success = await _apiService.promoteLeave( + widget.leave.personId, + widget.leave.billNo + ); + + setState(() => _isPromoting = false); if (success && mounted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已成功撤回'))); - Navigator.pop(context, true); // 回傳 true 讓前一頁 (Manager) 重新整理列表 + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('已成功上呈單據'), + backgroundColor: Colors.green, + ) + ); + Navigator.pop(context, true); } } } @@ -56,24 +72,31 @@ class _LeaveDetailState extends State { _buildInfoCard(context), const SizedBox(height: 24), - // 修正 3: 新增簽核進度區塊 + // 簽核進度區塊 const Text('簽核進度', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.blueGrey)), const SizedBox(height: 8), - _buildFlowTimeline(), + _buildSignHistoryTable(), const SizedBox(height: 24), - // 修正 5: 實作撤回按鈕邏輯 if (leave.flowStatus == '0' || leave.flowStatus == '1') SizedBox( width: double.infinity, - child: OutlinedButton.icon( - onPressed: _isWithdrawing ? null : _handleWithdraw, - icon: _isWithdrawing ? const CircularProgressIndicator(strokeWidth: 2) : const Icon(Icons.history_outlined), - label: Text(_isWithdrawing ? '處理中...' : '撤回申請'), - style: OutlinedButton.styleFrom( - foregroundColor: Colors.red, - side: const BorderSide(color: Colors.red), - padding: const EdgeInsets.symmetric(vertical: 12), + child: ElevatedButton.icon( + onPressed: _isPromoting ? null : _handlePromote, + icon: _isPromoting + ? const SizedBox( + width: 20, height: 20, + child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white) + ) + : const Icon(Icons.send_rounded), + label: Text(_isPromoting ? '上呈處理中...' : '上呈申請'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), ), ), ), @@ -92,7 +115,6 @@ class _LeaveDetailState extends State { children: [ Text('單號: ${widget.leave.billNo}', style: const TextStyle(fontSize: 14, color: Colors.grey)), const SizedBox(height: 4), - // 修正 4: 顯示 leaveTypeName Text(widget.leave.leaveTypeName, style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold)), ], ), @@ -106,10 +128,7 @@ class _LeaveDetailState extends State { } Widget _buildInfoCard(BuildContext context) { - // 處理代理人顯示文字 (防呆處理) - String displayAgent = widget.leave.agentName.isNotEmpty - ? '${widget.leave.agentName} (${widget.leave.agentId})' - : widget.leave.agentId; + String displayAgent = '${widget.leave.agentId} ${widget.leave.agentName}'; return Card( elevation: 0, @@ -122,7 +141,6 @@ class _LeaveDetailState extends State { const Divider(height: 30), _buildDetailRow(Icons.timer_outlined, '請假時數', '${widget.leave.days} 天 ${widget.leave.hours} 小時'), const Divider(height: 30), - // 修正 6: 顯示帶有姓名的代理人資訊 _buildDetailRow(Icons.person_outline, '代理人', displayAgent), const Divider(height: 30), _buildDetailRow(Icons.edit_note, '事由', widget.leave.leaveNote ?? '未填寫'), @@ -148,33 +166,103 @@ class _LeaveDetailState extends State { ); } - // 實作簽核時間軸 (搭配 FutureBuilder) - Widget _buildFlowTimeline() { - return FutureBuilder>( - future: _apiService.fetchFlowLogs(widget.leave.billNo), + // ===== 簽核進度 Table (精準對接 SP 欄位) ===== + Widget _buildSignHistoryTable() { + return FutureBuilder>>( + future: _apiService.fetchSignHistory(widget.leave.billNo), builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator()); - if (!snapshot.hasData || snapshot.data!.isEmpty) return const Text('尚無簽核歷程', style: TextStyle(color: Colors.grey)); + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: Padding( + padding: EdgeInsets.all(20.0), + child: CircularProgressIndicator(), + )); + } + if (!snapshot.hasData || snapshot.data!.isEmpty) { + return Container( + padding: const EdgeInsets.all(16), + width: double.infinity, + decoration: BoxDecoration(color: Colors.grey.shade50, borderRadius: BorderRadius.circular(10)), + child: const Text('尚無簽核歷程', style: TextStyle(color: Colors.grey), textAlign: TextAlign.center), + ); + } final logs = snapshot.data!; - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration(color: Colors.grey.shade50, borderRadius: BorderRadius.circular(10), border: Border.all(color: Colors.grey.shade200)), - child: Column( - children: logs.map((log) { - return ListTile( - contentPadding: EdgeInsets.zero, - leading: Icon( - log.status == '1' ? Icons.check_circle : (log.status == 'X' ? Icons.cancel : Icons.pending), - color: log.status == '1' ? Colors.green : (log.status == 'X' ? Colors.red : Colors.orange), - ), - title: Text('${log.stepName} - ${log.approverName}'), - subtitle: Text(log.time != null ? DateFormat('MM/dd HH:mm').format(log.time!) : ''), - ); - }).toList(), + + return ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Table( + border: TableBorder.all(color: Colors.grey.shade300, width: 1), + // 設定各欄位的寬度比例:意見欄位給予最大空間 + columnWidths: const { + 0: FlexColumnWidth(2.0), // 簽核人 + 1: FlexColumnWidth(2.0), // 狀態 + 2: FlexColumnWidth(3.0), // 意見 + 3: FlexColumnWidth(2.5), // 時間 + }, + children: [ + // 表頭列 + TableRow( + decoration: BoxDecoration(color: Colors.blueGrey.shade50), + children: [ + _buildTableCell('簽核人', isHeader: true), + _buildTableCell('狀態', isHeader: true), + _buildTableCell('意見', isHeader: true), + _buildTableCell('時間', isHeader: true), + ], + ), + // 動態產生資料列,對接 SP 欄位 + ...logs.map((log) { + final approver = log['personcname']?.toString() ?? '-'; + final statusName = log['sign_status_name']?.toString() ?? '-'; + final note = log['sign_note']?.toString() ?? ''; + final timeStr = log['create_date']?.toString() ?? ''; + + // 簡單判斷狀態文字來給予顏色提示 (依照中文涵義) + Color statusColor = Colors.black87; + if (statusName.contains('同意') || statusName.contains('核准')) { + statusColor = Colors.green; + } else if (statusName.contains('駁回') || statusName.contains('拒絕')) { + statusColor = Colors.red; + } else if (statusName.contains('待簽')) { + statusColor = Colors.orange; + } + + // 處理時間格式,如果為 yyyy-MM-dd HH:mm:ss 則截取 MM-dd HH:mm 以節省空間 + String displayTime = timeStr; + if (displayTime.length >= 16) { + // 如果前面有年份(例如: 2026-03-04),可視需求決定要不要擷取 substring(5, 16) + displayTime = displayTime.substring(5, 16); + } + + return TableRow( + children: [ + _buildTableCell(approver), + _buildTableCell(statusName, textColor: statusColor), + _buildTableCell(note.isEmpty ? '-' : note), + _buildTableCell(displayTime), + ], + ); + }).toList(), + ], ), ); }, ); } + + // 表格單元格的小元件 + Widget _buildTableCell(String text, {bool isHeader = false, Color? textColor}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 4), + child: Text( + text, + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: isHeader ? FontWeight.bold : FontWeight.normal, + color: textColor ?? (isHeader ? Colors.blueGrey.shade700 : Colors.black87), + fontSize: 13, + ), + ), + ); + } } \ No newline at end of file diff --git a/lib/leave/leave_form.dart b/lib/leave/leave_form.dart index 21c935d..e0ba96a 100644 --- a/lib/leave/leave_form.dart +++ b/lib/leave/leave_form.dart @@ -76,7 +76,9 @@ class _LeaveFormState extends State { billNo: '', // API 端生成 personId: widget.userId, agentId: _selectedAgent!['id'].toString(), // 確保轉為 String + agentName: '', leaveType: _selectedType!.id, // 傳送 ID 給後端 + leaveTypeName: '', startTime: start, endTime: end, days: calcDays, // 寫入計算後的天數 diff --git a/lib/leave/leave_model.dart b/lib/leave/leave_model.dart index 0635ba5..a826383 100644 --- a/lib/leave/leave_model.dart +++ b/lib/leave/leave_model.dart @@ -47,9 +47,9 @@ class Leave { this.billDate, required this.personId, required this.agentId, - this.agentName = '', + required this.agentName, required this.leaveType, - this.leaveTypeName = '', + required this.leaveTypeName, this.startTime, this.endTime, this.days = 0, @@ -70,7 +70,7 @@ class Leave { billDate: json['billdate'] != null ? DateTime.tryParse(json['billdate']) : null, personId: json['personid'] as String? ?? '', agentId: json['agentid'] as String? ?? '', - agentName: json['agentname'] ?? '', /// 假設 API 會 Join 姓名 + agentName: json['agentName'] ?? '', /// 假設 API 會 Join 姓名 leaveType: json['leavetype'] as String? ?? '', // leaveTypeName: json['leavetype_name'] ?? json['leavetype'] ?? '', leaveTypeName: typeName, // 這裡現在保證有值 diff --git a/lib/main.dart b/lib/main.dart index 35c286a..011bb11 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -98,92 +98,21 @@ class MainMenu extends StatelessWidget { // 所有選單項目的列表 final List menuItems = [ - // 1. 新聞資訊 (使用您的 NewsManager) - MenuItem( - title: '公告事項', - icon: Icons.article, - //targetScreen: NewsManager(), - targetScreen: AnnouncementManager(), - ), - // 2. 員工打卡 - MenuItem( - title: '員工打卡', - icon: Icons.access_time_filled, - targetScreen: ClockInManager(userId: uid), - ), - // 2. 員工打卡 - MenuItem( - title: '請假作業', - icon: Icons.event_available, - targetScreen: LeaveManager(currentUserId: uid), - ), // 3. 待簽核事項 - MenuItem( - title: '待簽核事項', - icon: Icons.pending_actions, - targetScreen: SignTodoManager(), - ), - // 4. 待辦事項 - MenuItem( - title: '待辦事項', - icon: Icons.checklist, - targetScreen: TodoManager(currentUserId: uid), - ), - // 5. 行事曆 - MenuItem( - title: '行事曆', - icon: Icons.calendar_today, - targetScreen: CalendarManager(userId: uid), - ), - // 6. 其他功能 (佔位) - MenuItem( - title: '會議通知', - icon: Icons.people_alt, - targetScreen: MeetingManager(), - ), - // 6. 其他功能 (佔位) - MenuItem( - title: '產品型錄', - icon: Icons.local_mall, - targetScreen: PlaceholderScreen(title: '產品型錄'), - ), - // 6. 其他功能 (佔位) - MenuItem( - title: '通訊錄', - icon: Icons.contact_phone, - targetScreen: PersonManager(), - ), - MenuItem( - title: '業績查詢', - icon: Icons.query_stats, - targetScreen: ChannelSalesManager(), - ), - MenuItem( - title: 'Issue List', - icon: Icons.bug_report, - targetScreen: IssueManager(), - ), - MenuItem( - title: '訊息通知', - icon: Icons.notifications, - targetScreen: MessageManager(currentUserId: uid), - ), - MenuItem( - title: '費用申請', - icon: Icons.article, - targetScreen: ExpenseManager(currentUserId: uid), - //targetScreen: AnnouncementManager(), - ), - MenuItem( - title: '新聞(Demo)', - icon: Icons.article, - targetScreen: NewsManager(), - //targetScreen: AnnouncementManager(), - ), - MenuItem( - title: '設定', - icon: Icons.settings, - targetScreen: SettingsPage(), - ), + MenuItem(title: '公告事項', icon: Icons.article, targetScreen: AnnouncementManager()), + MenuItem(title: '員工打卡',icon: Icons.access_time_filled,targetScreen: ClockInManager(userId: uid)), + MenuItem(title: '請假作業', icon: Icons.event_available, targetScreen: LeaveManager(currentUserId: uid)), + MenuItem(title: '待簽核事項', icon: Icons.pending_actions, targetScreen: SignTodoManager()), + MenuItem(title: '待辦事項', icon: Icons.checklist, targetScreen: TodoManager(currentUserId: uid)), + MenuItem(title: '行事曆', icon: Icons.calendar_today, targetScreen: CalendarManager(userId: uid)), + MenuItem(title: '會議通知', icon: Icons.people_alt, targetScreen: MeetingManager()), + MenuItem(title: '產品型錄', icon: Icons.local_mall, targetScreen: PlaceholderScreen(title: '產品型錄')), + MenuItem(title: '通訊錄', icon: Icons.contact_phone, targetScreen: PersonManager()), + MenuItem(title: '業績查詢', icon: Icons.query_stats, targetScreen: ChannelSalesManager()), + MenuItem(title: 'Issue List', icon: Icons.bug_report, targetScreen: IssueManager()), + MenuItem(title: '訊息通知', icon: Icons.notifications, targetScreen: MessageManager(currentUserId: uid)), + MenuItem(title: '費用申請', icon: Icons.article, targetScreen: ExpenseManager(currentUserId: uid)), + MenuItem(title: '新聞(Demo)', icon: Icons.article, targetScreen: NewsManager()), + MenuItem(title: '設定', icon: Icons.settings, targetScreen: SettingsPage()), ]; return Scaffold(