hwhub/lib/screens/my_reports_screen.dart

241 lines
7.1 KiB
Dart

import 'package:flutter/material.dart';
import '../main.dart';
import '../theme/app_colors.dart';
import '../utils/error_utils.dart';
import '../utils/reporting_utils.dart';
class MyReportsScreen extends StatefulWidget {
const MyReportsScreen({super.key});
@override
State<MyReportsScreen> createState() => _MyReportsScreenState();
}
class _MyReportsScreenState extends State<MyReportsScreen> {
bool _isLoading = true;
String? _error;
List<Map<String, dynamic>> _reports = [];
@override
void initState() {
super.initState();
_loadReports();
}
Future<void> _loadReports() async {
final user = supabase.auth.currentUser;
if (user == null) return;
setState(() {
_isLoading = true;
_error = null;
});
try {
final rows = await supabase
.from('car_reports')
.select('id, hw_id, reason, note, status, created_at, global_cars(name, series)')
.eq('reporter_user_id', user.id)
.order('created_at', ascending: false);
if (!mounted) return;
setState(() {
_reports = List<Map<String, dynamic>>.from(rows);
_isLoading = false;
});
} catch (e) {
if (!mounted) return;
setState(() {
_error = userMessageForError(
e,
fallback: 'Failed to load reports. Please try again.',
);
_isLoading = false;
});
logError('reports.load', e);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('My Reports'),
),
body: RefreshIndicator(
onRefresh: _loadReports,
child: _buildBody(),
),
);
}
Widget _buildBody() {
if (_isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (_error != null) {
return ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: [
const SizedBox(height: 120),
Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: [
const Icon(Icons.error_outline, color: AppColors.error, size: 42),
const SizedBox(height: 10),
Text(
_error!,
textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.textSecondary),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: _loadReports,
child: const Text('Retry'),
),
],
),
),
),
],
);
}
if (_reports.isEmpty) {
return ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: const [
SizedBox(height: 130),
Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Column(
children: [
Icon(Icons.flag_outlined, size: 48, color: AppColors.textHint),
SizedBox(height: 12),
Text(
'No reports yet',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
),
SizedBox(height: 6),
Text(
'When you report a catalog issue, it will appear here.',
textAlign: TextAlign.center,
style: TextStyle(color: AppColors.textSecondary),
),
],
),
),
),
],
);
}
return ListView.separated(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.fromLTRB(12, 12, 12, 24),
itemBuilder: (_, index) {
final report = _reports[index];
final global = report['global_cars'] as Map<String, dynamic>?;
final status = (report['status'] as String? ?? 'open').toLowerCase();
final createdAt = DateTime.tryParse(report['created_at'] as String? ?? '');
final note = report['note'] as String?;
return Card(
child: Padding(
padding: const EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
report['hw_id'] as String? ?? 'Unknown ID',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
letterSpacing: 0.4,
),
),
),
_StatusChip(status: status),
],
),
if ((global?['name'] as String?) != null) ...[
const SizedBox(height: 6),
Text(
global?['name'] as String,
style: const TextStyle(
fontWeight: FontWeight.w600,
color: AppColors.textSecondary,
),
),
],
const SizedBox(height: 8),
Text(
'Reason: ${report['reason'] as String? ?? 'Unknown'}',
style: const TextStyle(color: AppColors.textSecondary),
),
if (note != null && note.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
note,
style: const TextStyle(color: AppColors.textHint),
),
],
if (createdAt != null) ...[
const SizedBox(height: 10),
Text(
formatReportDateTime(createdAt),
style: const TextStyle(fontSize: 12, color: AppColors.textHint),
),
],
],
),
),
);
},
separatorBuilder: (context, index) => const SizedBox(height: 8),
itemCount: _reports.length,
);
}
}
class _StatusChip extends StatelessWidget {
final String status;
const _StatusChip({required this.status});
@override
Widget build(BuildContext context) {
final normalized = normalizeReportStatus(status);
final label = reportStatusLabel(normalized);
final color = switch (normalized) {
ReportStatus.reviewed => Colors.blueGrey,
ReportStatus.resolved => AppColors.success,
ReportStatus.dismissed => AppColors.error,
ReportStatus.open => AppColors.orange,
};
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(999),
),
child: Text(
label,
style: TextStyle(
color: color,
fontWeight: FontWeight.w600,
fontSize: 12,
),
),
);
}
}