Add todos admin and 30s refresh

This commit is contained in:
kihong.kim
2026-01-24 23:45:22 +09:00
parent 2944b6d9b5
commit 6fb00fec5d
5 changed files with 617 additions and 276 deletions

View File

@@ -13,6 +13,8 @@ import '../../services/family_service.dart';
import '../../services/photo_service.dart';
import '../../services/announcement_service.dart';
import '../../services/schedule_service.dart';
import '../../models/todo_item.dart';
import '../../services/todo_service.dart';
class AdminScreen extends StatefulWidget {
const AdminScreen({super.key});
@@ -25,7 +27,7 @@ class _AdminScreenState extends State<AdminScreen> {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 5,
length: 6,
child: Scaffold(
appBar: AppBar(
title: const Text('Admin Settings'),
@@ -37,6 +39,7 @@ class _AdminScreenState extends State<AdminScreen> {
Tab(text: 'Bible Verses'),
Tab(text: 'Announcements'),
Tab(text: 'Schedules'),
Tab(text: 'Todos'),
],
),
),
@@ -47,6 +50,7 @@ class _AdminScreenState extends State<AdminScreen> {
BibleVerseManagerTab(),
AnnouncementManagerTab(),
ScheduleManagerTab(),
TodoManagerTab(),
],
),
),
@@ -1239,3 +1243,226 @@ class _ScheduleManagerTabState extends State<ScheduleManagerTab> {
);
}
}
class TodoManagerTab extends StatefulWidget {
const TodoManagerTab({super.key});
@override
State<TodoManagerTab> createState() => _TodoManagerTabState();
}
class _TodoManagerTabState extends State<TodoManagerTab> {
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => _showAddTodoDialog(context),
child: const Icon(Icons.check_box),
),
body: FutureBuilder<List<TodoItem>>(
future: Provider.of<TodoService>(context).fetchTodos(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final todos = snapshot.data!;
if (todos.isEmpty) {
return const Center(
child: Text(
'No todos added yet',
style: TextStyle(color: Colors.grey),
),
);
}
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return ListTile(
leading: Checkbox(
value: todo.completed,
onChanged: (val) async {
await Provider.of<TodoService>(
context,
listen: false,
).updateTodo(
TodoItem(
id: todo.id,
familyMemberId: todo.familyMemberId,
title: todo.title,
completed: val ?? false,
dueDate: todo.dueDate,
),
);
setState(() {});
},
),
title: Text(
todo.title,
style: TextStyle(
decoration:
todo.completed ? TextDecoration.lineThrough : null,
),
),
subtitle: todo.dueDate != null
? Text(
todo.dueDate!.toIso8601String().split('T')[0],
style:
const TextStyle(fontSize: 12, color: Colors.grey),
)
: null,
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit, color: Colors.blue),
onPressed: () => _showEditTodoDialog(context, todo),
),
IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () async {
await Provider.of<TodoService>(
context,
listen: false,
).deleteTodo(todo.id);
setState(() {});
},
),
],
),
);
},
);
},
),
);
}
void _showAddTodoDialog(BuildContext context) {
_showTodoDialog(context, null);
}
void _showEditTodoDialog(BuildContext context, TodoItem todo) {
_showTodoDialog(context, todo);
}
void _showTodoDialog(BuildContext context, TodoItem? todo) {
final isEditing = todo != null;
final titleController = TextEditingController(text: todo?.title ?? '');
final dateController = TextEditingController(
text: todo?.dueDate?.toIso8601String().split('T')[0] ?? '',
);
String? selectedFamilyMemberId = todo?.familyMemberId;
bool isCompleted = todo?.completed ?? false;
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setDialogState) => AlertDialog(
title: Text(isEditing ? 'Edit Todo' : 'Add Todo'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: titleController,
decoration: const InputDecoration(labelText: 'Title'),
),
TextField(
controller: dateController,
decoration: const InputDecoration(
labelText: 'Due Date (YYYY-MM-DD)',
hintText: '2024-01-01',
),
),
FutureBuilder<List<FamilyMember>>(
future: Provider.of<FamilyService>(context, listen: false)
.fetchFamilyMembers(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const SizedBox();
final members = snapshot.data!;
return DropdownButtonFormField<String>(
value: selectedFamilyMemberId,
decoration: const InputDecoration(
labelText: 'Family Member',
),
items: members.map((member) {
return DropdownMenuItem(
value: member.id,
child: Text(member.name),
);
}).toList(),
onChanged: (value) {
setDialogState(() {
selectedFamilyMemberId = value;
});
},
);
},
),
if (isEditing)
SwitchListTile(
title: const Text('Completed'),
value: isCompleted,
onChanged: (value) {
setDialogState(() {
isCompleted = value;
});
},
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
TextButton(
onPressed: () async {
if (titleController.text.isNotEmpty &&
selectedFamilyMemberId != null) {
final date = dateController.text.isNotEmpty
? DateTime.tryParse(dateController.text)
: null;
final newItem = TodoItem(
id: todo?.id ?? '',
familyMemberId: selectedFamilyMemberId!,
title: titleController.text,
completed: isCompleted,
dueDate: date,
);
if (isEditing) {
await Provider.of<TodoService>(
context,
listen: false,
).updateTodo(newItem);
} else {
await Provider.of<TodoService>(
context,
listen: false,
).createTodo(newItem);
}
if (mounted) {
Navigator.pop(context);
setState(() {});
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Title and Family Member are required')),
);
}
},
child: Text(isEditing ? 'Save' : 'Add'),
),
],
),
),
);
}
}