2025-12-29 First Commit

This commit is contained in:
DATAEXPRESS\4734
2025-12-29 15:20:40 +08:00
commit fb2603c6f0
92 changed files with 6006 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
import './todo_model.dart';
import '../services/generic_api_service.dart'; // 引入共用服務
class TodoApiService {
final GenericApiService _apiService = GenericApiService();
final String currentUserId;
TodoApiService({this.currentUserId = 'admin'});
Future<List<Todo>> fetchTodos() async {
return await _apiService.fetchList<Todo>(
tableName: "eip_todolist",
pk: "id",
// 如果 queryFilter 需要動態包含使用者 ID,可以在這裡字串插值
// 假設原程式碼邏輯是 "1^10^id^*^^pmsm02^^"
queryFilter: "1^10^id^*^^pmsm02^^",
fromJson: (json) => Todo.fromJson(json),
// 如果未來需要傳遞其他參數 (如 userID),可以用 additionalParams
// additionalParams: { "userId": currentUserId },
);
}
}
+97
View File
@@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import './todo_model.dart';
class TodoDetail extends StatelessWidget {
final Todo todo;
const TodoDetail({required this.todo, super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(todo.taskName, overflow: TextOverflow.ellipsis),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 任務標題
Text(
todo.taskName,
style: const TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold, color: Colors.black87),
),
const Divider(height: 24.0),
// 任務屬性表格 (簡潔顯示)
_buildAttributeRow(context, '狀態', todo.status ?? 'N/A', todo.statusColor),
_buildAttributeRow(context, '優先級', todo.priority ?? 'N/A', Colors.red),
_buildAttributeRow(context, '截止日期', todo.formattedEndDate, Colors.blue),
_buildAttributeRow(context, '建立者', todo.createdBy ?? 'N/A', Colors.grey),
_buildAttributeRow(context, '分類', todo.className, Colors.purple),
const Divider(height: 32.0),
// 詳細說明
const Text(
'詳細說明:',
style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w600, color: Colors.black87),
),
const SizedBox(height: 8),
Text(
todo.description ?? '無詳細說明。',
style: const TextStyle(fontSize: 16.0, height: 1.5, color: Colors.black54),
textAlign: TextAlign.justify,
),
const SizedBox(height: 40),
// 底部按鈕 (例如:標記完成)
Center(
child: ElevatedButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('已標記任務 "${todo.taskName}" 待實作更新狀態。')),
);
// 實際應用中,這裡會呼叫 API 更新 pbi_status 為 'DONE'
},
icon: const Icon(Icons.check_circle_outline),
label: const Text('標記為已完成'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15),
backgroundColor: Colors.green,
foregroundColor: Colors.white,
textStyle: const TextStyle(fontSize: 18),
),
),
),
],
),
),
);
}
Widget _buildAttributeRow(BuildContext context, String label, String value, Color color) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 100,
child: Text(
'$label:',
style: const TextStyle(fontWeight: FontWeight.w500, color: Colors.black),
),
),
Expanded(
child: Text(
value,
style: TextStyle(fontWeight: FontWeight.w600, color: color),
),
),
],
),
);
}
}
+149
View File
@@ -0,0 +1,149 @@
import 'package:flutter/material.dart';
import './todo_api.dart';
import './todo_model.dart';
import './todo_detail.dart'; // 稍後創建
// import './main.dart'; // 確保可以訪問 MainMenu
class TodoManager extends StatefulWidget {
// 實際應用中,這裡應該傳入當前用戶 ID
final String currentUserId;
const TodoManager({this.currentUserId = 'admin', super.key});
@override
State<StatefulWidget> createState() {
return _TodoManagerState();
}
}
class _TodoManagerState extends State<TodoManager> {
late TodoApiService _apiService;
late Future<List<Todo>> _todosFuture;
@override
void initState() {
super.initState();
_apiService = TodoApiService(currentUserId: widget.currentUserId);
// 頁面加載時自動開始獲取資料
_todosFuture = _apiService.fetchTodos();
}
// 刷新資料的函數
void _refreshTodos() {
setState(() {
_todosFuture = _apiService.fetchTodos();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('我的待辦事項'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.refresh),
tooltip: '刷新列表',
onPressed: _refreshTodos,
),
// 假設這裡使用 Navigator.pop(context) 即可返回 MainMenu
IconButton(
icon: const Icon(Icons.home),
tooltip: '返回主頁',
onPressed: () => Navigator.pop(context),
),
],
),
body: FutureBuilder<List<Todo>>(
future: _todosFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('載入失敗: ${snapshot.error}', textAlign: TextAlign.center),
const SizedBox(height: 16),
ElevatedButton(onPressed: _refreshTodos, child: const Text('重試')),
],
),
);
} else if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return TodoList(todos: snapshot.data!);
} else {
return const Center(child: Text('目前沒有待辦事項。工作很輕鬆!'));
}
},
),
);
}
}
// -----------------------------------------------------------
// 列表顯示小部件 (TodoList)
// -----------------------------------------------------------
class TodoList extends StatelessWidget {
final List<Todo> todos;
const TodoList({required this.todos, super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: todos.length,
itemBuilder: (BuildContext context, int index) {
final item = todos[index];
return Card(
elevation: 3,
margin: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 16.0),
child: InkWell(
onTap: () {
// 點擊項目:導航到詳細頁面
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TodoDetail(todo: item),
),
);
},
child: ListTile(
// 左側狀態指示器
leading: Container(
width: 10,
decoration: BoxDecoration(
color: item.statusColor,
borderRadius: BorderRadius.circular(5),
),
),
title: Text(
item.taskName,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
'分類: ${item.className} | 優先級: ${item.priority ?? '一般'}',
style: const TextStyle(fontSize: 12),
),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'截止日',
style: TextStyle(fontSize: 10, color: item.statusColor),
),
Text(
item.formattedEndDate,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600),
),
],
),
),
),
);
},
);
}
}
+70
View File
@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class Todo {
final int id;
final String className; // todolist_class (類別/分類)
final String taskName; // task_name (任務名稱/標題)
final String? description; // task_desc (詳細說明)
final String? priority; // issue_priority (優先級)
final String? status; // pbi_status (狀態)
final DateTime? endDate; // end_date (預計完成日期)
final String? createdBy; // create_user (建立者)
final DateTime? createDate; // create_date (建立日期)
Todo({
required this.id,
required this.className,
required this.taskName,
this.description,
this.priority,
this.status,
this.endDate,
this.createdBy,
this.createDate,
});
// Factory 構造函數:從 API 返回的 JSON (Map) 創建 Todo 物件
factory Todo.fromJson(Map<String, dynamic> json) {
// 輔助函數:安全解析日期字串
DateTime? parseDate(dynamic date) {
if (date is String && date.isNotEmpty) {
// 假設日期格式為 YYYY-MM-DD HH:mm:ss.sss 或 YYYY-MM-DD
return DateTime.tryParse(date);
}
return null;
}
return Todo(
id: json['id'] as int? ?? 0,
className: json['todolist_class'] as String? ?? '未分類',
taskName: json['task_name'] as String? ?? '無任務標題',
description: json['task_desc'] as String?,
priority: json['issue_priority'] as String?,
status: json['pbi_status'] as String?,
endDate: parseDate(json['end_date']),
createdBy: json['create_user'] as String?,
createDate: parseDate(json['create_date']),
);
}
// 格式化日期,用於列表顯示
String get formattedEndDate {
if (endDate == null) return 'N/A';
return DateFormat('yyyy/MM/dd').format(endDate!);
}
// 根據狀態獲取顏色 (例如:已完成/進行中)
Color get statusColor {
switch (status?.toUpperCase()) {
case 'WIP': // Work In Progress
return Colors.blue;
case 'DONE': // Completed
return Colors.green;
case 'HOLD': // On Hold
return Colors.orange;
default:
return Colors.grey;
}
}
}