Team,Member, Entryの登録まで完了
This commit is contained in:
@ -9,7 +9,7 @@ class Entry {
|
|||||||
final Team team;
|
final Team team;
|
||||||
final Event event;
|
final Event event;
|
||||||
final NewCategory category;
|
final NewCategory category;
|
||||||
final DateTime date;
|
final DateTime? date;
|
||||||
final String owner;
|
final String owner;
|
||||||
|
|
||||||
Entry({
|
Entry({
|
||||||
@ -27,8 +27,10 @@ class Entry {
|
|||||||
team: Team.fromJson(json['team']),
|
team: Team.fromJson(json['team']),
|
||||||
event: Event.fromJson(json['event']),
|
event: Event.fromJson(json['event']),
|
||||||
category: NewCategory.fromJson(json['category']),
|
category: NewCategory.fromJson(json['category']),
|
||||||
date: DateTime.parse(json['date']),
|
date: json['date'] != null
|
||||||
owner: json['owner'],
|
? DateTime.tryParse(json['date'])
|
||||||
|
: null,
|
||||||
|
owner: json['owner'] is Map ? json['owner']['name'] ?? '' : json['owner'] ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +40,7 @@ class Entry {
|
|||||||
'team': team.toJson(),
|
'team': team.toJson(),
|
||||||
'event': event.toJson(),
|
'event': event.toJson(),
|
||||||
'category': category.toJson(),
|
'category': category.toJson(),
|
||||||
'date': date.toIso8601String(),
|
'date': date?.toIso8601String(),
|
||||||
'owner': owner,
|
'owner': owner,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// lib/entry/entry_controller.dart
|
// lib/entry/entry_controller.dart
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:rogapp/model/entry.dart';
|
import 'package:rogapp/model/entry.dart';
|
||||||
import 'package:rogapp/model/event.dart';
|
import 'package:rogapp/model/event.dart';
|
||||||
import 'package:rogapp/model/team.dart';
|
import 'package:rogapp/model/team.dart';
|
||||||
@ -8,7 +9,7 @@ import 'package:rogapp/model/category.dart';
|
|||||||
import 'package:rogapp/services/api_service.dart';
|
import 'package:rogapp/services/api_service.dart';
|
||||||
|
|
||||||
class EntryController extends GetxController {
|
class EntryController extends GetxController {
|
||||||
late final ApiService _apiService;
|
late ApiService _apiService;
|
||||||
|
|
||||||
final entries = <Entry>[].obs;
|
final entries = <Entry>[].obs;
|
||||||
final events = <Event>[].obs;
|
final events = <Event>[].obs;
|
||||||
@ -21,14 +22,27 @@ class EntryController extends GetxController {
|
|||||||
final selectedDate = Rx<DateTime?>(null);
|
final selectedDate = Rx<DateTime?>(null);
|
||||||
|
|
||||||
final currentEntry = Rx<Entry?>(null);
|
final currentEntry = Rx<Entry?>(null);
|
||||||
|
final isLoading = true.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() async{
|
void onInit() async {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
await Get.putAsync(() => ApiService().init());
|
await initializeApiService();
|
||||||
_apiService = Get.find<ApiService>();
|
await loadInitialData();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initializeApiService() async {
|
||||||
try {
|
try {
|
||||||
|
_apiService = await Get.putAsync(() => ApiService().init());
|
||||||
|
} catch (e) {
|
||||||
|
print('Error initializing ApiService: $e');
|
||||||
|
Get.snackbar('Error', 'Failed to initialize API service');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadInitialData() async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
fetchEntries(),
|
fetchEntries(),
|
||||||
fetchEvents(),
|
fetchEvents(),
|
||||||
@ -37,14 +51,53 @@ class EntryController extends GetxController {
|
|||||||
]);
|
]);
|
||||||
if (Get.arguments != null && Get.arguments['entry'] != null) {
|
if (Get.arguments != null && Get.arguments['entry'] != null) {
|
||||||
currentEntry.value = Get.arguments['entry'];
|
currentEntry.value = Get.arguments['entry'];
|
||||||
_initializeEntryData();
|
initializeEditMode(currentEntry.value!);
|
||||||
|
} else {
|
||||||
|
// 新規作成モードの場合、最初のイベントを選択
|
||||||
|
if (events.isNotEmpty) {
|
||||||
|
selectedEvent.value = events.first;
|
||||||
|
selectedDate.value = events.first.startDatetime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Get.putAsync(() => ApiService().init());
|
} catch(e) {
|
||||||
}catch(e){
|
|
||||||
print('Error initializing data: $e');
|
print('Error initializing data: $e');
|
||||||
|
Get.snackbar('Error', 'Failed to load initial data');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void initializeEditMode(Entry entry) {
|
||||||
|
selectedEvent.value = entry.event;
|
||||||
|
selectedTeam.value = entry.team;
|
||||||
|
selectedCategory.value = entry.category;
|
||||||
|
selectedDate.value = entry.date;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateEvent(Event? value) {
|
||||||
|
selectedEvent.value = value;
|
||||||
|
if (value != null) {
|
||||||
|
// イベント変更時に日付を調整
|
||||||
|
if (selectedDate.value == null ||
|
||||||
|
selectedDate.value!.isBefore(value.startDatetime) ||
|
||||||
|
selectedDate.value!.isAfter(value.endDatetime)) {
|
||||||
|
selectedDate.value = value.startDatetime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateTeam(Team? value) => selectedTeam.value = value;
|
||||||
|
void updateCategory(NewCategory? value) => selectedCategory.value = value;
|
||||||
|
void updateDate(DateTime value) => selectedDate.value = value;
|
||||||
|
|
||||||
|
/*
|
||||||
|
void updateDate(DateTime value){
|
||||||
|
selectedDate.value = DateFormat('yyyy-MM-dd').format(value!) as DateTime?;
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
void _initializeEntryData() {
|
void _initializeEntryData() {
|
||||||
if (currentEntry.value != null) {
|
if (currentEntry.value != null) {
|
||||||
selectedEvent.value = currentEntry.value!.event;
|
selectedEvent.value = currentEntry.value!.event;
|
||||||
@ -60,6 +113,7 @@ class EntryController extends GetxController {
|
|||||||
entries.assignAll(fetchedEntries);
|
entries.assignAll(fetchedEntries);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error fetching entries: $e');
|
print('Error fetching entries: $e');
|
||||||
|
Get.snackbar('Error', 'Failed to fetch entries');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +123,7 @@ class EntryController extends GetxController {
|
|||||||
events.assignAll(fetchedEvents);
|
events.assignAll(fetchedEvents);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error fetching events: $e');
|
print('Error fetching events: $e');
|
||||||
|
Get.snackbar('Error', 'Failed to fetch events');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +133,7 @@ class EntryController extends GetxController {
|
|||||||
teams.assignAll(fetchedTeams);
|
teams.assignAll(fetchedTeams);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error fetching teams: $e');
|
print('Error fetching teams: $e');
|
||||||
|
Get.snackbar('Error', 'Failed to fetch team');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +143,7 @@ class EntryController extends GetxController {
|
|||||||
categories.assignAll(fetchedCategories);
|
categories.assignAll(fetchedCategories);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error fetching categories: $e');
|
print('Error fetching categories: $e');
|
||||||
|
Get.snackbar('Error', 'Failed to fetch categories');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,10 +200,6 @@ class EntryController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
bool isOwner() {
|
||||||
// Implement logic to check if the current user is the owner of the entry
|
// Implement logic to check if the current user is the owner of the entry
|
||||||
|
|||||||
@ -3,6 +3,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:rogapp/pages/entry/entry_controller.dart';
|
import 'package:rogapp/pages/entry/entry_controller.dart';
|
||||||
|
import 'package:rogapp/model/event.dart';
|
||||||
|
import 'package:rogapp/model/category.dart';
|
||||||
|
import 'package:rogapp/model/team.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class EntryDetailPage extends GetView<EntryController> {
|
class EntryDetailPage extends GetView<EntryController> {
|
||||||
@override
|
@override
|
||||||
@ -11,52 +15,121 @@ class EntryDetailPage extends GetView<EntryController> {
|
|||||||
final mode = Get.arguments['mode'] as String? ?? 'new';
|
final mode = Get.arguments['mode'] as String? ?? 'new';
|
||||||
final entry = Get.arguments['entry'];
|
final entry = Get.arguments['entry'];
|
||||||
|
|
||||||
|
if (mode == 'edit' && entry != null) {
|
||||||
|
controller.initializeEditMode(entry);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(mode == 'new' ? 'エントリー登録' : 'エントリー詳細'),
|
title: Text(mode == 'new' ? 'エントリー登録' : 'エントリー詳細'),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Obx(() {
|
||||||
padding: EdgeInsets.all(16.0),
|
if (controller.isLoading.value) {
|
||||||
child: Obx(() => Column(
|
return Center(child: CircularProgressIndicator());
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
}
|
||||||
children: [
|
return Padding(
|
||||||
DropdownButtonFormField(
|
padding: EdgeInsets.all(16.0),
|
||||||
decoration: InputDecoration(labelText: 'イベント'),
|
child: SingleChildScrollView(
|
||||||
value: controller.selectedEvent.value,
|
child: Column(
|
||||||
items: controller.events.map((event) => DropdownMenuItem(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
value: event,
|
children: [
|
||||||
child: Text(event.eventName),
|
_buildDropdown<Event>(
|
||||||
)).toList(),
|
label: 'イベント',
|
||||||
onChanged: (value) => controller.updateEvent(value),
|
items: controller.events,
|
||||||
|
selectedId: controller.selectedEvent.value?.id,
|
||||||
|
onChanged: (eventId) => controller.updateEvent(
|
||||||
|
controller.events.firstWhere((e) => e.id == eventId)
|
||||||
|
),
|
||||||
|
getDisplayName: (event) => event.eventName,
|
||||||
|
getId: (event) => event.id,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
_buildDropdown<Team>(
|
||||||
|
label: 'チーム',
|
||||||
|
items: controller.teams,
|
||||||
|
selectedId: controller.selectedTeam.value?.id,
|
||||||
|
onChanged: (teamId) => controller.updateTeam(
|
||||||
|
controller.teams.firstWhere((t) => t.id == teamId)
|
||||||
|
),
|
||||||
|
getDisplayName: (team) => team.teamName,
|
||||||
|
getId: (team) => team.id,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
_buildDropdown<NewCategory>(
|
||||||
|
label: 'カテゴリ',
|
||||||
|
items: controller.categories,
|
||||||
|
selectedId: controller.selectedCategory.value?.id,
|
||||||
|
onChanged: (categoryId) => controller.updateCategory(
|
||||||
|
controller.categories.firstWhere((c) => c.id == categoryId)
|
||||||
|
),
|
||||||
|
getDisplayName: (category) => category.categoryName,
|
||||||
|
getId: (category) => category.id,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
ListTile(
|
||||||
|
title: Text('日付'),
|
||||||
|
subtitle: Text(
|
||||||
|
controller.selectedDate.value != null
|
||||||
|
? DateFormat('yyyy-MM-dd').format(controller.selectedDate.value!)
|
||||||
|
: '日付を選択してください',
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
if (controller.selectedEvent.value == null) {
|
||||||
|
Get.snackbar('Error', 'Please select an event first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final DateTime? picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: controller.selectedDate.value ?? controller.selectedEvent.value!.startDatetime,
|
||||||
|
firstDate: controller.selectedEvent.value!.startDatetime,
|
||||||
|
lastDate: controller.selectedEvent.value!.endDatetime,
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
controller.updateDate(picked);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 32),
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text(mode == 'new' ? 'エントリーを作成' : 'エントリーを更新'),
|
||||||
|
onPressed: () {
|
||||||
|
if (mode == 'new') {
|
||||||
|
controller.createEntry();
|
||||||
|
} else {
|
||||||
|
controller.updateEntry();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (mode == 'edit' && controller.isOwner())
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text('エントリーを削除'),
|
||||||
|
onPressed: () => controller.deleteEntry(),
|
||||||
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
DropdownButtonFormField(
|
),
|
||||||
decoration: InputDecoration(labelText: 'チーム'),
|
);
|
||||||
value: controller.selectedTeam.value,
|
}),
|
||||||
items: controller.teams.map((team) => DropdownMenuItem(
|
);
|
||||||
value: team,
|
}
|
||||||
child: Text(team.teamName),
|
|
||||||
)).toList(),
|
Widget _buildDropdown<T>({
|
||||||
onChanged: (value) => controller.updateTeam(value),
|
required String label,
|
||||||
),
|
required List<T> items,
|
||||||
DropdownButtonFormField(
|
required int? selectedId,
|
||||||
decoration: InputDecoration(labelText: 'カテゴリ'),
|
required void Function(int?) onChanged,
|
||||||
value: controller.selectedCategory.value,
|
required String Function(T) getDisplayName,
|
||||||
items: controller.categories.map((category) => DropdownMenuItem(
|
required int Function(T) getId,
|
||||||
value: category,
|
}) {
|
||||||
child: Text(category.categoryName),
|
return DropdownButtonFormField<int>(
|
||||||
)).toList(),
|
decoration: InputDecoration(labelText: label),
|
||||||
onChanged: (value) => controller.updateCategory(value),
|
value: selectedId,
|
||||||
),
|
items: items.map((item) => DropdownMenuItem<int>(
|
||||||
// 日付選択ウィジェットを追加
|
value: getId(item),
|
||||||
if (mode == 'edit' && controller.isOwner())
|
child: Text(getDisplayName(item)),
|
||||||
ElevatedButton(
|
)).toList(),
|
||||||
child: Text('エントリーを削除'),
|
onChanged: onChanged,
|
||||||
onPressed: () => controller.deleteEntry(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,31 +2,52 @@
|
|||||||
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:rogapp/model/user.dart';
|
import 'package:rogapp/model/user.dart';
|
||||||
|
import 'package:rogapp/pages/team/team_controller.dart';
|
||||||
import 'package:rogapp/services/api_service.dart';
|
import 'package:rogapp/services/api_service.dart';
|
||||||
|
|
||||||
class MemberController extends GetxController {
|
class MemberController extends GetxController {
|
||||||
late final ApiService _apiService;
|
late final ApiService _apiService;
|
||||||
|
|
||||||
final selectedMember = Rx<User?>(null);
|
final selectedMember = Rx<User?>(null);
|
||||||
final int teamId = 0;
|
int teamId = 0;
|
||||||
final member = Rx<User?>(null);
|
final member = Rx<User?>(null);
|
||||||
final email = ''.obs;
|
final email = ''.obs;
|
||||||
final firstname = ''.obs;
|
final firstname = ''.obs;
|
||||||
final lastname = ''.obs;
|
final lastname = ''.obs;
|
||||||
|
final female = false.obs;
|
||||||
final dateOfBirth = Rx<DateTime?>(null);
|
final dateOfBirth = Rx<DateTime?>(null);
|
||||||
final isLoading = true.obs; // isLoadingプロパティを追加
|
final isLoading = false.obs; // isLoadingプロパティを追加
|
||||||
|
final isActive = false.obs;
|
||||||
|
|
||||||
|
//MemberController(this._apiService);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() async{
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
await Get.putAsync(() => ApiService().init());
|
|
||||||
_apiService = Get.find<ApiService>();
|
_apiService = Get.find<ApiService>();
|
||||||
await loadInitialData();
|
ever(member, (_) => _initializeMemberData());
|
||||||
|
loadInitialData();
|
||||||
|
}
|
||||||
|
|
||||||
if (Get.arguments != null && Get.arguments['member'] != null) {
|
bool get isDummyEmail => email.value.startsWith('dummy_');
|
||||||
member.value = Get.arguments['member'];
|
bool get isApproved => !email.value.startsWith('dummy_') && member.value?.isActive == true;
|
||||||
_initializeMemberData();
|
|
||||||
|
Future<void> loadInitialData() async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
if (Get.arguments != null) {
|
||||||
|
if (Get.arguments['member'] != null) {
|
||||||
|
member.value = Get.arguments['member'];
|
||||||
|
}
|
||||||
|
if (Get.arguments['teamId'] != null) {
|
||||||
|
teamId = Get.arguments['teamId'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 他の必要な初期データの取得をここで行う
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading initial data: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,23 +61,26 @@ class MemberController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadInitialData() async {
|
|
||||||
try {
|
|
||||||
isLoading.value = true;
|
|
||||||
// 必要な初期データの取得をここで行う
|
|
||||||
// 例: await fetchTeamMembers();
|
|
||||||
} catch (e) {
|
|
||||||
print('Error loading initial data: $e');
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void setSelectedMember(User member) {
|
void setSelectedMember(User member) {
|
||||||
selectedMember.value = member;
|
this.member.value = member;
|
||||||
firstname.value = member.firstname;
|
email.value = member.email ?? '';
|
||||||
lastname.value = member.lastname;
|
firstname.value = member.firstname ?? '';
|
||||||
|
lastname.value = member.lastname ?? '';
|
||||||
|
dateOfBirth.value = member.dateOfBirth;
|
||||||
|
female.value = member.female ?? false;
|
||||||
|
isActive.value = member.isActive ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _validateInputs() {
|
||||||
|
if (email.value.isNotEmpty && !isDummyEmail) {
|
||||||
|
return true; // Emailのみの場合は有効
|
||||||
|
}
|
||||||
|
if (firstname.value.isEmpty || lastname.value.isEmpty || dateOfBirth.value == null || female.value == null) {
|
||||||
|
Get.snackbar('エラー', 'Emailが空の場合、姓名と生年月日及び性別は必須です', snackPosition: SnackPosition.BOTTOM);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateFirstName(String value) {
|
void updateFirstName(String value) {
|
||||||
@ -67,32 +91,52 @@ class MemberController extends GetxController {
|
|||||||
lastname.value = value;
|
lastname.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveMember() async {
|
Future<bool> saveMember() async {
|
||||||
|
if (!_validateInputs()) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
// メンバー保存のロジックをここに実装
|
User updatedMember;
|
||||||
// 例: await _apiService.updateMember(selectedMember.value!.id, firstName.value, lastName.value);
|
if (member.value == null || member.value!.id == null) {
|
||||||
|
// 新規メンバー作成
|
||||||
|
updatedMember = await _apiService.createTeamMember(
|
||||||
|
teamId,
|
||||||
|
isDummyEmail ? null : email.value, // dummy_メールの場合はnullを送信
|
||||||
|
firstname.value,
|
||||||
|
lastname.value,
|
||||||
|
dateOfBirth.value,
|
||||||
|
female.value,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 既存メンバー更新
|
||||||
|
updatedMember = await _apiService.updateTeamMember(
|
||||||
|
teamId,
|
||||||
|
member.value!.id!,
|
||||||
|
firstname.value,
|
||||||
|
lastname.value,
|
||||||
|
dateOfBirth.value,
|
||||||
|
female.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
member.value = updatedMember;
|
||||||
|
Get.snackbar('成功', 'メンバーが保存されました', snackPosition: SnackPosition.BOTTOM);
|
||||||
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error saving member: $e');
|
print('Error saving member: $e');
|
||||||
// エラーハンドリング(例:ユーザーへの通知)
|
Get.snackbar('エラー', 'メンバーの保存に失敗しました: ${e.toString()}', snackPosition: SnackPosition.BOTTOM);
|
||||||
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createMember(int teamId) async {
|
|
||||||
try {
|
String getDisplayName() {
|
||||||
final newMember = await _apiService.createTeamMember(
|
if (!isActive.value && !isDummyEmail) {
|
||||||
teamId,
|
final displayName = email.value.split('@')[0];
|
||||||
email.value,
|
return '$displayName(未承認)';
|
||||||
firstname.value,
|
|
||||||
lastname.value,
|
|
||||||
dateOfBirth.value,
|
|
||||||
);
|
|
||||||
member.value = newMember;
|
|
||||||
} catch (e) {
|
|
||||||
print('Error creating member: $e');
|
|
||||||
}
|
}
|
||||||
|
return '${lastname.value} ${firstname.value}'.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateMember() async {
|
Future<void> updateMember() async {
|
||||||
@ -105,6 +149,7 @@ class MemberController extends GetxController {
|
|||||||
firstname.value,
|
firstname.value,
|
||||||
lastname.value,
|
lastname.value,
|
||||||
dateOfBirth.value,
|
dateOfBirth.value,
|
||||||
|
female.value,
|
||||||
);
|
);
|
||||||
member.value = updatedMember;
|
member.value = updatedMember;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -112,6 +157,26 @@ class MemberController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> deleteMember() async {
|
||||||
|
if (member.value == null || member.value!.id == null) {
|
||||||
|
Get.snackbar('エラー', 'メンバー情報が不正です', snackPosition: SnackPosition.BOTTOM);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
await _apiService.deleteTeamMember(teamId, member.value!.id!);
|
||||||
|
Get.snackbar('成功', 'メンバーが削除されました', snackPosition: SnackPosition.BOTTOM);
|
||||||
|
member.value = null;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error deleting member: $e');
|
||||||
|
Get.snackbar('エラー', 'メンバーの削除に失敗しました: ${e.toString()}', snackPosition: SnackPosition.BOTTOM);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
Future<void> deleteMember() async {
|
Future<void> deleteMember() async {
|
||||||
if (member.value == null) return;
|
if (member.value == null) return;
|
||||||
int? memberId = member.value?.id;
|
int? memberId = member.value?.id;
|
||||||
@ -124,7 +189,14 @@ class MemberController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
Future<void> resendInvitation() async {
|
Future<void> resendInvitation() async {
|
||||||
|
if (isDummyEmail) {
|
||||||
|
Get.snackbar('エラー', 'ダミーメールアドレスには招待メールを送信できません', snackPosition: SnackPosition.BOTTOM);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (member.value == null || member.value!.email == null) return;
|
if (member.value == null || member.value!.email == null) return;
|
||||||
int? memberId = member.value?.id;
|
int? memberId = member.value?.id;
|
||||||
try {
|
try {
|
||||||
@ -147,4 +219,52 @@ class MemberController extends GetxController {
|
|||||||
if (member.value!.isActive) return '承認済';
|
if (member.value!.isActive) return '承認済';
|
||||||
return '招待中';
|
return '招待中';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int calculateAge() {
|
||||||
|
if (dateOfBirth.value == null) return 0;
|
||||||
|
final today = DateTime.now();
|
||||||
|
int age = today.year - dateOfBirth.value!.year;
|
||||||
|
if (today.month < dateOfBirth.value!.month ||
|
||||||
|
(today.month == dateOfBirth.value!.month && today.day < dateOfBirth.value!.day)) {
|
||||||
|
age--;
|
||||||
|
}
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
|
||||||
|
String calculateGrade() {
|
||||||
|
if (dateOfBirth.value == null) return '不明';
|
||||||
|
|
||||||
|
final today = DateTime.now();
|
||||||
|
final birthDate = dateOfBirth.value!;
|
||||||
|
|
||||||
|
// 今年の4月1日
|
||||||
|
final thisYearSchoolStart = DateTime(today.year, 4, 1);
|
||||||
|
|
||||||
|
// 生まれた年の翌年の4月1日(学齢期の始まり)
|
||||||
|
final schoolStartDate = DateTime(birthDate.year + 1, 4, 1);
|
||||||
|
|
||||||
|
// 学齢期の開始からの年数
|
||||||
|
int yearsFromSchoolStart = today.year - schoolStartDate.year;
|
||||||
|
|
||||||
|
// 今年の4月1日より前なら1年引く
|
||||||
|
if (today.isBefore(thisYearSchoolStart)) {
|
||||||
|
yearsFromSchoolStart--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yearsFromSchoolStart < 0) return '未就学';
|
||||||
|
if (yearsFromSchoolStart < 6) return '小${yearsFromSchoolStart + 1}';
|
||||||
|
if (yearsFromSchoolStart < 9) return '中${yearsFromSchoolStart - 5}';
|
||||||
|
if (yearsFromSchoolStart < 12) return '高${yearsFromSchoolStart - 8}';
|
||||||
|
return '成人';
|
||||||
|
}
|
||||||
|
|
||||||
|
String getAgeAndGrade() {
|
||||||
|
final age = calculateAge();
|
||||||
|
final grade = calculateGrade();
|
||||||
|
return '$age歳/$grade';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isOver18() {
|
||||||
|
return calculateAge() >= 18;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -3,92 +3,210 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:rogapp/pages/team/member_controller.dart';
|
import 'package:rogapp/pages/team/member_controller.dart';
|
||||||
|
import 'package:intl/intl.dart'; // この行を追加
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart'; // 追加
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
|
class MemberDetailPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_MemberDetailPageState createState() => _MemberDetailPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MemberDetailPageState extends State<MemberDetailPage> {
|
||||||
|
final MemberController controller = Get.find<MemberController>();
|
||||||
|
late TextEditingController _firstNameController;
|
||||||
|
late TextEditingController _lastNameController;
|
||||||
|
late TextEditingController _emailController;
|
||||||
|
|
||||||
class MemberDetailPage extends GetView<MemberController> {
|
|
||||||
final TextEditingController _firstNameController = TextEditingController();
|
|
||||||
final TextEditingController _lastNameController = TextEditingController();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
void initState() {
|
||||||
final mode = Get.arguments['mode'];
|
super.initState();
|
||||||
final member = Get.arguments['member'];
|
_initializeControllers();
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final mode = Get.arguments['mode'];
|
||||||
|
final member = Get.arguments['member'];
|
||||||
if (mode == 'edit' && member != null) {
|
if (mode == 'edit' && member != null) {
|
||||||
controller.setSelectedMember(member);
|
controller.setSelectedMember(member);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
void _initializeControllers() {
|
||||||
appBar: AppBar(
|
_firstNameController = TextEditingController(text: controller.firstname.value);
|
||||||
title: Text(mode == 'new' ? 'メンバー追加' : 'メンバー詳細'),
|
_lastNameController = TextEditingController(text: controller.lastname.value);
|
||||||
actions: [
|
_emailController = TextEditingController(text: controller.email.value);
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.save),
|
controller.firstname.listen((value) {
|
||||||
onPressed: () async {
|
if (_firstNameController.text != value) {
|
||||||
await controller.saveMember();
|
_firstNameController.value = TextEditingValue(
|
||||||
Get.back();
|
text: value,
|
||||||
},
|
selection: TextSelection.fromPosition(TextPosition(offset: value.length)),
|
||||||
),
|
);
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Obx(()
|
|
||||||
{
|
|
||||||
if (controller.isLoading.value) {
|
|
||||||
return Center(child: CircularProgressIndicator());
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
_firstNameController.value = _firstNameController.value.copyWith(
|
controller.lastname.listen((value) {
|
||||||
text: controller.firstname.value,
|
if (_lastNameController.text != value) {
|
||||||
selection: TextSelection.collapsed(
|
_lastNameController.value = TextEditingValue(
|
||||||
offset: controller.firstname.value.length),
|
text: value,
|
||||||
);
|
selection: TextSelection.fromPosition(TextPosition(offset: value.length)),
|
||||||
_lastNameController.value = _lastNameController.value.copyWith(
|
);
|
||||||
text: controller.lastname.value,
|
}
|
||||||
selection: TextSelection.collapsed(
|
});
|
||||||
offset: controller.lastname.value.length),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Padding(
|
controller.email.listen((value) {
|
||||||
padding: EdgeInsets.all(16.0),
|
if (_emailController.text != value) {
|
||||||
child: Column(
|
_emailController.value = TextEditingValue(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
text: value,
|
||||||
children: [
|
selection: TextSelection.fromPosition(TextPosition(offset: value.length)),
|
||||||
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: '姓'),
|
|
||||||
controller: _lastNameController,
|
|
||||||
onChanged: (value) => controller.updateLastName(value),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
decoration: InputDecoration(labelText: '名'),
|
|
||||||
controller: _firstNameController,
|
|
||||||
onChanged: (value) => controller.updateFirstName(value),
|
|
||||||
),
|
|
||||||
|
|
||||||
// 誕生日選択ウィジェットを追加
|
@override
|
||||||
if (mode == 'edit')
|
Widget build(BuildContext context) {
|
||||||
Text('ステータス: ${controller.getMemberStatus()}'),
|
final mode = Get.arguments['mode'] as String;
|
||||||
if (mode == 'edit' && controller.getMemberStatus() == '招待中')
|
//final member = Get.arguments['member'];
|
||||||
ElevatedButton(
|
final teamId = Get.arguments['teamId'] as int;
|
||||||
child: Text('招待メールを再送信'),
|
|
||||||
onPressed: () => controller.resendInvitation(),
|
/*
|
||||||
|
return MaterialApp( // MaterialApp をここに追加
|
||||||
|
localizationsDelegates: [
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
supportedLocales: [
|
||||||
|
const Locale('ja', 'JP'),
|
||||||
|
],
|
||||||
|
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);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (mode == 'edit')
|
],
|
||||||
ElevatedButton(
|
),
|
||||||
child: Text('メンバーから除外'),
|
body: Obx(() {
|
||||||
onPressed: () => controller.deleteMember(),
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_firstNameController.dispose();
|
||||||
|
_lastNameController.dispose();
|
||||||
|
_emailController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,6 +71,15 @@ class TeamController extends GetxController {
|
|||||||
teamMembers.clear();
|
teamMembers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cleanupForNavigation() {
|
||||||
|
selectedTeam.value = null;
|
||||||
|
teamName.value = '';
|
||||||
|
selectedCategory.value = categories.isNotEmpty ? categories.first : null;
|
||||||
|
teamMembers.clear();
|
||||||
|
//teamMembersはクリアしない
|
||||||
|
// 必要に応じて他のクリーンアップ処理を追加
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> fetchTeams() async {
|
Future<void> fetchTeams() async {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
@ -126,25 +135,75 @@ class TeamController extends GetxController {
|
|||||||
|
|
||||||
Future<void> fetchTeamMembers(int teamId) async {
|
Future<void> fetchTeamMembers(int teamId) async {
|
||||||
try {
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
final members = await _apiService.getTeamMembers(teamId);
|
final members = await _apiService.getTeamMembers(teamId);
|
||||||
teamMembers.assignAll(members);
|
teamMembers.assignAll(members);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
error.value = 'メンバーの取得に失敗しました: $e';
|
||||||
print('Error fetching team members: $e');
|
print('Error fetching team members: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateMember(teamId, User member) async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
await _apiService.updateTeamMember(teamId,member.id, member.firstname, member.lastname, member.dateOfBirth, member.female);
|
||||||
|
await fetchTeamMembers(selectedTeam.value!.id);
|
||||||
|
} catch (e) {
|
||||||
|
error.value = 'メンバーの更新に失敗しました: $e';
|
||||||
|
print('Error updating member: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteMember(int memberId, int teamId) async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
await _apiService.deleteTeamMember(teamId,memberId);
|
||||||
|
await fetchTeamMembers(teamId);
|
||||||
|
} catch (e) {
|
||||||
|
error.value = 'メンバーの削除に失敗しました: $e';
|
||||||
|
print('Error deleting member: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Future<void> addMember(User member, int teamId) async {
|
||||||
|
// try {
|
||||||
|
// isLoading.value = true;
|
||||||
|
// await _apiService.createTeamMember(teamId, member.email, member.firstname, member.lastname, member.dateOfBirth);
|
||||||
|
// await fetchTeamMembers(teamId);
|
||||||
|
// } catch (e) {
|
||||||
|
// error.value = 'メンバーの追加に失敗しました: $e';
|
||||||
|
// print('Error adding member: $e');
|
||||||
|
// } finally {
|
||||||
|
// isLoading.value = false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
void updateTeamName(String value) {
|
void updateTeamName(String value) {
|
||||||
teamName.value = value;
|
teamName.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateCategory(NewCategory? value) {
|
void updateCategory(NewCategory? value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
selectedCategory.value = categories.firstWhere(
|
selectedCategory.value = value;
|
||||||
(category) => category.id == value.id,
|
|
||||||
orElse: () => value,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//void updateCategory(NewCategory? value) {
|
||||||
|
// if (value != null) {
|
||||||
|
// selectedCategory.value = categories.firstWhere(
|
||||||
|
// (category) => category.id == value.id,
|
||||||
|
// orElse: () => value,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
Future<void> saveTeam() async {
|
Future<void> saveTeam() async {
|
||||||
try {
|
try {
|
||||||
@ -161,18 +220,10 @@ class TeamController extends GetxController {
|
|||||||
|
|
||||||
// サーバーから最新のデータを再取得
|
// サーバーから最新のデータを再取得
|
||||||
await fetchTeams();
|
await fetchTeams();
|
||||||
|
|
||||||
// 選択中のチームを更新
|
|
||||||
if (selectedTeam.value != null) {
|
|
||||||
selectedTeam.value = teams.firstWhere((t) => t.id == selectedTeam.value!.id);
|
|
||||||
teamName.value = selectedTeam.value!.teamName;
|
|
||||||
selectedCategory.value = selectedTeam.value!.category;
|
|
||||||
}
|
|
||||||
|
|
||||||
update(); // UIを強制的に更新
|
update(); // UIを強制的に更新
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = 'チームの保存に失敗しました: $e';
|
error.value = 'チームの保存に失敗しました: $e';
|
||||||
print("Team save error: $e");
|
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,21 +7,76 @@ import 'package:rogapp/routes/app_pages.dart';
|
|||||||
import 'package:rogapp/model/team.dart';
|
import 'package:rogapp/model/team.dart';
|
||||||
import 'package:rogapp/model/category.dart';
|
import 'package:rogapp/model/category.dart';
|
||||||
|
|
||||||
class TeamDetailPage extends GetView<TeamController> {
|
class TeamDetailPage extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
_TeamDetailPageState createState() => _TeamDetailPageState();
|
||||||
final mode = Get.arguments['mode'] as String;
|
}
|
||||||
final team = Get.arguments['team'] as Team?;
|
|
||||||
|
|
||||||
if (mode == 'edit' && team != null) {
|
class _TeamDetailPageState extends State<TeamDetailPage> {
|
||||||
controller.setSelectedTeam(team);
|
late TeamController controller;
|
||||||
|
late TextEditingController _teamNameController = TextEditingController();
|
||||||
|
final RxString mode = ''.obs;
|
||||||
|
final Rx<Team?> team = Rx<Team?>(null);
|
||||||
|
Worker? _teamNameWorker;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
controller = Get.find<TeamController>();
|
||||||
|
_teamNameController = TextEditingController();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_initializeData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initializeData() {
|
||||||
|
final args = Get.arguments;
|
||||||
|
if (args != null && args is Map<String, dynamic>) {
|
||||||
|
mode.value = args['mode'] as String? ?? '';
|
||||||
|
team.value = args['team'] as Team?;
|
||||||
|
|
||||||
|
if (mode.value == 'edit' && team.value != null) {
|
||||||
|
controller.setSelectedTeam(team.value!);
|
||||||
|
} else {
|
||||||
|
controller.resetForm();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 引数がない場合は新規作成モードとして扱う
|
||||||
|
mode.value = 'new';
|
||||||
controller.resetForm();
|
controller.resetForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_teamNameController.text = controller.teamName.value;
|
||||||
|
|
||||||
|
// Use ever instead of direct listener
|
||||||
|
_teamNameWorker = ever(controller.teamName, (String value) {
|
||||||
|
if (_teamNameController.text != value) {
|
||||||
|
_teamNameController.text = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_teamNameWorker?.dispose();
|
||||||
|
_teamNameController.dispose();
|
||||||
|
//controller.resetForm(); // Reset the form when leaving the page
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(mode == 'new' ? '新規チーム作成' : 'チーム詳細'),
|
title: Text(Get.arguments['mode'] == 'new' ? '新規チーム作成' : 'チーム詳細'),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
controller.cleanupForNavigation();
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.save),
|
icon: Icon(Icons.save),
|
||||||
@ -34,56 +89,63 @@ class TeamDetailPage extends GetView<TeamController> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (mode == 'edit')
|
Obx(() {
|
||||||
IconButton(
|
if (mode.value == 'edit') {
|
||||||
icon: Icon(Icons.delete),
|
return IconButton(
|
||||||
onPressed: () async {
|
icon: Icon(Icons.delete),
|
||||||
try {
|
onPressed: () async {
|
||||||
await controller.deleteSelectedTeam();
|
try {
|
||||||
Get.back();
|
await controller.deleteSelectedTeam();
|
||||||
} catch (e) {
|
Get.back();
|
||||||
Get.snackbar('エラー', e.toString(), snackPosition: SnackPosition.BOTTOM);
|
} catch (e) {
|
||||||
}
|
Get.snackbar('エラー', e.toString(), snackPosition: SnackPosition.BOTTOM);
|
||||||
},
|
}
|
||||||
),
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: GetBuilder<TeamController>(
|
body: Obx(() {
|
||||||
builder: (controller) {
|
if (controller.isLoading.value) {
|
||||||
if (controller.isLoading.value) {
|
return Center(child: CircularProgressIndicator());
|
||||||
return Center(child: CircularProgressIndicator());
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
initialValue: controller.teamName.value,
|
decoration: InputDecoration(labelText: 'チーム名'),
|
||||||
decoration: InputDecoration(labelText: 'チーム名'),
|
controller: _teamNameController,
|
||||||
onChanged: (value) => controller.updateTeamName(value),
|
onChanged: (value) => controller.updateTeamName(value),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
|
||||||
DropdownButtonFormField<NewCategory>(
|
SizedBox(height: 16),
|
||||||
decoration: InputDecoration(labelText: 'カテゴリ'),
|
DropdownButtonFormField<NewCategory>(
|
||||||
value: controller.selectedCategory.value,
|
decoration: InputDecoration(labelText: 'カテゴリ'),
|
||||||
items: controller.categories.map((category) => DropdownMenuItem(
|
value: controller.selectedCategory.value,
|
||||||
value: category,
|
items: controller.categories.map((category) => DropdownMenuItem(
|
||||||
child: Text(category.categoryName),
|
value: category,
|
||||||
)).toList(),
|
child: Text(category.categoryName),
|
||||||
onChanged: (value) => controller.updateCategory(value),
|
)).toList(),
|
||||||
),
|
onChanged: (value) => controller.updateCategory(value),
|
||||||
if (mode == 'edit')
|
),
|
||||||
Padding(
|
if (mode.value == 'edit')
|
||||||
padding: EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: Text('ゼッケン番号: ${controller.selectedTeam.value?.zekkenNumber ?? ""}'),
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 16),
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
child: Text('所有者: ${controller.currentUser.value?.email ?? "未設定"}'),
|
child: Text('ゼッケン番号: ${controller.selectedTeam.value?.zekkenNumber ?? ""}'),
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Text('所有者: ${controller.currentUser.value?.email ?? "未設定"}'),
|
||||||
|
),
|
||||||
|
|
||||||
|
if (mode.value == 'edit') ...[
|
||||||
SizedBox(height: 24),
|
SizedBox(height: 24),
|
||||||
Text('メンバーリスト', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
Text('メンバーリスト', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
@ -93,34 +155,55 @@ class TeamDetailPage extends GetView<TeamController> {
|
|||||||
itemCount: controller.teamMembers.length,
|
itemCount: controller.teamMembers.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final member = controller.teamMembers[index];
|
final member = controller.teamMembers[index];
|
||||||
|
final isDummyEmail = member.email?.startsWith('dummy_') ?? false;
|
||||||
|
final displayName = isDummyEmail
|
||||||
|
? '${member.lastname} ${member.firstname}'
|
||||||
|
: member.isActive
|
||||||
|
? '${member.lastname} ${member.firstname}'
|
||||||
|
: '${member.email?.split('@')[0] ?? ''}(未承認)';
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text('${member.lastname}, ${member.firstname}'),
|
title: Text(displayName),
|
||||||
onTap: () => Get.toNamed(
|
subtitle: isDummyEmail ? Text('Email未設定') : null,
|
||||||
AppPages.MEMBER_DETAIL,
|
onTap: () async {
|
||||||
arguments: {'mode': 'edit', 'member': member, 'teamId': controller.selectedTeam.value?.id},
|
final result = await Get.toNamed(
|
||||||
),
|
AppPages.MEMBER_DETAIL,
|
||||||
|
arguments: {'mode': 'edit', 'member': member, 'teamId': controller.selectedTeam.value?.id},
|
||||||
|
);
|
||||||
|
if (result == true) {
|
||||||
|
await controller.fetchTeamMembers(controller.selectedTeam.value!.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
child: Text('新しいメンバーを追加'),
|
child: Text('新しいメンバーを追加'),
|
||||||
onPressed: () => Get.toNamed(
|
onPressed: () async {
|
||||||
AppPages.MEMBER_DETAIL,
|
await Get.toNamed(
|
||||||
arguments: {'mode': 'new', 'teamId': controller.selectedTeam.value?.id},
|
AppPages.MEMBER_DETAIL,
|
||||||
),
|
arguments: {'mode': 'new', 'teamId': controller.selectedTeam.value?.id},
|
||||||
|
);
|
||||||
|
if (controller.selectedTeam.value != null) {
|
||||||
|
controller.fetchTeamMembers(controller.selectedTeam.value!.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
},
|
);
|
||||||
),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import 'package:rogapp/model/category.dart';
|
|||||||
import 'package:rogapp/model/user.dart';
|
import 'package:rogapp/model/user.dart';
|
||||||
import 'package:rogapp/pages/index/index_controller.dart';
|
import 'package:rogapp/pages/index/index_controller.dart';
|
||||||
import '../utils/const.dart';
|
import '../utils/const.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ApiService extends GetxService{
|
class ApiService extends GetxService{
|
||||||
@ -19,16 +21,18 @@ class ApiService extends GetxService{
|
|||||||
String baseUrl = '';
|
String baseUrl = '';
|
||||||
String token = 'your-auth-token';
|
String token = 'your-auth-token';
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<ApiService> init() async {
|
||||||
try {
|
try {
|
||||||
// ここで必要な初期化処理を行う
|
// ここで必要な初期化処理を行う
|
||||||
serverUrl = ConstValues.currentServer();
|
serverUrl = ConstValues.currentServer();
|
||||||
baseUrl = '$serverUrl/api';
|
baseUrl = '$serverUrl/api';
|
||||||
//await Future.delayed(Duration(seconds: 2)); // 仮の遅延(実際の初期化処理に置き換えてください)
|
//await Future.delayed(Duration(seconds: 2)); // 仮の遅延(実際の初期化処理に置き換えてください)
|
||||||
print('ApiService initialized successfully');
|
print('ApiService initialized successfully');
|
||||||
|
return this;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
print('Error in ApiService initialization: $e');
|
print('Error in ApiService initialization: $e');
|
||||||
rethrow; // エラーを再スローして、呼び出し元で処理できるようにする
|
rethrow; // エラーを再スローして、呼び出し元で処理できるようにする
|
||||||
|
//return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,7 +236,8 @@ class ApiService extends GetxService{
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 201) {
|
if (response.statusCode == 201) {
|
||||||
return Team.fromJson(json.decode(response.body));
|
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||||
|
return Team.fromJson(json.decode(decodedResponse));
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Failed to create team');
|
throw Exception('Failed to create team');
|
||||||
}
|
}
|
||||||
@ -255,7 +260,9 @@ class ApiService extends GetxService{
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return Team.fromJson(json.decode(response.body));
|
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||||
|
|
||||||
|
return Team.fromJson(json.decode(decodedResponse));
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Failed to update team');
|
throw Exception('Failed to update team');
|
||||||
}
|
}
|
||||||
@ -295,10 +302,15 @@ class ApiService extends GetxService{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<User> createTeamMember(int teamId, String email, String firstname, String lastname, DateTime? dateOfBirth) async {
|
Future<User> createTeamMember(int teamId, String? email, String firstname, String lastname, DateTime? dateOfBirth,bool? female) async {
|
||||||
init();
|
init();
|
||||||
getToken();
|
getToken();
|
||||||
|
|
||||||
|
String? formattedDateOfBirth;
|
||||||
|
if (dateOfBirth != null) {
|
||||||
|
formattedDateOfBirth = DateFormat('yyyy-MM-dd').format(dateOfBirth);
|
||||||
|
}
|
||||||
|
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse('$baseUrl/teams/$teamId/members/'),
|
Uri.parse('$baseUrl/teams/$teamId/members/'),
|
||||||
headers: {
|
headers: {
|
||||||
@ -309,21 +321,28 @@ class ApiService extends GetxService{
|
|||||||
'email': email,
|
'email': email,
|
||||||
'firstname': firstname,
|
'firstname': firstname,
|
||||||
'lastname': lastname,
|
'lastname': lastname,
|
||||||
'date_of_birth': dateOfBirth?.toIso8601String(),
|
'date_of_birth': formattedDateOfBirth,
|
||||||
|
'female': female,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 201) {
|
if (response.statusCode == 201) {
|
||||||
return User.fromJson(json.decode(response.body));
|
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||||
|
return User.fromJson(json.decode(decodedResponse));
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Failed to create team member');
|
throw Exception('Failed to create team member');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<User> updateTeamMember(int teamId,int memberId, String firstname, String lastname, DateTime? dateOfBirth) async {
|
Future<User> updateTeamMember(int teamId,int? memberId, String firstname, String lastname, DateTime? dateOfBirth,bool? female) async {
|
||||||
init();
|
init();
|
||||||
getToken();
|
getToken();
|
||||||
|
|
||||||
|
String? formattedDateOfBirth;
|
||||||
|
if (dateOfBirth != null) {
|
||||||
|
formattedDateOfBirth = DateFormat('yyyy-MM-dd').format(dateOfBirth);
|
||||||
|
}
|
||||||
|
|
||||||
final response = await http.put(
|
final response = await http.put(
|
||||||
Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'),
|
Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'),
|
||||||
headers: {
|
headers: {
|
||||||
@ -333,12 +352,14 @@ class ApiService extends GetxService{
|
|||||||
body: json.encode({
|
body: json.encode({
|
||||||
'firstname': firstname,
|
'firstname': firstname,
|
||||||
'lastname': lastname,
|
'lastname': lastname,
|
||||||
'date_of_birth': dateOfBirth?.toIso8601String(),
|
'date_of_birth': formattedDateOfBirth,
|
||||||
|
'female': female,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return User.fromJson(json.decode(response.body));
|
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||||
|
return User.fromJson(json.decode(decodedResponse));
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Failed to update team member');
|
throw Exception('Failed to update team member');
|
||||||
}
|
}
|
||||||
@ -378,13 +399,14 @@ class ApiService extends GetxService{
|
|||||||
getToken();
|
getToken();
|
||||||
|
|
||||||
final response = await http.get(
|
final response = await http.get(
|
||||||
Uri.parse('$baseUrl/entries/'),
|
Uri.parse('$baseUrl/entry/'),
|
||||||
headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8',
|
headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
List<dynamic> entriesJson = json.decode(response.body);
|
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||||
|
List<dynamic> entriesJson = json.decode(decodedResponse);
|
||||||
return entriesJson.map((json) => Entry.fromJson(json)).toList();
|
return entriesJson.map((json) => Entry.fromJson(json)).toList();
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Failed to load entries');
|
throw Exception('Failed to load entries');
|
||||||
@ -416,6 +438,11 @@ class ApiService extends GetxService{
|
|||||||
init();
|
init();
|
||||||
getToken();
|
getToken();
|
||||||
|
|
||||||
|
String? formattedDate;
|
||||||
|
if (date != null) {
|
||||||
|
formattedDate = DateFormat('yyyy-MM-dd').format(date);
|
||||||
|
}
|
||||||
|
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse('$baseUrl/entry/'),
|
Uri.parse('$baseUrl/entry/'),
|
||||||
headers: {
|
headers: {
|
||||||
@ -426,12 +453,14 @@ class ApiService extends GetxService{
|
|||||||
'team': teamId,
|
'team': teamId,
|
||||||
'event': eventId,
|
'event': eventId,
|
||||||
'category': categoryId,
|
'category': categoryId,
|
||||||
'date': date.toIso8601String(),
|
'date': formattedDate,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 201) {
|
if (response.statusCode == 201) {
|
||||||
return Entry.fromJson(json.decode(response.body));
|
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||||
|
|
||||||
|
return Entry.fromJson(json.decode(decodedResponse));
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Failed to create entry');
|
throw Exception('Failed to create entry');
|
||||||
}
|
}
|
||||||
@ -441,6 +470,11 @@ class ApiService extends GetxService{
|
|||||||
init();
|
init();
|
||||||
getToken();
|
getToken();
|
||||||
|
|
||||||
|
String? formattedDate;
|
||||||
|
if (date != null) {
|
||||||
|
formattedDate = DateFormat('yyyy-MM-dd').format(date);
|
||||||
|
}
|
||||||
|
|
||||||
final response = await http.put(
|
final response = await http.put(
|
||||||
Uri.parse('$baseUrl/entry/$entryId/'),
|
Uri.parse('$baseUrl/entry/$entryId/'),
|
||||||
headers: {
|
headers: {
|
||||||
@ -450,12 +484,14 @@ class ApiService extends GetxService{
|
|||||||
body: json.encode({
|
body: json.encode({
|
||||||
'event': eventId,
|
'event': eventId,
|
||||||
'category': categoryId,
|
'category': categoryId,
|
||||||
'date': date.toIso8601String(),
|
'date': formattedDate,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return Entry.fromJson(json.decode(response.body));
|
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||||
|
|
||||||
|
return Entry.fromJson(json.decode(decodedResponse));
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Failed to update entry');
|
throw Exception('Failed to update entry');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -374,6 +374,11 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.1"
|
||||||
|
flutter_localizations:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_map:
|
flutter_map:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -660,10 +665,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.18.1"
|
version: "0.19.0"
|
||||||
isar:
|
isar:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -29,6 +29,8 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_localizations:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
@ -81,7 +83,7 @@ dependencies:
|
|||||||
circular_menu: ^2.0.1
|
circular_menu: ^2.0.1
|
||||||
camera: ^0.10.0+3
|
camera: ^0.10.0+3
|
||||||
camera_camera: ^3.0.0
|
camera_camera: ^3.0.0
|
||||||
intl: ^0.18.1
|
intl: ^0.19.0 #^0.18.1
|
||||||
modal_bottom_sheet: ^3.0.0-pre
|
modal_bottom_sheet: ^3.0.0-pre
|
||||||
connectivity_plus: ^5.0.2
|
connectivity_plus: ^5.0.2
|
||||||
flutter_map_tile_caching: ^9.0.0-dev.5
|
flutter_map_tile_caching: ^9.0.0-dev.5
|
||||||
|
|||||||
Reference in New Issue
Block a user