Implemented subject suggestions in Task Creation and Edit.

Fix editing redirects
This commit is contained in:
2026-03-03 18:39:01 +08:00
parent bfcca47353
commit b1f5d209a2
2 changed files with 152 additions and 81 deletions
+115 -75
View File
@@ -890,6 +890,8 @@ class _TicketDetailScreenState extends ConsumerState<TicketDetailScreen> {
WidgetRef ref,
Ticket ticket,
) async {
final screenContext = context;
final dialogShape = AppSurfaces.of(context).dialogShape;
final officesAsync = ref.watch(officesOnceProvider);
final subjectCtrl = TextEditingController(text: ticket.subject);
final descCtrl = TextEditingController(text: ticket.description);
@@ -898,85 +900,123 @@ class _TicketDetailScreenState extends ConsumerState<TicketDetailScreen> {
await showDialog<void>(
context: context,
builder: (dialogContext) {
return AlertDialog(
shape: AppSurfaces.of(context).dialogShape,
title: const Text('Edit Ticket'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: subjectCtrl,
decoration: const InputDecoration(labelText: 'Subject'),
var saving = false;
return StatefulBuilder(
builder: (dialogBuilderContext, setDialogState) {
return AlertDialog(
shape: dialogShape,
title: const Text('Edit Ticket'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: subjectCtrl,
enabled: !saving,
decoration: const InputDecoration(labelText: 'Subject'),
),
const SizedBox(height: 8),
TextField(
controller: descCtrl,
enabled: !saving,
decoration: const InputDecoration(
labelText: 'Description',
),
maxLines: 4,
),
const SizedBox(height: 8),
officesAsync.when(
data: (offices) {
final officesSorted = List<Office>.from(offices)
..sort(
(a, b) => a.name.toLowerCase().compareTo(
b.name.toLowerCase(),
),
);
return DropdownButtonFormField<String?>(
initialValue: selectedOffice,
decoration: const InputDecoration(
labelText: 'Office',
),
items: [
const DropdownMenuItem(
value: null,
child: Text('Unassigned'),
),
for (final o in officesSorted)
DropdownMenuItem(
value: o.id,
child: Text(o.name),
),
],
onChanged: saving
? null
: (v) => setDialogState(() => selectedOffice = v),
);
},
loading: () => const SizedBox.shrink(),
error: (error, stack) => const SizedBox.shrink(),
),
],
),
const SizedBox(height: 8),
TextField(
controller: descCtrl,
decoration: const InputDecoration(labelText: 'Description'),
maxLines: 4,
),
actions: [
TextButton(
onPressed: saving
? null
: () => Navigator.of(dialogContext).pop(),
child: const Text('Cancel'),
),
const SizedBox(height: 8),
officesAsync.when(
data: (offices) {
final officesSorted = List<Office>.from(offices)
..sort(
(a, b) => a.name.toLowerCase().compareTo(
b.name.toLowerCase(),
),
);
return DropdownButtonFormField<String?>(
initialValue: selectedOffice,
decoration: const InputDecoration(labelText: 'Office'),
items: [
const DropdownMenuItem(
value: null,
child: Text('Unassigned'),
),
for (final o in officesSorted)
DropdownMenuItem(value: o.id, child: Text(o.name)),
],
onChanged: (v) => selectedOffice = v,
);
},
loading: () => const SizedBox.shrink(),
error: (error, stack) => const SizedBox.shrink(),
ElevatedButton(
onPressed: saving
? null
: () async {
final subject = subjectCtrl.text.trim();
final desc = descCtrl.text.trim();
setDialogState(() => saving = true);
try {
await ref
.read(ticketsControllerProvider)
.updateTicket(
ticketId: ticket.id,
subject: subject.isEmpty ? null : subject,
description: desc.isEmpty ? null : desc,
officeId: selectedOffice,
);
ref.invalidate(ticketsProvider);
ref.invalidate(ticketByIdProvider(ticket.id));
if (!dialogContext.mounted ||
!screenContext.mounted) {
return;
}
Navigator.of(dialogContext).pop();
showSuccessSnackBar(
screenContext,
'Ticket updated',
);
} catch (e) {
if (!screenContext.mounted) return;
showErrorSnackBar(
screenContext,
'Failed to update ticket: $e',
);
} finally {
if (dialogContext.mounted) {
setDialogState(() => saving = false);
}
}
},
child: saving
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Save'),
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () async {
final subject = subjectCtrl.text.trim();
final desc = descCtrl.text.trim();
try {
await ref
.read(ticketsControllerProvider)
.updateTicket(
ticketId: ticket.id,
subject: subject.isEmpty ? null : subject,
description: desc.isEmpty ? null : desc,
officeId: selectedOffice,
);
if (!mounted) return;
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pop();
showSuccessSnackBar(context, 'Ticket updated');
});
} catch (e) {
if (!mounted) return;
WidgetsBinding.instance.addPostFrameCallback((_) {
showErrorSnackBar(context, 'Failed to update ticket: $e');
});
}
},
child: const Text('Save'),
),
],
);
},
);
},
);