ほぼ完成:QRcodeトライ
This commit is contained in:
@ -85,121 +85,153 @@ class _MemberDetailPageState extends State<MemberDetailPage> {
|
||||
home:Scaffold(
|
||||
*/
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(mode == 'new' ? 'メンバー追加' : 'メンバー詳細'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.save),
|
||||
onPressed: () async {
|
||||
await controller.saveMember();
|
||||
Get.back(result: true);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (mode == 'new')
|
||||
TextField(
|
||||
decoration: InputDecoration(labelText: 'メールアドレス'),
|
||||
onChanged: (value) => controller.email.value = value,
|
||||
controller: TextEditingController(text: controller.email.value),
|
||||
)
|
||||
else if (controller.isDummyEmail)
|
||||
Text('メールアドレス: (メアド無し)')
|
||||
else
|
||||
Text('メールアドレス: ${controller.email.value}'),
|
||||
|
||||
if (controller.email.value.isEmpty || mode == 'edit') ...[
|
||||
TextField(
|
||||
decoration: InputDecoration(labelText: '姓'),
|
||||
onChanged: (value) => controller.lastname.value = value,
|
||||
controller: TextEditingController(text: controller.lastname.value),
|
||||
),
|
||||
TextField(
|
||||
decoration: InputDecoration(labelText: '名'),
|
||||
onChanged: (value) => controller.firstname.value = value,
|
||||
controller: TextEditingController(text: controller.firstname.value),
|
||||
),
|
||||
// 生年月日
|
||||
if (controller.isDummyEmail || !controller.isOver18())
|
||||
ListTile(
|
||||
title: Text('生年月日'),
|
||||
subtitle: Text(controller.dateOfBirth.value != null
|
||||
? '${DateFormat('yyyy年MM月dd日').format(controller.dateOfBirth.value!)} (${controller.getAgeAndGrade()})'
|
||||
: '未設定'),
|
||||
onTap: () async {
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: controller.dateOfBirth.value ?? DateTime.now(),
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime.now(),
|
||||
//locale: const Locale('ja', 'JP'),
|
||||
);
|
||||
if (date != null) controller.dateOfBirth.value = date;
|
||||
},
|
||||
)
|
||||
else
|
||||
Text('18歳以上'),
|
||||
|
||||
SwitchListTile(
|
||||
title: Text('性別'),
|
||||
subtitle: Text(controller.female.value ? '女性' : '男性'),
|
||||
value: controller.female.value,
|
||||
onChanged: (value) => controller.female.value = value,
|
||||
),
|
||||
],
|
||||
// 招待メール再送信ボタン(通常のEmailで未承認の場合のみ)
|
||||
if (!controller.isDummyEmail && !controller.isApproved)
|
||||
ElevatedButton(
|
||||
child: Text('招待メールを再送信'),
|
||||
onPressed: () => controller.resendInvitation(),
|
||||
),
|
||||
|
||||
// メンバー削除ボタン
|
||||
ElevatedButton(
|
||||
child: Text('メンバーから削除'),
|
||||
onPressed: () async {
|
||||
final confirmed = await Get.dialog<bool>(
|
||||
AlertDialog(
|
||||
title: Text('確認'),
|
||||
content: Text('このメンバーを削除してもよろしいですか?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('キャンセル'),
|
||||
onPressed: () => Get.back(result: false),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('削除'),
|
||||
onPressed: () => Get.back(result: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == true) {
|
||||
await controller.deleteMember();
|
||||
Get.back(result: true);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
|
||||
}),
|
||||
appBar: AppBar(
|
||||
title: Text(mode == 'new' ? 'メンバー追加' : 'メンバー詳細'),
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
// TextEditingControllerをObxの中で作成
|
||||
final emailController = TextEditingController(text: controller.email.value);
|
||||
// カーソル位置を保持
|
||||
emailController.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: controller.email.value.length),
|
||||
);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (mode == 'new')
|
||||
TextFormField(
|
||||
decoration: InputDecoration(labelText: 'メールアドレス'),
|
||||
//onChanged: (value) => controller.email.value = value,
|
||||
onChanged: (value) {
|
||||
controller.email.value = value;
|
||||
// カーソル位置を更新
|
||||
emailController.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: value.length),
|
||||
);
|
||||
},
|
||||
controller: emailController,
|
||||
//controller: TextEditingController(text: controller.email.value),
|
||||
keyboardType: TextInputType.emailAddress, // メールアドレス用のキーボードを表示
|
||||
autocorrect: false, // 自動修正を無効化
|
||||
enableSuggestions: false,
|
||||
)
|
||||
else if (controller.isDummyEmail)
|
||||
Text('メールアドレス: (メアド無し)')
|
||||
else
|
||||
Text('メールアドレス: ${controller.email.value}'),
|
||||
|
||||
if (controller.email.value.isEmpty || mode == 'edit') ...[
|
||||
TextFormField(
|
||||
decoration: InputDecoration(labelText: '姓'),
|
||||
onChanged: (value) => controller.lastname.value = value,
|
||||
controller: TextEditingController(text: controller.lastname.value),
|
||||
),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(labelText: '名'),
|
||||
onChanged: (value) => controller.firstname.value = value,
|
||||
controller: TextEditingController(text: controller.firstname.value),
|
||||
),
|
||||
// 生年月日
|
||||
if (controller.isDummyEmail || !controller.isOver18())
|
||||
ListTile(
|
||||
title: Text('生年月日'),
|
||||
subtitle: Text(controller.dateOfBirth.value != null
|
||||
? '${DateFormat('yyyy年MM月dd日').format(controller.dateOfBirth.value!)} (${controller.getAgeAndGrade()})'
|
||||
: '未設定'),
|
||||
onTap: () async {
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: controller.dateOfBirth.value ?? DateTime.now(),
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime.now(),
|
||||
//locale: const Locale('ja', 'JP'),
|
||||
);
|
||||
if (date != null) controller.dateOfBirth.value = date;
|
||||
},
|
||||
)
|
||||
else
|
||||
Text('18歳以上'),
|
||||
|
||||
SwitchListTile(
|
||||
title: Text('性別'),
|
||||
subtitle: Text(controller.female.value ? '女性' : '男性'),
|
||||
value: controller.female.value,
|
||||
onChanged: (value) => controller.female.value = value,
|
||||
),
|
||||
],
|
||||
]),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
child: Text('削除'),
|
||||
onPressed: () async {
|
||||
final confirmed = await Get.dialog<bool>(
|
||||
AlertDialog(
|
||||
title: Text('確認'),
|
||||
content: Text('このメンバーを削除してもよろしいですか?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('キャンセル'),
|
||||
onPressed: () => Get.back(result: false),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('削除'),
|
||||
onPressed: () => Get.back(result: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == true) {
|
||||
await controller.deleteMember();
|
||||
Get.back(result: true);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
if (!controller.isDummyEmail && !controller.isApproved)
|
||||
ElevatedButton(
|
||||
child: Text('招待再送信'),
|
||||
onPressed: () => controller.resendInvitation(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
child: Text('保存・招待'),
|
||||
onPressed: () async {
|
||||
await controller.saveMember();
|
||||
Get.back(result: true);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// lib/controllers/team_controller.dart
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/model/team.dart';
|
||||
import 'package:rogapp/model/category.dart';
|
||||
@ -27,6 +28,7 @@ class TeamController extends GetxController {
|
||||
super.onInit();
|
||||
try {
|
||||
_apiService = Get.find<ApiService>();
|
||||
isLoading.value = true;
|
||||
|
||||
await fetchCategories();
|
||||
await Future.wait([
|
||||
@ -67,8 +69,13 @@ class TeamController extends GetxController {
|
||||
void resetForm() {
|
||||
selectedTeam.value = null;
|
||||
teamName.value = '';
|
||||
selectedCategory.value = categories.isNotEmpty ? categories.first : null;
|
||||
teamMembers.clear();
|
||||
if (categories.isNotEmpty) {
|
||||
selectedCategory.value = categories.first;
|
||||
} else {
|
||||
selectedCategory.value = null;
|
||||
// カテゴリが空の場合、エラーメッセージをセット
|
||||
error.value = 'カテゴリデータが取得できませんでした。';
|
||||
} teamMembers.clear();
|
||||
}
|
||||
|
||||
void cleanupForNavigation() {
|
||||
@ -93,6 +100,14 @@ class TeamController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
bool checkIfUserHasEntryData(){
|
||||
if (teams.isEmpty) {
|
||||
return false;
|
||||
}else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchCategories() async {
|
||||
try {
|
||||
final fetchedCategories = await _apiService.getCategories();
|
||||
@ -115,6 +130,17 @@ class TeamController extends GetxController {
|
||||
|
||||
Future<Team> createTeam(String teamName, int categoryId) async {
|
||||
final newTeam = await _apiService.createTeam(teamName, categoryId);
|
||||
|
||||
// 自分自身をメンバーとして自動登録
|
||||
await _apiService.createTeamMember(
|
||||
newTeam.id,
|
||||
currentUser.value?.email,
|
||||
currentUser.value!.firstname,
|
||||
currentUser.value!.lastname,
|
||||
currentUser.value?.dateOfBirth,
|
||||
currentUser.value?.female,
|
||||
);
|
||||
|
||||
return newTeam;
|
||||
}
|
||||
|
||||
@ -125,11 +151,51 @@ class TeamController extends GetxController {
|
||||
}
|
||||
|
||||
Future<void> deleteTeam(int teamId) async {
|
||||
try {
|
||||
await _apiService.deleteTeam(teamId);
|
||||
teams.removeWhere((team) => team.id == teamId);
|
||||
} catch (e) {
|
||||
print('Error deleting team: $e');
|
||||
bool confirmDelete = await Get.dialog(
|
||||
AlertDialog(
|
||||
title: Text('チーム削除の確認'),
|
||||
content: Text('このチームとそのすべてのメンバーを削除しますか?この操作は取り消せません。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('キャンセル'),
|
||||
onPressed: () => Get.back(result: false),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('削除'),
|
||||
onPressed: () => Get.back(result: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
) ?? false;
|
||||
|
||||
if (confirmDelete) {
|
||||
try {
|
||||
// まず、チームのメンバーを全て削除
|
||||
await _apiService.deleteAllTeamMembers(teamId);
|
||||
|
||||
// その後、チームを削除
|
||||
await _apiService.deleteTeam(teamId);
|
||||
|
||||
// ローカルのチームリストを更新
|
||||
teams.removeWhere((team) => team.id == teamId);
|
||||
|
||||
/*
|
||||
Get.snackbar(
|
||||
'成功',
|
||||
'チームとそのメンバーが削除されました',
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
*/
|
||||
|
||||
// チームリスト画面に戻る
|
||||
Get.back();
|
||||
|
||||
} catch (e) {
|
||||
print('Error deleting team and members: $e');
|
||||
Get.snackbar('エラー', 'チームとメンバーの削除に失敗しました');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,4 +303,30 @@ class TeamController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
List<NewCategory> getFilteredCategories() {
|
||||
//List<User> teamMembers = getCurrentTeamMembers();
|
||||
return categories.where((category) {
|
||||
return isCategoryValid(category, teamMembers);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
bool isCategoryValid(NewCategory category, List<User> teamMembers) {
|
||||
int maleCount = teamMembers.where((member) => !member.female).length;
|
||||
int femaleCount = teamMembers.where((member) => member.female).length;
|
||||
int totalCount = teamMembers.length;
|
||||
|
||||
bool isValidGender = category.female ? (femaleCount == totalCount) : true;
|
||||
bool isValidMemberCount = totalCount == category.numOfMember;
|
||||
bool isValidFamily = category.family ? areAllMembersFamily(teamMembers) : true;
|
||||
|
||||
return isValidGender && isValidMemberCount && isValidFamily;
|
||||
}
|
||||
|
||||
bool areAllMembersFamily(List<User> teamMembers) {
|
||||
// 家族かどうかを判断するロジック(例: 同じ姓を持つメンバーが2人以上いる場合は家族とみなす)
|
||||
Set<String> familyNames = teamMembers.map((member) => member.lastname).toSet();
|
||||
return familyNames.length < teamMembers.length;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -126,15 +126,26 @@ class _TeamDetailPageState extends State<TeamDetailPage> {
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
DropdownButtonFormField<NewCategory>(
|
||||
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),
|
||||
),
|
||||
Obx(() {
|
||||
if (controller.categories.isEmpty) {
|
||||
return Text('カテゴリデータを読み込めませんでした。', style: TextStyle(color: Colors.red));
|
||||
}
|
||||
return DropdownButtonFormField<NewCategory>(
|
||||
decoration: InputDecoration(labelText: 'カテゴリ'),
|
||||
value: controller.selectedCategory.value,
|
||||
items: controller.categories.map((category) => DropdownMenuItem(
|
||||
value: category,
|
||||
child: Text(category.categoryName),
|
||||
)).toList(),
|
||||
/*
|
||||
items: controller.getFilteredCategories().map((category) => DropdownMenuItem(
|
||||
value: category,
|
||||
child: Text(category.categoryName),
|
||||
)).toList(),
|
||||
*/
|
||||
onChanged: (value) => controller.updateCategory(value),
|
||||
);
|
||||
}),
|
||||
if (mode.value == 'edit')
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
@ -160,7 +171,7 @@ class _TeamDetailPageState extends State<TeamDetailPage> {
|
||||
? '${member.lastname} ${member.firstname}'
|
||||
: member.isActive
|
||||
? '${member.lastname} ${member.firstname}'
|
||||
: '${member.email?.split('@')[0] ?? ''}(未承認)';
|
||||
: '${member.email?.split('@')[0] ?? ''}'; //(未承認)';
|
||||
return ListTile(
|
||||
title: Text(displayName),
|
||||
subtitle: isDummyEmail ? Text('Email未設定') : null,
|
||||
|
||||
Reference in New Issue
Block a user