Added Type, Category and Signatories on task
This commit is contained in:
@@ -8,6 +8,9 @@ import '../../models/task_activity_log.dart';
|
||||
import '../../models/ticket.dart';
|
||||
import '../../models/ticket_message.dart';
|
||||
import '../../providers/notifications_provider.dart';
|
||||
import 'dart:async';
|
||||
import '../../providers/supabase_provider.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
import '../../providers/profile_provider.dart';
|
||||
import '../../providers/tasks_provider.dart';
|
||||
import '../../providers/tickets_provider.dart';
|
||||
@@ -21,6 +24,17 @@ import '../../theme/app_surfaces.dart';
|
||||
import '../../widgets/task_assignment_section.dart';
|
||||
import '../../widgets/typing_dots.dart';
|
||||
|
||||
// Local request metadata options (kept consistent with other screens)
|
||||
const List<String> requestTypeOptions = [
|
||||
'Install',
|
||||
'Repair',
|
||||
'Upgrade',
|
||||
'Replace',
|
||||
'Other',
|
||||
];
|
||||
|
||||
const List<String> requestCategoryOptions = ['Software', 'Hardware', 'Network'];
|
||||
|
||||
class TaskDetailScreen extends ConsumerStatefulWidget {
|
||||
const TaskDetailScreen({super.key, required this.taskId});
|
||||
|
||||
@@ -32,6 +46,13 @@ class TaskDetailScreen extends ConsumerStatefulWidget {
|
||||
|
||||
class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||
final _messageController = TextEditingController();
|
||||
// Controllers for editable signatories
|
||||
final _requestedController = TextEditingController();
|
||||
final _notedController = TextEditingController();
|
||||
final _receivedController = TextEditingController();
|
||||
Timer? _requestedDebounce;
|
||||
Timer? _notedDebounce;
|
||||
Timer? _receivedDebounce;
|
||||
static const List<String> _statusOptions = [
|
||||
'queued',
|
||||
'in_progress',
|
||||
@@ -54,6 +75,12 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||
@override
|
||||
void dispose() {
|
||||
_messageController.dispose();
|
||||
_requestedController.dispose();
|
||||
_notedController.dispose();
|
||||
_receivedController.dispose();
|
||||
_requestedDebounce?.cancel();
|
||||
_notedDebounce?.cancel();
|
||||
_receivedDebounce?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -149,10 +176,480 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||
const SizedBox(height: 12),
|
||||
Text(description),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
_buildTatSection(task),
|
||||
const SizedBox(height: 16),
|
||||
TaskAssignmentSection(taskId: task.id, canAssign: showAssign),
|
||||
// Tabbed details: Assignees / Type & Category / Signatories
|
||||
DefaultTabController(
|
||||
length: 3,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TabBar(
|
||||
labelColor: Theme.of(context).colorScheme.onSurface,
|
||||
indicatorColor: Theme.of(context).colorScheme.primary,
|
||||
tabs: const [
|
||||
Tab(text: 'Assignees'),
|
||||
Tab(text: 'Type & Category'),
|
||||
Tab(text: 'Signatories'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: isWide ? 360 : 300,
|
||||
child: TabBarView(
|
||||
children: [
|
||||
// Assignees
|
||||
SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TaskAssignmentSection(
|
||||
taskId: task.id,
|
||||
canAssign: showAssign,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: _buildTatSection(task),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Type & Category
|
||||
SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!canUpdateStatus) ...[
|
||||
_MetaBadge(
|
||||
label: 'Type',
|
||||
value: task.requestType ?? 'None',
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_MetaBadge(
|
||||
label: 'Category',
|
||||
value: task.requestCategory ?? 'None',
|
||||
),
|
||||
] else ...[
|
||||
const Text('Type'),
|
||||
const SizedBox(height: 6),
|
||||
DropdownButtonFormField<String?>(
|
||||
initialValue: task.requestType,
|
||||
items: [
|
||||
const DropdownMenuItem(
|
||||
value: null,
|
||||
child: Text('None'),
|
||||
),
|
||||
for (final t in requestTypeOptions)
|
||||
DropdownMenuItem(
|
||||
value: t,
|
||||
child: Text(t),
|
||||
),
|
||||
],
|
||||
onChanged: (v) => ref
|
||||
.read(tasksControllerProvider)
|
||||
.updateTask(
|
||||
taskId: task.id,
|
||||
requestType: v,
|
||||
),
|
||||
),
|
||||
if (task.requestType == 'Other') ...[
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
initialValue: task.requestTypeOther,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Details',
|
||||
),
|
||||
onChanged: (text) => ref
|
||||
.read(tasksControllerProvider)
|
||||
.updateTask(
|
||||
taskId: task.id,
|
||||
requestTypeOther: text.isEmpty
|
||||
? null
|
||||
: text,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
const Text('Category'),
|
||||
const SizedBox(height: 6),
|
||||
DropdownButtonFormField<String?>(
|
||||
initialValue: task.requestCategory,
|
||||
items: [
|
||||
const DropdownMenuItem(
|
||||
value: null,
|
||||
child: Text('None'),
|
||||
),
|
||||
for (final c in requestCategoryOptions)
|
||||
DropdownMenuItem(
|
||||
value: c,
|
||||
child: Text(c),
|
||||
),
|
||||
],
|
||||
onChanged: (v) => ref
|
||||
.read(tasksControllerProvider)
|
||||
.updateTask(
|
||||
taskId: task.id,
|
||||
requestCategory: v,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Signatories (editable)
|
||||
SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Requested by',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
TypeAheadFormField<String>(
|
||||
textFieldConfiguration:
|
||||
TextFieldConfiguration(
|
||||
controller: _requestedController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Requester name or id',
|
||||
),
|
||||
onChanged: (v) {
|
||||
_requestedDebounce?.cancel();
|
||||
_requestedDebounce = Timer(
|
||||
const Duration(milliseconds: 700),
|
||||
() async {
|
||||
final name = v.trim();
|
||||
await ref
|
||||
.read(
|
||||
tasksControllerProvider,
|
||||
)
|
||||
.updateTask(
|
||||
taskId: task.id,
|
||||
requestedBy: name.isEmpty
|
||||
? null
|
||||
: name,
|
||||
);
|
||||
if (name.isNotEmpty) {
|
||||
try {
|
||||
await ref
|
||||
.read(
|
||||
supabaseClientProvider,
|
||||
)
|
||||
.from('clients')
|
||||
.upsert({'name': name});
|
||||
} catch (_) {}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
suggestionsCallback: (pattern) async {
|
||||
final profiles =
|
||||
ref
|
||||
.watch(profilesProvider)
|
||||
.valueOrNull ??
|
||||
[];
|
||||
final fromProfiles = profiles
|
||||
.map(
|
||||
(p) => p.fullName.isEmpty
|
||||
? p.id
|
||||
: p.fullName,
|
||||
)
|
||||
.where(
|
||||
(n) => n.toLowerCase().contains(
|
||||
pattern.toLowerCase(),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
try {
|
||||
final clientRows = await ref
|
||||
.read(supabaseClientProvider)
|
||||
.from('clients')
|
||||
.select('name')
|
||||
.ilike('name', '%$pattern%');
|
||||
final clientNames =
|
||||
(clientRows as List<dynamic>?)
|
||||
?.map(
|
||||
(r) => r['name'] as String,
|
||||
)
|
||||
.whereType<String>()
|
||||
.toList() ??
|
||||
<String>[];
|
||||
final merged = {
|
||||
...fromProfiles,
|
||||
...clientNames,
|
||||
}.toList();
|
||||
return merged;
|
||||
} catch (_) {
|
||||
return fromProfiles;
|
||||
}
|
||||
},
|
||||
itemBuilder: (context, suggestion) =>
|
||||
ListTile(title: Text(suggestion)),
|
||||
onSuggestionSelected: (suggestion) async {
|
||||
_requestedDebounce?.cancel();
|
||||
_requestedController.text = suggestion;
|
||||
await ref
|
||||
.read(tasksControllerProvider)
|
||||
.updateTask(
|
||||
taskId: task.id,
|
||||
requestedBy: suggestion.isEmpty
|
||||
? null
|
||||
: suggestion,
|
||||
);
|
||||
try {
|
||||
if (suggestion.isNotEmpty) {
|
||||
await ref
|
||||
.read(supabaseClientProvider)
|
||||
.from('clients')
|
||||
.upsert({'name': suggestion});
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Noted by (Supervisor/Senior)',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
TypeAheadFormField<String>(
|
||||
textFieldConfiguration:
|
||||
TextFieldConfiguration(
|
||||
controller: _notedController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Supervisor/Senior',
|
||||
),
|
||||
onChanged: (v) {
|
||||
_notedDebounce?.cancel();
|
||||
_notedDebounce = Timer(
|
||||
const Duration(milliseconds: 700),
|
||||
() async {
|
||||
final name = v.trim();
|
||||
await ref
|
||||
.read(
|
||||
tasksControllerProvider,
|
||||
)
|
||||
.updateTask(
|
||||
taskId: task.id,
|
||||
notedBy: name.isEmpty
|
||||
? null
|
||||
: name,
|
||||
);
|
||||
if (name.isNotEmpty) {
|
||||
try {
|
||||
await ref
|
||||
.read(
|
||||
supabaseClientProvider,
|
||||
)
|
||||
.from('clients')
|
||||
.upsert({'name': name});
|
||||
} catch (_) {}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
suggestionsCallback: (pattern) async {
|
||||
final profiles =
|
||||
ref
|
||||
.watch(profilesProvider)
|
||||
.valueOrNull ??
|
||||
[];
|
||||
final fromProfiles = profiles
|
||||
.map(
|
||||
(p) => p.fullName.isEmpty
|
||||
? p.id
|
||||
: p.fullName,
|
||||
)
|
||||
.where(
|
||||
(n) => n.toLowerCase().contains(
|
||||
pattern.toLowerCase(),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
try {
|
||||
final clientRows = await ref
|
||||
.read(supabaseClientProvider)
|
||||
.from('clients')
|
||||
.select('name')
|
||||
.ilike('name', '%$pattern%');
|
||||
final clientNames =
|
||||
(clientRows as List<dynamic>?)
|
||||
?.map(
|
||||
(r) => r['name'] as String,
|
||||
)
|
||||
.whereType<String>()
|
||||
.toList() ??
|
||||
<String>[];
|
||||
final merged = {
|
||||
...fromProfiles,
|
||||
...clientNames,
|
||||
}.toList();
|
||||
return merged;
|
||||
} catch (_) {
|
||||
return fromProfiles;
|
||||
}
|
||||
},
|
||||
itemBuilder: (context, suggestion) =>
|
||||
ListTile(title: Text(suggestion)),
|
||||
onSuggestionSelected: (suggestion) async {
|
||||
_notedDebounce?.cancel();
|
||||
_notedController.text = suggestion;
|
||||
await ref
|
||||
.read(tasksControllerProvider)
|
||||
.updateTask(
|
||||
taskId: task.id,
|
||||
notedBy: suggestion.isEmpty
|
||||
? null
|
||||
: suggestion,
|
||||
);
|
||||
try {
|
||||
if (suggestion.isNotEmpty) {
|
||||
await ref
|
||||
.read(supabaseClientProvider)
|
||||
.from('clients')
|
||||
.upsert({'name': suggestion});
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Received by',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
TypeAheadFormField<String>(
|
||||
textFieldConfiguration:
|
||||
TextFieldConfiguration(
|
||||
controller: _receivedController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Receiver name or id',
|
||||
),
|
||||
onChanged: (v) {
|
||||
_receivedDebounce?.cancel();
|
||||
_receivedDebounce = Timer(
|
||||
const Duration(milliseconds: 700),
|
||||
() async {
|
||||
final name = v.trim();
|
||||
await ref
|
||||
.read(
|
||||
tasksControllerProvider,
|
||||
)
|
||||
.updateTask(
|
||||
taskId: task.id,
|
||||
receivedBy: name.isEmpty
|
||||
? null
|
||||
: name,
|
||||
);
|
||||
if (name.isNotEmpty) {
|
||||
try {
|
||||
await ref
|
||||
.read(
|
||||
supabaseClientProvider,
|
||||
)
|
||||
.from('clients')
|
||||
.upsert({'name': name});
|
||||
} catch (_) {}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
suggestionsCallback: (pattern) async {
|
||||
final profiles =
|
||||
ref
|
||||
.watch(profilesProvider)
|
||||
.valueOrNull ??
|
||||
[];
|
||||
final fromProfiles = profiles
|
||||
.map(
|
||||
(p) => p.fullName.isEmpty
|
||||
? p.id
|
||||
: p.fullName,
|
||||
)
|
||||
.where(
|
||||
(n) => n.toLowerCase().contains(
|
||||
pattern.toLowerCase(),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
try {
|
||||
final clientRows = await ref
|
||||
.read(supabaseClientProvider)
|
||||
.from('clients')
|
||||
.select('name')
|
||||
.ilike('name', '%$pattern%');
|
||||
final clientNames =
|
||||
(clientRows as List<dynamic>?)
|
||||
?.map(
|
||||
(r) => r['name'] as String,
|
||||
)
|
||||
.whereType<String>()
|
||||
.toList() ??
|
||||
<String>[];
|
||||
final merged = {
|
||||
...fromProfiles,
|
||||
...clientNames,
|
||||
}.toList();
|
||||
return merged;
|
||||
} catch (_) {
|
||||
return fromProfiles;
|
||||
}
|
||||
},
|
||||
itemBuilder: (context, suggestion) =>
|
||||
ListTile(title: Text(suggestion)),
|
||||
onSuggestionSelected: (suggestion) async {
|
||||
_receivedDebounce?.cancel();
|
||||
_receivedController.text = suggestion;
|
||||
await ref
|
||||
.read(tasksControllerProvider)
|
||||
.updateTask(
|
||||
taskId: task.id,
|
||||
receivedBy: suggestion.isEmpty
|
||||
? null
|
||||
: suggestion,
|
||||
);
|
||||
try {
|
||||
if (suggestion.isNotEmpty) {
|
||||
await ref
|
||||
.read(supabaseClientProvider)
|
||||
.from('clients')
|
||||
.upsert({'name': suggestion});
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -630,7 +1127,10 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||
}
|
||||
|
||||
return StreamBuilder<int>(
|
||||
stream: Stream.periodic(const Duration(seconds: 1), (tick) => tick),
|
||||
stream: Stream.periodic(
|
||||
const Duration(seconds: 1),
|
||||
(tick) => tick,
|
||||
).asBroadcastStream(),
|
||||
builder: (context, snapshot) {
|
||||
return _buildTatContent(task, AppTime.now());
|
||||
},
|
||||
|
||||
@@ -20,6 +20,21 @@ import '../../widgets/tasq_adaptive_list.dart';
|
||||
import '../../widgets/typing_dots.dart';
|
||||
import '../../theme/app_surfaces.dart';
|
||||
|
||||
// request metadata options used in task creation/editing dialogs
|
||||
const List<String> _requestTypeOptions = [
|
||||
'Install',
|
||||
'Repair',
|
||||
'Upgrade',
|
||||
'Replace',
|
||||
'Other',
|
||||
];
|
||||
|
||||
const List<String> _requestCategoryOptions = [
|
||||
'Software',
|
||||
'Hardware',
|
||||
'Network',
|
||||
];
|
||||
|
||||
class TasksListScreen extends ConsumerStatefulWidget {
|
||||
const TasksListScreen({super.key});
|
||||
|
||||
@@ -402,6 +417,9 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
|
||||
final titleController = TextEditingController();
|
||||
final descriptionController = TextEditingController();
|
||||
String? selectedOfficeId;
|
||||
String? selectedRequestType;
|
||||
String? requestTypeOther;
|
||||
String? selectedRequestCategory;
|
||||
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
@@ -438,21 +456,71 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
|
||||
return const Text('No offices available.');
|
||||
}
|
||||
selectedOfficeId ??= offices.first.id;
|
||||
return DropdownButtonFormField<String>(
|
||||
initialValue: selectedOfficeId,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Office',
|
||||
),
|
||||
items: offices
|
||||
.map(
|
||||
(office) => DropdownMenuItem(
|
||||
value: office.id,
|
||||
child: Text(office.name),
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: selectedOfficeId,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Office',
|
||||
),
|
||||
items: offices
|
||||
.map(
|
||||
(office) => DropdownMenuItem(
|
||||
value: office.id,
|
||||
child: Text(office.name),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) =>
|
||||
setState(() => selectedOfficeId = value),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// optional request metadata inputs
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: selectedRequestType,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Request type (optional)',
|
||||
),
|
||||
items: _requestTypeOptions
|
||||
.map(
|
||||
(t) => DropdownMenuItem(
|
||||
value: t,
|
||||
child: Text(t),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) =>
|
||||
setState(() => selectedRequestType = value),
|
||||
),
|
||||
if (selectedRequestType == 'Other') ...[
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Please specify',
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) =>
|
||||
setState(() => selectedOfficeId = value),
|
||||
onChanged: (v) => requestTypeOther = v,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: selectedRequestCategory,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Request category (optional)',
|
||||
),
|
||||
items: _requestCategoryOptions
|
||||
.map(
|
||||
(t) => DropdownMenuItem(
|
||||
value: t,
|
||||
child: Text(t),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) => setState(
|
||||
() => selectedRequestCategory = value,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
loading: () => const Align(
|
||||
@@ -484,6 +552,9 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
|
||||
title: title,
|
||||
description: description,
|
||||
officeId: officeId,
|
||||
requestType: selectedRequestType,
|
||||
requestTypeOther: requestTypeOther,
|
||||
requestCategory: selectedRequestCategory,
|
||||
);
|
||||
if (context.mounted) {
|
||||
Navigator.of(dialogContext).pop();
|
||||
|
||||
@@ -477,7 +477,7 @@ class _ScheduleTile extends ConsumerWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DropdownButtonFormField<String>(
|
||||
value: selectedId,
|
||||
initialValue: selectedId,
|
||||
items: [
|
||||
for (final profile in staff)
|
||||
DropdownMenuItem(
|
||||
@@ -515,7 +515,7 @@ class _ScheduleTile extends ConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
value: selectedTargetShiftId,
|
||||
initialValue: selectedTargetShiftId,
|
||||
items: [
|
||||
for (final s in recipientShifts)
|
||||
DropdownMenuItem(
|
||||
@@ -553,11 +553,14 @@ class _ScheduleTile extends ConsumerWidget {
|
||||
},
|
||||
);
|
||||
|
||||
if (!context.mounted) return;
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
if (confirmed != true ||
|
||||
selectedId == null ||
|
||||
selectedTargetShiftId == null)
|
||||
selectedTargetShiftId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ref
|
||||
@@ -1976,7 +1979,7 @@ class _SwapRequestsPanel extends ConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
Profile? _choice = eligible.first;
|
||||
Profile? choice = eligible.first;
|
||||
final selected = await showDialog<Profile?>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
@@ -1984,13 +1987,13 @@ class _SwapRequestsPanel extends ConsumerWidget {
|
||||
title: const Text('Change recipient'),
|
||||
content: StatefulBuilder(
|
||||
builder: (context, setState) => DropdownButtonFormField<Profile>(
|
||||
value: _choice,
|
||||
initialValue: choice,
|
||||
items: eligible
|
||||
.map(
|
||||
(p) => DropdownMenuItem(value: p, child: Text(p.fullName)),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (v) => setState(() => _choice = v),
|
||||
onChanged: (v) => setState(() => choice = v),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
@@ -1999,7 +2002,7 @@ class _SwapRequestsPanel extends ConsumerWidget {
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(_choice),
|
||||
onPressed: () => Navigator.of(context).pop(choice),
|
||||
child: const Text('Save'),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user