bottleneckcrm/mobile/lib/widgets/common/export_csv_button.dart
Ashwin e26ab380fc Add unit tests for ticket board provider, status settable, web forms, CSV import, deal aging badge, and tickets selection
- Implement comprehensive tests for the ticket board provider to validate kanban and pipeline modes, including ticket movement and error handling.
- Create tests for ticket status settable to ensure proper status selection logic.
- Enhance web form model tests to cover ticket-specific fields and submission behavior.
- Introduce CSV import sheet tests to verify file handling and error reporting.
- Add deal aging badge tests to confirm correct display of aging statuses.
- Implement tickets selection tests to ensure UI responsiveness and functionality at various screen sizes.
2026-09-26 16:53:22 +05:30

88 lines
2.5 KiB
Dart

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import '../../services/api_service.dart';
import '../../services/record_export.dart';
/// "Export CSV", in the app bar of every record list.
///
/// [query] is read at the moment of the tap, from the same state the list was
/// fetched with, so the file holds the rows the screen is showing with every
/// filter applied, all pages of them. Offered to every member, as on the web:
/// the API decides what each caller's file contains.
///
/// Disabled while a download is in flight, so a second tap cannot start a
/// second one.
class ExportCsvButton extends StatefulWidget {
const ExportCsvButton({
super.key,
required this.endpoint,
required this.filePrefix,
required this.query,
this.api,
this.saveFile,
});
final String endpoint;
final String filePrefix;
final Future<Map<String, Object?>> Function() query;
/// Injectable for tests; the real ones are used otherwise.
final ApiService? api;
final Future<String?> Function(Uint8List bytes, String fileName)? saveFile;
@override
State<ExportCsvButton> createState() => _ExportCsvButtonState();
}
class _ExportCsvButtonState extends State<ExportCsvButton> {
bool _busy = false;
Future<void> _export() async {
setState(() => _busy = true);
RecordExportResult result;
try {
result = await exportRecordsCsv(
endpoint: widget.endpoint,
query: await widget.query(),
filePrefix: widget.filePrefix,
api: widget.api,
saveFile: widget.saveFile,
);
} catch (_) {
result = const RecordExportResult(
RecordExportOutcome.failed,
error: 'Could not export.',
);
}
if (!mounted) return;
setState(() => _busy = false);
final message = switch (result.outcome) {
RecordExportOutcome.saved => 'Saved ${result.fileName}',
RecordExportOutcome.failed => result.error ?? 'Could not export.',
RecordExportOutcome.cancelled => null,
};
if (message != null) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
}
}
@override
Widget build(BuildContext context) {
return IconButton(
tooltip: 'Export CSV',
onPressed: _busy ? null : _export,
icon: _busy
? const SizedBox(
height: 18,
width: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(LucideIcons.download),
);
}
}