A better state and alerts

This commit is contained in:
2026-02-23 20:19:07 +08:00
parent 0b900d3480
commit 1074572905
17 changed files with 673 additions and 428 deletions
+50 -28
View File
@@ -16,6 +16,7 @@ import '../../widgets/responsive_body.dart';
import '../../widgets/tasq_adaptive_list.dart';
import '../../widgets/typing_dots.dart';
import '../../theme/app_surfaces.dart';
import '../../utils/snackbar.dart';
class TicketsListScreen extends ConsumerStatefulWidget {
const TicketsListScreen({super.key});
@@ -319,6 +320,7 @@ class _TicketsListScreenState extends ConsumerState<TicketsListScreen> {
await showDialog<void>(
context: context,
builder: (dialogContext) {
bool saving = false;
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
@@ -337,6 +339,7 @@ class _TicketsListScreenState extends ConsumerState<TicketsListScreen> {
decoration: const InputDecoration(
labelText: 'Subject',
),
enabled: !saving,
),
const SizedBox(height: 12),
TextField(
@@ -345,6 +348,7 @@ class _TicketsListScreenState extends ConsumerState<TicketsListScreen> {
labelText: 'Description',
),
maxLines: 3,
enabled: !saving,
),
const SizedBox(height: 12),
officesAsync.when(
@@ -364,8 +368,10 @@ class _TicketsListScreenState extends ConsumerState<TicketsListScreen> {
),
)
.toList(),
onChanged: (value) =>
setState(() => selectedOffice = value),
onChanged: saving
? null
: (value) =>
setState(() => selectedOffice = value),
decoration: const InputDecoration(
labelText: 'Office',
),
@@ -382,35 +388,51 @@ class _TicketsListScreenState extends ConsumerState<TicketsListScreen> {
),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(),
onPressed: saving
? null
: () => Navigator.of(dialogContext).pop(),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () async {
final subject = subjectController.text.trim();
final description = descriptionController.text.trim();
if (subject.isEmpty ||
description.isEmpty ||
selectedOffice == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Fill out all fields.')),
);
return;
}
await ref
.read(ticketsControllerProvider)
.createTicket(
subject: subject,
description: description,
officeId: selectedOffice!.id,
);
// Supabase stream will emit the new ticket — no explicit
// invalidation required and avoids a temporary reload.
if (context.mounted) {
Navigator.of(dialogContext).pop();
}
},
child: const Text('Create'),
onPressed: saving
? null
: () async {
final subject = subjectController.text.trim();
final description = descriptionController.text.trim();
if (subject.isEmpty ||
description.isEmpty ||
selectedOffice == null) {
showWarningSnackBar(
context,
'Fill out all fields.',
);
return;
}
setState(() => saving = true);
await ref
.read(ticketsControllerProvider)
.createTicket(
subject: subject,
description: description,
officeId: selectedOffice!.id,
);
// Supabase stream will emit the new ticket — no explicit
// invalidation required and avoids a temporary reload.
if (context.mounted) {
Navigator.of(dialogContext).pop();
showSuccessSnackBar(
context,
'Ticket "$subject" has been created successfully.',
);
}
},
child: saving
? const SizedBox(
height: 18,
width: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Create'),
),
],
);