チームまでは表示できた。表示と更新及びメンバー編集不可。エントリー以降も表示不可。

This commit is contained in:
2024-07-27 09:46:00 +09:00
parent c81bcef4bc
commit 08ffc42cdd
24 changed files with 17083 additions and 9 deletions

15597
all_combined.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,7 @@ import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'pages/permission/permission.dart'; import 'pages/permission/permission.dart';
import 'package:rogapp/services/api_service.dart';
Map<String, dynamic> deviceInfo = {}; Map<String, dynamic> deviceInfo = {};
@ -100,6 +100,7 @@ void restoreGame() async {
pref.getBool("rogaining_counted") ?? false; pref.getBool("rogaining_counted") ?? false;
DestinationController.ready_for_goal = DestinationController.ready_for_goal =
pref.getBool("ready_for_goal") ?? false; pref.getBool("ready_for_goal") ?? false;
await Get.putAsync(() => ApiService().init());
} }
} }
@ -150,13 +151,39 @@ void main() async {
}); });
*/ */
try {
// ApiServiceを初期化
//await Get.putAsync(() => ApiService().init());
await initServices();
runApp(const ProviderScope(child: MyApp()));
runApp(const ProviderScope(child: MyApp())); //runApp(HomePage()); // MyApp()からHomePage()に変更
//runApp(HomePage()); // MyApp()からHomePage()に変更 //runApp(const MyApp());
//runApp(const MyApp()); }catch(e, stackTrace){
print('Error during initialization: $e');
print('Stack trace: $stackTrace');
}
} }
Future<void> initServices() async {
print('Starting services ...');
try {
await Get.putAsync(() => ApiService().init());
print('All services started...');
}catch(e){
print('Error initializing ApiService: $e');
}
try {
Get.put(SettingsController());
print('SettingsController initialized successfully');
} catch (e) {
print('Error initializing SettingsController: $e');
}
print('All services started...');
}
Future<void> requestLocationPermission() async { Future<void> requestLocationPermission() async {
try { try {

45
lib/model/category.dart Normal file
View File

@ -0,0 +1,45 @@
// lib/models/category.dart
class NewCategory {
final int id;
final String categoryName;
final int categoryNumber;
final Duration duration;
final int numOfMember;
final bool family;
final bool female;
NewCategory({
required this.id,
required this.categoryName,
required this.categoryNumber,
required this.duration,
required this.numOfMember,
required this.family,
required this.female,
});
factory NewCategory.fromJson(Map<String, dynamic> json) {
return NewCategory(
id: json['id'] ?? 0,
categoryName: json['category_name'] ?? 'Unknown Category',
categoryNumber: json['category_number'] ?? 0,
duration: Duration(seconds: json['duration'] ?? 0),
numOfMember: json['num_of_member'] ?? 1,
family: json['family'] ?? false,
female: json['female'] ?? false,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'category_name': categoryName,
'category_number': categoryNumber,
'duration': duration.inSeconds,
'num_of_member': numOfMember,
'family': family,
'female': female,
};
}
}

45
lib/model/entry.dart Normal file
View File

@ -0,0 +1,45 @@
// lib/models/entry.dart
import 'event.dart';
import 'event.dart';
import 'team.dart';
import 'category.dart';
class Entry {
final int id;
final Team team;
final Event event;
final NewCategory category;
final DateTime date;
final String owner;
Entry({
required this.id,
required this.team,
required this.event,
required this.category,
required this.date,
required this.owner,
});
factory Entry.fromJson(Map<String, dynamic> json) {
return Entry(
id: json['id'],
team: Team.fromJson(json['team']),
event: Event.fromJson(json['event']),
category: NewCategory.fromJson(json['category']),
date: DateTime.parse(json['date']),
owner: json['owner'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'team': team.toJson(),
'event': event.toJson(),
'category': category.toJson(),
'date': date.toIso8601String(),
'owner': owner,
};
}
}

33
lib/model/event.dart Normal file
View File

@ -0,0 +1,33 @@
// lib/models/event.dart
class Event {
final int id;
final String eventName;
final DateTime startDatetime;
final DateTime endDatetime;
Event({
required this.id,
required this.eventName,
required this.startDatetime,
required this.endDatetime,
});
factory Event.fromJson(Map<String, dynamic> json) {
return Event(
id: json['id'],
eventName: json['event_name'],
startDatetime: DateTime.parse(json['start_datetime']),
endDatetime: DateTime.parse(json['end_datetime']),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'event_name': eventName,
'start_datetime': startDatetime.toIso8601String(),
'end_datetime': endDatetime.toIso8601String(),
};
}
}

44
lib/model/team.dart Normal file
View File

@ -0,0 +1,44 @@
// lib/models/team.dart
import 'category.dart';
import 'user.dart';
class Team {
final int id;
final String zekkenNumber;
final String teamName;
final NewCategory category;
final User owner;
Team({
required this.id,
required this.zekkenNumber,
required this.teamName,
required this.category,
required this.owner,
});
factory Team.fromJson(Map<String, dynamic> json) {
return Team(
id: json['id'] ?? 0,
zekkenNumber: json['zekken_number'] ?? 'Unknown',
teamName: json['team_name'] ?? 'Unknown Team',
category: json['category'] != null
? NewCategory.fromJson(json['category'])
: NewCategory(id: 0, categoryName: 'Unknown', categoryNumber: 0, duration: Duration.zero, numOfMember: 1, family: false, female: false),
owner: json['owner'] != null
? User.fromJson(json['owner'])
: User(id: 0, email: 'unknown@example.com', firstname: 'Unknown', lastname: 'User', dateOfBirth: null, female: false, isActive: false),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'zekken_number': zekkenNumber,
'team_name': teamName,
'category': category.toJson(),
'owner': owner.toJson(),
};
}
}

47
lib/model/user.dart Normal file
View File

@ -0,0 +1,47 @@
// lib/models/user.dart
class User {
final int? id;
final String? email;
final String firstname;
final String lastname;
final DateTime? dateOfBirth;
final bool female;
final bool isActive;
User({
this.id,
this.email,
required this.firstname,
required this.lastname,
this.dateOfBirth,
required this.female,
required this.isActive,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
email: json['email'],
firstname: json['firstname'] ?? 'Unknown',
lastname: json['lastname'] ?? 'Unknown',
dateOfBirth: json['date_of_birth'] != null
? DateTime.tryParse(json['date_of_birth'])
: null,
female: json['female'] ?? false,
isActive: json['is_active'] ?? false,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'email': email,
'firstname': firstname,
'lastname': lastname,
'date_of_birth': dateOfBirth?.toIso8601String(),
'female': female,
'is_active': isActive,
};
}
}

View File

@ -206,6 +206,22 @@ class DrawerPage extends StatelessWidget {
width: 0, width: 0,
height: 0, height: 0,
), ),
ListTile(
leading: Icon(Icons.group),
title: Text('チーム管理'),
onTap: () {
Get.back();
Get.toNamed(AppPages.TEAM_LIST);
},
),
ListTile(
leading: Icon(Icons.event),
title: Text('エントリー管理'),
onTap: () {
Get.back();
Get.toNamed(AppPages.ENTRY_LIST);
},
),
ListTile( ListTile(
leading: const Icon(Icons.privacy_tip), leading: const Icon(Icons.privacy_tip),
title: Text("privacy".tr), title: Text("privacy".tr),

View File

@ -0,0 +1,12 @@
import 'package:get/get.dart';
import 'package:rogapp/pages/entry/entry_controller.dart';
import 'package:rogapp/pages/team/member_controller.dart';
import 'package:rogapp/services/api_service.dart';
class EntryBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<ApiService>(() => ApiService());
Get.lazyPut<EntryController>(() => EntryController());
}
}

View File

@ -0,0 +1,155 @@
// lib/entry/entry_controller.dart
import 'package:get/get.dart';
import 'package:rogapp/model/entry.dart';
import 'package:rogapp/model/event.dart';
import 'package:rogapp/model/team.dart';
import 'package:rogapp/model/category.dart';
import 'package:rogapp/services/api_service.dart';
class EntryController extends GetxController {
late final ApiService _apiService;
final entries = <Entry>[].obs;
final events = <Event>[].obs;
final teams = <Team>[].obs;
final categories = <NewCategory>[].obs;
final selectedEvent = Rx<Event?>(null);
final selectedTeam = Rx<Team?>(null);
final selectedCategory = Rx<NewCategory?>(null);
final selectedDate = Rx<DateTime?>(null);
final currentEntry = Rx<Entry?>(null);
@override
void onInit() async{
super.onInit();
await Get.putAsync(() => ApiService().init());
_apiService = Get.find<ApiService>();
try {
await Future.wait([
fetchEntries(),
fetchEvents(),
fetchTeams(),
fetchCategories(),
]);
if (Get.arguments != null && Get.arguments['entry'] != null) {
currentEntry.value = Get.arguments['entry'];
_initializeEntryData();
}
Get.putAsync(() => ApiService().init());
}catch(e){
print('Error initializing data: $e');
}
}
void _initializeEntryData() {
if (currentEntry.value != null) {
selectedEvent.value = currentEntry.value!.event;
selectedTeam.value = currentEntry.value!.team;
selectedCategory.value = currentEntry.value!.category;
selectedDate.value = currentEntry.value!.date;
}
}
Future<void> fetchEntries() async {
try {
final fetchedEntries = await _apiService.getEntries();
entries.assignAll(fetchedEntries);
} catch (e) {
print('Error fetching entries: $e');
}
}
Future<void> fetchEvents() async {
try {
final fetchedEvents = await _apiService.getEvents();
events.assignAll(fetchedEvents);
} catch (e) {
print('Error fetching events: $e');
}
}
Future<void> fetchTeams() async {
try {
final fetchedTeams = await _apiService.getTeams();
teams.assignAll(fetchedTeams);
} catch (e) {
print('Error fetching teams: $e');
}
}
Future<void> fetchCategories() async {
try {
final fetchedCategories = await _apiService.getCategories();
categories.assignAll(fetchedCategories);
} catch (e) {
print('Error fetching categories: $e');
}
}
Future<void> createEntry() async {
if (selectedEvent.value == null || selectedTeam.value == null ||
selectedCategory.value == null || selectedDate.value == null) {
Get.snackbar('Error', 'Please fill all fields');
return;
}
try {
final newEntry = await _apiService.createEntry(
selectedTeam.value!.id,
selectedEvent.value!.id,
selectedCategory.value!.id,
selectedDate.value!,
);
entries.add(newEntry);
Get.back();
} catch (e) {
print('Error creating entry: $e');
Get.snackbar('Error', 'Failed to create entry');
}
}
Future<void> updateEntry() async {
if (currentEntry.value == null) return;
try {
final updatedEntry = await _apiService.updateEntry(
currentEntry.value!.id,
selectedEvent.value!.id,
selectedCategory.value!.id,
selectedDate.value!,
);
final index = entries.indexWhere((entry) => entry.id == updatedEntry.id);
if (index != -1) {
entries[index] = updatedEntry;
}
Get.back();
} catch (e) {
print('Error updating entry: $e');
Get.snackbar('Error', 'Failed to update entry');
}
}
Future<void> deleteEntry() async {
if (currentEntry.value == null) return;
try {
await _apiService.deleteEntry(currentEntry.value!.id);
entries.removeWhere((entry) => entry.id == currentEntry.value!.id);
Get.back();
} catch (e) {
print('Error deleting entry: $e');
Get.snackbar('Error', 'Failed to delete entry');
}
}
void updateEvent(Event? value) => selectedEvent.value = value;
void updateTeam(Team? value) => selectedTeam.value = value;
void updateCategory(NewCategory? value) => selectedCategory.value = value;
void updateDate(DateTime value) => selectedDate.value = value;
bool isOwner() {
// Implement logic to check if the current user is the owner of the entry
return true; // Placeholder
}
}

View File

@ -0,0 +1,62 @@
// lib/pages/entry/entry_detail_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/entry/entry_controller.dart';
class EntryDetailPage extends GetView<EntryController> {
@override
Widget build(BuildContext context) {
final Map<String, dynamic> arguments = Get.arguments ?? {};
final mode = Get.arguments['mode'] as String? ?? 'new';
final entry = Get.arguments['entry'];
return Scaffold(
appBar: AppBar(
title: Text(mode == 'new' ? 'エントリー登録' : 'エントリー詳細'),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Obx(() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownButtonFormField(
decoration: InputDecoration(labelText: 'イベント'),
value: controller.selectedEvent.value,
items: controller.events.map((event) => DropdownMenuItem(
value: event,
child: Text(event.eventName),
)).toList(),
onChanged: (value) => controller.updateEvent(value),
),
DropdownButtonFormField(
decoration: InputDecoration(labelText: 'チーム'),
value: controller.selectedTeam.value,
items: controller.teams.map((team) => DropdownMenuItem(
value: team,
child: Text(team.teamName),
)).toList(),
onChanged: (value) => controller.updateTeam(value),
),
DropdownButtonFormField(
decoration: InputDecoration(labelText: 'カテゴリ'),
value: controller.selectedCategory.value,
items: controller.categories.map((category) => DropdownMenuItem(
value: category,
child: Text(category.categoryName),
)).toList(),
onChanged: (value) => controller.updateCategory(value),
),
// 日付選択ウィジェットを追加
if (mode == 'edit' && controller.isOwner())
ElevatedButton(
child: Text('エントリーを削除'),
onPressed: () => controller.deleteEntry(),
),
],
),
),
)
);
}
}

View File

@ -0,0 +1,35 @@
// lib/pages/entry/entry_list_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/entry/entry_controller.dart';
import 'package:rogapp/routes/app_pages.dart';
class EntryListPage extends GetView<EntryController> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('エントリー管理'),
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () => Get.toNamed(AppPages.ENTRY_DETAIL, arguments: {'mode': 'new'}),
),
],
),
body: Obx(() => ListView.builder(
itemCount: controller.entries.length,
itemBuilder: (context, index) {
final entry = controller.entries[index];
return ListTile(
title: Text(entry.event?.eventName ?? 'イベント未設定'),
subtitle: Text('${entry.team?.teamName ?? 'チーム未設定'} - ${entry.category?.categoryName ?? 'カテゴリ未設定'}'),
trailing: Text(entry.date?.toString().substring(0, 10) ?? '日付未設定'),
onTap: () => Get.toNamed(AppPages.ENTRY_DETAIL, arguments: {'mode': 'edit', 'entry': entry}),
);
},
)),
);
}
}

