214 lines
7.6 KiB
Dart
214 lines
7.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:open_filex/open_filex.dart';
|
|
import '../../models/project_model.dart';
|
|
import '../../models/user_model.dart';
|
|
import '../../services/report_service.dart';
|
|
import '../../services/task_log_service.dart';
|
|
import '../../services/project_service.dart';
|
|
import '../../services/auth_service.dart';
|
|
|
|
class ReportsTab extends StatefulWidget {
|
|
const ReportsTab({super.key});
|
|
|
|
@override
|
|
State<ReportsTab> createState() => _ReportsTabState();
|
|
}
|
|
|
|
class _ReportsTabState extends State<ReportsTab> {
|
|
DateTime _startDate = DateTime.now().subtract(const Duration(days: 30));
|
|
DateTime _endDate = DateTime.now().add(const Duration(hours: 23, minutes: 59));
|
|
String? _selectedProjectId;
|
|
String? _selectedUserId;
|
|
|
|
bool _isGenerating = false;
|
|
List<ProjectModel> _projects = [];
|
|
List<UserModel> _users = [];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadFilters();
|
|
}
|
|
|
|
void _loadFilters() async {
|
|
final projects = await context.read<ProjectService>().getProjects().first;
|
|
final users = await context.read<AuthService>().getUsers().first;
|
|
if (mounted) {
|
|
setState(() {
|
|
_projects = projects;
|
|
_users = users;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _generateReport(String type) async {
|
|
if (_isGenerating) return;
|
|
setState(() => _isGenerating = true);
|
|
|
|
try {
|
|
final logs = await context.read<TaskLogService>().getTaskLogs(
|
|
from: _startDate,
|
|
to: _endDate,
|
|
projectId: _selectedProjectId,
|
|
userId: _selectedUserId,
|
|
).first;
|
|
|
|
if (logs.isEmpty) {
|
|
if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("No data found for selected filters.")));
|
|
setState(() => _isGenerating = false);
|
|
return;
|
|
}
|
|
|
|
final reportService = context.read<ReportService>();
|
|
dynamic file;
|
|
|
|
if (type == 'pdf') {
|
|
file = await reportService.generatePDF(logs, _startDate, _endDate);
|
|
} else {
|
|
file = await reportService.generateExcel(logs);
|
|
}
|
|
|
|
if (file != null && mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Report generated! Opening..."), backgroundColor: Colors.green));
|
|
await OpenFilex.open(file.path);
|
|
}
|
|
} catch (e) {
|
|
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Report generation failed: $e")));
|
|
} finally {
|
|
if (mounted) setState(() => _isGenerating = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final DateFormat formatter = DateFormat('EEE, dd MMM yyyy');
|
|
|
|
return Scaffold(
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
const Card(
|
|
color: Colors.indigo,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
|
child: Padding(
|
|
padding: EdgeInsets.all(24),
|
|
child: Column(
|
|
children: [
|
|
Icon(Icons.bar_chart, color: Colors.white, size: 48),
|
|
SizedBox(height: 16),
|
|
Text(
|
|
"Generate Analytics Report",
|
|
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 18),
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
"Select filters below to download your team work log report.",
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(color: Colors.white70, fontSize: 14),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 32),
|
|
_buildFilterRow("Date Range", "${formatter.format(_startDate)} - ${formatter.format(_endDate)}", Icons.date_range, _pickDateRange),
|
|
_buildDropdownFilter<String?>(
|
|
"Project",
|
|
_selectedProjectId,
|
|
_projects.map((p) => DropdownMenuItem(value: p.id, child: Text(p.name))).toList(),
|
|
(v) => setState(() => _selectedProjectId = v),
|
|
Icons.folder_outlined,
|
|
),
|
|
_buildDropdownFilter<String?>(
|
|
"Team Member",
|
|
_selectedUserId,
|
|
_users.map((u) => DropdownMenuItem(value: u.uid, child: Text(u.name))).toList(),
|
|
(v) => setState(() => _selectedUserId = v),
|
|
Icons.person_outline,
|
|
),
|
|
const SizedBox(height: 48),
|
|
_isGenerating
|
|
? const Center(child: CircularProgressIndicator())
|
|
: Column(
|
|
children: [
|
|
ElevatedButton.icon(
|
|
onPressed: () => _generateReport('pdf'),
|
|
icon: const Icon(Icons.picture_as_pdf),
|
|
label: const Text("DOWNLOAD PDF"),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.red.shade700,
|
|
foregroundColor: Colors.white,
|
|
minimumSize: const Size(double.infinity, 55),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
OutlinedButton.icon(
|
|
onPressed: () => _generateReport('excel'),
|
|
icon: const Icon(Icons.table_chart),
|
|
label: const Text("DOWNLOAD EXCEL"),
|
|
style: OutlinedButton.styleFrom(
|
|
minimumSize: const Size(double.infinity, 55),
|
|
side: const BorderSide(color: Colors.green, width: 2),
|
|
foregroundColor: Colors.green.shade800,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildFilterRow(String label, String value, IconData icon, VoidCallback onTap) {
|
|
return ListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
leading: CircleAvatar(backgroundColor: Colors.indigo.shade50, child: Icon(icon, color: Colors.indigo, size: 20)),
|
|
title: Text(label, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14)),
|
|
subtitle: Text(value),
|
|
trailing: const Icon(Icons.chevron_right),
|
|
onTap: onTap,
|
|
);
|
|
}
|
|
|
|
Widget _buildDropdownFilter<T>(String label, T value, List<DropdownMenuItem<T>> items, ValueChanged<T> onChanged, IconData icon) {
|
|
return ListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
leading: CircleAvatar(backgroundColor: Colors.indigo.shade50, child: Icon(icon, color: Colors.indigo, size: 20)),
|
|
title: Text(label, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14)),
|
|
subtitle: DropdownButton<T>(
|
|
value: value,
|
|
isExpanded: true,
|
|
underline: const SizedBox(),
|
|
hint: const Text("All"),
|
|
items: [
|
|
DropdownMenuItem<T>(value: null, child: const Text("All")),
|
|
...items,
|
|
],
|
|
onChanged: (v) {
|
|
if (v != null || true) onChanged(v as T);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
void _pickDateRange() async {
|
|
final range = await showDateRangePicker(
|
|
context: context,
|
|
firstDate: DateTime(2024),
|
|
lastDate: DateTime.now(),
|
|
initialDateRange: DateTimeRange(start: _startDate, end: _endDate),
|
|
);
|
|
if (range != null) {
|
|
setState(() {
|
|
_startDate = range.start;
|
|
_endDate = DateTime(range.end.year, range.end.month, range.end.day, 23, 59, 59);
|
|
});
|
|
}
|
|
}
|
|
}
|