View File

@ -18,6 +18,8 @@ import 'package:rogapp/utils/database_helper.dart';
import 'package:rogapp/widgets/debug_widget.dart'; import 'package:rogapp/widgets/debug_widget.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:rogapp/services/api_service.dart';
import '../../main.dart'; import '../../main.dart';
@ -190,11 +192,19 @@ class IndexController extends GetxController with WidgetsBindingObserver {
WidgetsBinding.instance?.addObserver(this); WidgetsBinding.instance?.addObserver(this);
_startLocationService(); // アプリ起動時にLocationServiceを開始する _startLocationService(); // アプリ起動時にLocationServiceを開始する
initializeApiService();
print('IndexController onInit called'); // デバッグ用の出力を追加 print('IndexController onInit called'); // デバッグ用の出力を追加
} }
Future<void> initializeApiService() async {
if (currentUser.isNotEmpty) {
// 既にログインしている場合
await Get.putAsync(() => ApiService().init());
// 必要に応じて追加の初期化処理
}
}
/* /*
void checkPermission() void checkPermission()
@ -314,9 +324,9 @@ class IndexController extends GetxController with WidgetsBindingObserver {
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。 // 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
// //
void login(String email, String password, BuildContext context) { void login(String email, String password, BuildContext context) async{
AuthService.login(email, password).then((value) { AuthService.login(email, password).then((value) async {
print("------- logged in user details ######## $value ###### --------"); print("------- logged in user details ######## $value ###### --------");
if (value.isNotEmpty) { if (value.isNotEmpty) {
logManager.addOperationLog("User logged in : ${value}."); logManager.addOperationLog("User logged in : ${value}.");
@ -324,6 +334,11 @@ class IndexController extends GetxController with WidgetsBindingObserver {
// Navigator.pop(context); // Navigator.pop(context);
print("--------- user details login ----- $value"); print("--------- user details login ----- $value");
changeUser(value); changeUser(value);
// ログイン成功後、api_serviceを初期化
await Get.putAsync(() => ApiService().init());
} else { } else {
logManager.addOperationLog("User failed login : ${email} , ${password}."); logManager.addOperationLog("User failed login : ${email} , ${password}.");
isLoading.value = false; isLoading.value = false;

View File

@ -0,0 +1,11 @@
import 'package:get/get.dart';
import 'package:rogapp/pages/team/member_controller.dart';
import 'package:rogapp/services/api_service.dart';
class MemberBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<ApiService>(() => ApiService());
Get.lazyPut<MemberController>(() => MemberController());
}
}

View File

@ -0,0 +1,105 @@
// lib/controllers/member_controller.dart
import 'package:get/get.dart';
import 'package:rogapp/model/user.dart';
import 'package:rogapp/services/api_service.dart';
class MemberController extends GetxController {
late final ApiService _apiService;
final int teamId = 0;
final member = Rx<User?>(null);
final email = ''.obs;
final firstname = ''.obs;
final lastname = ''.obs;
final dateOfBirth = Rx<DateTime?>(null);
@override
void onInit() async{
super.onInit();
await Get.putAsync(() => ApiService().init());
_apiService = Get.find<ApiService>();
if (Get.arguments != null && Get.arguments['member'] != null) {
member.value = Get.arguments['member'];
_initializeMemberData();
}
}
void _initializeMemberData() {
if (member.value != null) {
email.value = member.value!.email ?? '';
firstname.value = member.value!.firstname ?? '';
lastname.value = member.value!.lastname ?? '';
dateOfBirth.value = member.value!.dateOfBirth;
}
}
Future<void> createMember(int teamId) async {
try {
final newMember = await _apiService.createTeamMember(
teamId,
email.value,
firstname.value,
lastname.value,
dateOfBirth.value,
);
member.value = newMember;
} catch (e) {
print('Error creating member: $e');
}
}
Future<void> updateMember() async {
if (member.value == null) return;
int? memberId = member.value?.id;
try {
final updatedMember = await _apiService.updateTeamMember(
teamId,
memberId!,
firstname.value,
lastname.value,
dateOfBirth.value,
);
member.value = updatedMember;
} catch (e) {
print('Error updating member: $e');
}
}
Future<void> deleteMember() async {
if (member.value == null) return;
int? memberId = member.value?.id;
try {
await _apiService.deleteTeamMember(teamId,memberId!);
member.value = null;
Get.back();
} catch (e) {
print('Error deleting member: $e');
}
}
Future<void> resendInvitation() async {
if (member.value == null || member.value!.email == null) return;
int? memberId = member.value?.id;
try {
await _apiService.resendMemberInvitation(memberId!);
Get.snackbar('Success', 'Invitation resent successfully');
} catch (e) {
print('Error resending invitation: $e');
Get.snackbar('Error', 'Failed to resend invitation');
}
}
void updateEmail(String value) => email.value = value;
void updateFirstname(String value) => firstname.value = value;
void updateLastname(String value) => lastname.value = value;
void updateDateOfBirth(DateTime value) => dateOfBirth.value = value;
String getMemberStatus() {
if (member.value == null) return '';
if (member.value!.email == null) return '未登録';
if (member.value!.isActive) return '承認済';
return '招待中';
}
}

View File

@ -0,0 +1,55 @@
// lib/pages/team/member_detail_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/team/member_controller.dart';
class MemberDetailPage extends GetView<MemberController> {
@override
Widget build(BuildContext context) {
final mode = Get.arguments['mode'];
final member = Get.arguments['member'];
return Scaffold(
appBar: AppBar(
title: Text(mode == 'new' ? 'メンバー追加' : 'メンバー詳細'),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (mode == 'edit' && member.email != null)
Text('Email: ${member.email}'),
if (mode == 'new' || (mode == 'edit' && member.email == null))
TextField(
decoration: InputDecoration(labelText: 'Email'),
onChanged: (value) => controller.updateEmail(value),
),
TextField(
decoration: InputDecoration(labelText: ''),
onChanged: (value) => controller.updateLastname(value),
),
TextField(
decoration: InputDecoration(labelText: ''),
onChanged: (value) => controller.updateFirstname(value),
),
// 誕生日選択ウィジェットを追加
if (mode == 'edit')
Text('ステータス: ${controller.getMemberStatus()}'),
if (mode == 'edit' && controller.getMemberStatus() == '招待中')
ElevatedButton(
child: Text('招待メールを再送信'),
onPressed: () => controller.resendInvitation(),
),
if (mode == 'edit')
ElevatedButton(
child: Text('メンバーから除外'),
onPressed: () => controller.deleteMember(),
),
],
),
),
);
}
}

View File

@ -0,0 +1,11 @@
import 'package:get/get.dart';
import 'package:rogapp/pages/team/team_controller.dart';
import 'package:rogapp/services/api_service.dart';
class TeamBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<ApiService>(() => ApiService());
Get.lazyPut<TeamController>(() => TeamController());
}
}

View File

@ -0,0 +1,117 @@
// lib/controllers/team_controller.dart
import 'package:get/get.dart';
import 'package:rogapp/model/team.dart';
import 'package:rogapp/model/category.dart';
import 'package:rogapp/model/user.dart';
import 'package:rogapp/services/api_service.dart';
class TeamController extends GetxController {
late final ApiService _apiService;
final teams = <Team>[].obs;
final categories = <NewCategory>[].obs;
final teamMembers = <User>[].obs;
final selectedCategory = Rx<NewCategory?>(null);
final currentUser = Rx<User?>(null);
final isLoading = true.obs;
final error = RxString('');
@override
void onInit() async{
super.onInit();
try {
//await Get.putAsync(() => ApiService().init());
_apiService = Get.find<ApiService>();
await Future.wait([
fetchTeams(),
fetchCategories(),
getCurrentUser(),
]);
}catch(e){
print("Team Controller error: $e");
}finally{
isLoading.value = false;
}
}
Future<void> fetchTeams() async {
try {
final fetchedTeams = await _apiService.getTeams();
teams.assignAll(fetchedTeams);
} catch (e) {
print('Error fetching teams: $e');
}
}
Future<void> fetchCategories() async {
try {
final fetchedCategories = await _apiService.getCategories();
categories.assignAll(fetchedCategories);
} catch (e) {
print('Error fetching categories: $e');
}
}
Future<void> getCurrentUser() async {
try {
final user = await _apiService.getCurrentUser();
currentUser.value = user;
} catch (e) {
print('Error getting current user: $e');
}
}
Future<void> createTeam(String teamName, int categoryId) async {
try {
final newTeam = await _apiService.createTeam(teamName, categoryId);
teams.add(newTeam);
} catch (e) {
print('Error creating team: $e');
}
}
Future<void> updateTeam(int teamId, String teamName, int categoryId) async {
try {
final updatedTeam = await _apiService.updateTeam(teamId, teamName, categoryId);
final index = teams.indexWhere((team) => team.id == teamId);
if (index != -1) {
teams[index] = updatedTeam;
}
} catch (e) {
print('Error updating team: $e');
}
}
Future<void> deleteTeam(int teamId) async {
try {
await _apiService.deleteTeam(teamId);
teams.removeWhere((team) => team.id == teamId);
} catch (e) {
print('Error deleting team: $e');
}
}
Future<void> fetchTeamMembers(int teamId) async {
try {
final members = await _apiService.getTeamMembers(teamId);
teamMembers.assignAll(members);
} catch (e) {
print('Error fetching team members: $e');
}
}
void updateTeamName(String value) {
// Update local state
}
void updateCategory(NewCategory? value) {
selectedCategory.value = value;
}
}

View File

@ -0,0 +1,69 @@
// lib/pages/team/team_detail_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/team/team_controller.dart';
import 'package:rogapp/routes/app_pages.dart';
import 'package:rogapp/services/api_service.dart';
class TeamDetailPage extends GetView<TeamController> {
@override
Widget build(BuildContext context) {
final mode = Get.arguments['mode'];
final team = Get.arguments['team'];
return Scaffold(
appBar: AppBar(
title: Text(mode == 'new' ? '新規チーム作成' : 'チーム詳細'),
actions: [
if (mode == 'edit')
IconButton(
icon: Icon(Icons.add),
onPressed: () => Get.toNamed(AppPages.MEMBER_DETAIL, arguments: {'mode': 'new', 'teamId': team.id}),
),
],
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
decoration: InputDecoration(labelText: 'チーム名'),
onChanged: (value) => controller.updateTeamName(value),
),
DropdownButtonFormField(
decoration: InputDecoration(labelText: 'カテゴリ'),
value: controller.selectedCategory.value,
items: controller.categories.map((category) => DropdownMenuItem(
value: category,
child: Text(category.categoryName),
)).toList(),
onChanged: (value) => controller.updateCategory(value),
),
if (mode == 'edit')
Text('ゼッケン番号: ${team.zekkenNumber}'),
Obx(() {
final currentUser = controller.currentUser.value;
return Text('所有者: ${currentUser?.email ?? "未設定"}');
}),
SizedBox(height: 20),
Text('メンバーリスト', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Expanded(
child: Obx(() => ListView.builder(
itemCount: controller.teamMembers.length,
itemBuilder: (context, index) {
final member = controller.teamMembers[index];
return ListTile(
title: Text('${member.lastname}, ${member.firstname}'),
onTap: () => Get.toNamed(AppPages.MEMBER_DETAIL, arguments: {'mode': 'edit', 'member': member}),
);
},
)),
),
],
),
),
);
}
}

View File

@ -0,0 +1,54 @@
// lib/pages/team/team_list_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/team/team_controller.dart';
import 'package:rogapp/routes/app_pages.dart';
import 'package:rogapp/services/api_service.dart';
class TeamListPage extends GetView<TeamController> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('チーム管理'),
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () => Get.toNamed(AppPages.TEAM_DETAIL, arguments: {'mode': 'new'}),
),
],
),
body: FutureBuilder(
future: Get.putAsync(() => ApiService().init()),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Obx(() {
if (controller.isLoading.value) {
return Center(child: CircularProgressIndicator());
} else if (controller.error.value.isNotEmpty) {
return Center(child: Text('エラー: ${controller.error.value}'));
} else {
return ListView.builder(
itemCount: controller.teams.length,
itemBuilder: (context, index) {
final team = controller.teams[index];
return ListTile(
title: Text(team.teamName),
subtitle: Text('${team.category.categoryName} - ${team.zekkenNumber}'),
trailing: Text('メンバー: (メンバー数表示予定)'),
onTap: () => Get.toNamed(AppPages.TEAM_DETAIL, arguments: {'mode': 'edit', 'team': team}),
);
},
);
}
});
} else {
return Center(child: CircularProgressIndicator());
}
},
),
);
}
}

View File

@ -28,6 +28,15 @@ import 'package:rogapp/spa/spa_binding.dart';
import 'package:rogapp/spa/spa_page.dart'; import 'package:rogapp/spa/spa_page.dart';
import 'package:rogapp/widgets/permission_handler_screen.dart'; import 'package:rogapp/widgets/permission_handler_screen.dart';
import 'package:rogapp/pages/team/team_binding.dart';
import 'package:rogapp/pages/team/team_list_page.dart';
import 'package:rogapp/pages/team/team_detail_page.dart';
import 'package:rogapp/pages/team/member_binding.dart';
import 'package:rogapp/pages/team/member_detail_page.dart';
import 'package:rogapp/pages/entry/entry_list_page.dart';
import 'package:rogapp/pages/entry/entry_detail_page.dart';
import 'package:rogapp/pages/entry/entry_binding.dart';
part 'app_routes.dart'; part 'app_routes.dart';
class AppPages { class AppPages {
@ -56,6 +65,11 @@ class AppPages {
static const GPS = Routes.GPS; static const GPS = Routes.GPS;
static const SETTINGS = Routes.SETTINGS; static const SETTINGS = Routes.SETTINGS;
static const DEBUG = Routes.DEBUG; static const DEBUG = Routes.DEBUG;
static const TEAM_LIST = Routes.TEAM_LIST;
static const TEAM_DETAIL = Routes.TEAM_DETAIL;
static const MEMBER_DETAIL = Routes.MEMBER_DETAIL;
static const ENTRY_LIST = Routes.ENTRY_LIST;
static const ENTRY_DETAIL = Routes.ENTRY_DETAIL;
static final routes = [ static final routes = [
GetPage( GetPage(
@ -127,6 +141,31 @@ class AppPages {
page: () => DebugPage(), page: () => DebugPage(),
binding: DebugBinding(), binding: DebugBinding(),
), ),
GetPage(
name: Routes.TEAM_LIST,
page: () => TeamListPage(),
binding: TeamBinding(),
),
GetPage(
name: Routes.TEAM_DETAIL,
page: () => TeamDetailPage(),
binding: TeamBinding(),
),
GetPage(
name: Routes.MEMBER_DETAIL,
page: () => MemberDetailPage(),
binding: MemberBinding(),
),
GetPage(
name: Routes.ENTRY_LIST,
page: () => EntryListPage(),
binding: EntryBinding(),
),
GetPage(
name: Routes.ENTRY_DETAIL,
page: () => EntryDetailPage(),
binding: EntryBinding(),
),
]; ];

View File

@ -28,4 +28,10 @@ abstract class Routes {
static const GPS = '/gp'; static const GPS = '/gp';
static const SETTINGS = '/settings'; static const SETTINGS = '/settings';
static const DEBUG = '/debug'; static const DEBUG = '/debug';
static const TEAM_LIST = '/team-list';
static const TEAM_DETAIL = '/team-detail';
static const MEMBER_DETAIL = '/member-detail';
static const ENTRY_LIST = '/entry-list';
static const ENTRY_DETAIL = '/entry-detail';
} }

View File

@ -0,0 +1,474 @@
// lib/services/api_service.dart
import 'package:get/get.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
import 'package:rogapp/model/entry.dart';
import 'package:rogapp/model/event.dart';
import 'package:rogapp/model/team.dart';
import 'package:rogapp/model/category.dart';
import 'package:rogapp/model/user.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import '../utils/const.dart';
class ApiService extends GetxService{
static ApiService get to => Get.find<ApiService>();
String serverUrl = '';
String baseUrl = '';
String token = 'your-auth-token'; // これが使用されている。
Future<void> init() async {
try {
// ここで必要な初期化処理を行う
serverUrl = ConstValues.currentServer();
baseUrl = '$serverUrl/api';
//await Future.delayed(Duration(seconds: 2)); // 仮の遅延(実際の初期化処理に置き換えてください)
print('ApiService initialized successfully');
} catch(e) {
print('Error in ApiService initialization: $e');
rethrow; // エラーを再スローして、呼び出し元で処理できるようにする
}
}
/*
このメソッドは以下のように動作します:
まず、渡された type パラメータに基づいて、どのクラスのフィールドを扱っているかを判断します。
次に、クラス内で fieldName に対応する期待される型を返します。
クラスや フィールド名が予期されていないものである場合、'Unknown' または 'Unknown Type' を返します。
このメソッドを ApiService クラスに追加することで、_printDataComparison メソッドは各フィールドの期待される型を正確に表示できるようになります。
さらに、このメソッドを使用することで、API レスポンスのデータ型が期待と異なる場合に簡単に検出できるようになります。例えば、Category クラスの duration フィールドが整数型秒数で期待されているのに対し、API が文字列を返した場合、すぐに問題を特定できます。
注意点として、API のレスポンス形式が変更された場合や、新しいフィールドが追加された場合は、このメソッドも更新する必要があります。そのため、API の変更とクライアントサイドのコードの同期を保つことが重要です。
*/
String getToken()
{
// IndexControllerの初期化を待つ
final indexController = Get.find<IndexController>();
if (indexController.currentUser.isNotEmpty) {
token = indexController.currentUser[0]['token'] ?? '';
print("Get token = $token");
}else{
token = "";
}
return token;
}
Future<List<Team>> getTeams() async {
init();
getToken();
try {
final response = await http.get(
Uri.parse('$baseUrl/teams/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode == 200) {
// UTF-8でデコード
final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse');
List<dynamic> teamsJson = json.decode(decodedResponse);
List<Team> teams = [];
for (var teamJson in teamsJson) {
print('\nTeam Data:');
_printDataComparison(teamJson, Team);
teams.add(Team.fromJson(teamJson));
}
return teams;
} else {
throw Exception('Failed to load teams. Status code: ${response.statusCode}');
}
} catch (e, stackTrace) {
print('Error in getTeams: $e');
print('Stack trace: $stackTrace');
rethrow;
}
}
Future<List<NewCategory>> getCategories() async {
init();
getToken();
try {
final response = await http.get(
Uri.parse('$baseUrl/categories/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse');
List<dynamic> categoriesJson = json.decode(decodedResponse);
List<NewCategory> categories = [];
for (var categoryJson in categoriesJson) {
print('\nCategory Data:');
_printDataComparison(categoryJson, NewCategory);
categories.add(NewCategory.fromJson(categoryJson));
}
return categories;
} else {
throw Exception(
'Failed to load categories. Status code: ${response.statusCode}');
}
}catch(e, stackTrace){
print('Error in getCategories: $e');
print('Stack trace: $stackTrace');
rethrow;
}
}
Future<User> getCurrentUser() async {
init();
getToken();
try {
final response = await http.get(
Uri.parse('$baseUrl/user/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse');
final jsonData = json.decode(decodedResponse);
print('\nUser Data Comparison:');
_printDataComparison(jsonData, User);
return User.fromJson(jsonData);
} else {
throw Exception('Failed to get current user. Status code: ${response.statusCode}');
}
} catch (e, stackTrace) {
print('Error in getCurrentUser: $e');
print('Stack trace: $stackTrace');
rethrow;
}
}
void _printDataComparison(Map<String, dynamic> data, Type expectedType) {
print('Field\t\t| Expected Type\t| Actual Type\t| Actual Value');
print('------------------------------------------------------------');
data.forEach((key, value) {
String expectedFieldType = _getExpectedFieldType(expectedType, key);
_printComparison(key, expectedFieldType, value);
});
}
String _getExpectedFieldType(Type type, String fieldName) {
// This method should return the expected type for each field based on the class definition
// You might need to implement this based on your class structures
switch (type) {
case NewCategory:
switch (fieldName) {
case 'id': return 'int';
case 'category_name': return 'String';
case 'category_number': return 'int';
case 'duration': return 'int (seconds)';
case 'num_of_member': return 'int';
case 'family': return 'bool';
case 'female': return 'bool';
default: return 'Unknown';
}
case Team:
switch (fieldName) {
case 'id': return 'int';
case 'zekken_number': return 'String';
case 'team_name': return 'String';
case 'category': return 'NewCategory (Object)';
case 'owner': return 'User (Object)';
default: return 'Unknown';
}
case User:
switch (fieldName) {
case 'id': return 'int';
case 'email': return 'String';
case 'firstname': return 'String';
case 'lastname': return 'String';
case 'date_of_birth': return 'String (ISO8601)';
case 'female': return 'bool';
case 'is_active': return 'bool';
default: return 'Unknown';
}
default:
return 'Unknown Type';
}
}
void _printComparison(String fieldName, String expectedType, dynamic actualValue) {
String actualType = actualValue?.runtimeType.toString() ?? 'null';
String displayValue = actualValue.toString();
if (displayValue.length > 50) {
displayValue = '${displayValue.substring(0, 47)}...';
}
print('$fieldName\t\t| $expectedType\t\t| $actualType\t\t| $displayValue');
}
Future<Team> createTeam(String teamName, int categoryId) async {
init();
getToken();
final response = await http.post(
Uri.parse('$baseUrl/teams/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json',
},
body: json.encode({
'team_name': teamName,
'category': categoryId,
}),
);
if (response.statusCode == 201) {
return Team.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to create team');
}
}
Future<Team> updateTeam(int teamId, String teamName, int categoryId) async {
init();
getToken();
final response = await http.put(
Uri.parse('$baseUrl/teams/$teamId/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json',
},
body: json.encode({
'team_name': teamName,
'category': categoryId,
}),
);
if (response.statusCode == 200) {
return Team.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to update team');
}
}
Future<void> deleteTeam(int teamId) async {
init();
getToken();
final response = await http.delete(
Uri.parse('$baseUrl/teams/$teamId/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode != 204) {
throw Exception('Failed to delete team');
}
}
Future<List<User>> getTeamMembers(int teamId) async {
init();
getToken();
final response = await http.get(
Uri.parse('$baseUrl/teams/$teamId/members/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse');
List<dynamic> membersJson = json.decode(decodedResponse);
return membersJson.map((json) => User.fromJson(json)).toList();
} else {
throw Exception('Failed to load team members');
}
}
Future<User> createTeamMember(int teamId, String email, String firstname, String lastname, DateTime? dateOfBirth) async {
init();
getToken();
final response = await http.post(
Uri.parse('$baseUrl/teams/$teamId/members/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json',
},
body: json.encode({
'email': email,
'firstname': firstname,
'lastname': lastname,
'date_of_birth': dateOfBirth?.toIso8601String(),
}),
);
if (response.statusCode == 201) {
return User.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to create team member');
}
}
Future<User> updateTeamMember(int teamId,int memberId, String firstname, String lastname, DateTime? dateOfBirth) async {
init();
getToken();
final response = await http.put(
Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json',
},
body: json.encode({
'firstname': firstname,
'lastname': lastname,
'date_of_birth': dateOfBirth?.toIso8601String(),
}),
);
if (response.statusCode == 200) {
return User.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to update team member');
}
}
Future<void> deleteTeamMember(int teamId,int memberId) async {
init();
getToken();
final response = await http.delete(
Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode != 204) {
throw Exception('Failed to delete team member');
}
}
Future<void> resendMemberInvitation(int memberId) async {
init();
getToken();
final response = await http.post(
Uri.parse('$baseUrl/members/$memberId/resend-invitation/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode != 200) {
throw Exception('Failed to resend invitation');
}
}
Future<List<Entry>> getEntries() async {
init();
getToken();
final response = await http.get(
Uri.parse('$baseUrl/entries/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode == 200) {
List<dynamic> entriesJson = json.decode(response.body);
return entriesJson.map((json) => Entry.fromJson(json)).toList();
} else {
throw Exception('Failed to load entries');
}
}
Future<List<Event>> getEvents() async {
init();
getToken();
final response = await http.get(
Uri.parse('$baseUrl/new-events/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
print('Response body: $decodedResponse');
List<dynamic> eventsJson = json.decode(decodedResponse);
return eventsJson.map((json) => Event.fromJson(json)).toList();
} else {
throw Exception('Failed to load events');
}
}
Future<Entry> createEntry(int teamId, int eventId, int categoryId, DateTime date) async {
init();
getToken();
final response = await http.post(
Uri.parse('$baseUrl/entry/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json',
},
body: json.encode({
'team': teamId,
'event': eventId,
'category': categoryId,
'date': date.toIso8601String(),
}),
);
if (response.statusCode == 201) {
return Entry.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to create entry');
}
}
Future<Entry> updateEntry(int entryId, int eventId, int categoryId, DateTime date) async {
init();
getToken();
final response = await http.put(
Uri.parse('$baseUrl/entry/$entryId/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json',
},
body: json.encode({
'event': eventId,
'category': categoryId,
'date': date.toIso8601String(),
}),
);
if (response.statusCode == 200) {
return Entry.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to update entry');
}
}
Future<void> deleteEntry(int entryId) async {
init();
getToken();
final response = await http.delete(
Uri.parse('$baseUrl/entry/$entryId/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode != 204) {
throw Exception('Failed to delete entry');
}
}
}

View File

@ -3,7 +3,7 @@
class ConstValues{ class ConstValues{
static const container_svr = "http://container.intranet.sumasen.net:8100"; static const container_svr = "http://container.intranet.sumasen.net:8100";
static const server_uri = "https://rogaining.sumasen.net"; static const server_uri = "https://rogaining.intranet.sumasen.net";
static const dev_server = "http://localhost:8100"; static const dev_server = "http://localhost:8100";
static const dev_ip_server = "http://192.168.8.100:8100"; static const dev_ip_server = "http://192.168.8.100:8100";
static const dev_home_ip_server = "http://172.20.10.9:8100"; static const dev_home_ip_server = "http://172.20.10.9:8100";