tasks
This commit is contained in:
272
lib/features/tasks/ui/task_list_screen.dart
Normal file
272
lib/features/tasks/ui/task_list_screen.dart
Normal file
@@ -0,0 +1,272 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/features/tasks/blocs/task_list_cubit.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flux/core/routes/routes.dart';
|
||||
import 'package:flux/features/tasks/models/task_status.dart'; // Adegua al tuo path
|
||||
import 'package:flux/features/tasks/models/task_model.dart';
|
||||
|
||||
class TaskListScreen extends StatelessWidget {
|
||||
const TaskListScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Usiamo 3 tab per gli stati principali
|
||||
return DefaultTabController(
|
||||
length: 3,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Gestione Task'),
|
||||
bottom: const TabBar(
|
||||
indicatorColor: Colors.orange,
|
||||
labelColor: Colors.orange,
|
||||
tabs: [
|
||||
Tab(text: 'Da Fare'),
|
||||
Tab(text: 'In Corso'),
|
||||
Tab(text: 'Completati'),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () => context.read<TaskListCubit>().loadTasks(),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: BlocBuilder<TaskListCubit, TaskListState>(
|
||||
builder: (context, state) {
|
||||
if (state.status == TaskListStatus.loading ||
|
||||
state.status == TaskListStatus.initial) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (state.status == TaskListStatus.failure) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 48,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(state.errorMessage ?? 'Errore sconosciuto'),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () =>
|
||||
context.read<TaskListCubit>().loadTasks(),
|
||||
child: const Text('Riprova'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Filtriamo le 3 liste in memoria per ogni Tab
|
||||
final todoTasks = state.tasks
|
||||
.where((t) => t.status == TaskStatus.open)
|
||||
.toList();
|
||||
final inProgressTasks = state.tasks
|
||||
.where((t) => t.status == TaskStatus.inProgress)
|
||||
.toList();
|
||||
final doneTasks = state.tasks
|
||||
.where((t) => t.status == TaskStatus.completed)
|
||||
.toList(); // Adegua in base ai tuoi enum
|
||||
|
||||
return TabBarView(
|
||||
children: [
|
||||
_buildTaskList(context, todoTasks, 'Nessun task da fare. 🎉'),
|
||||
_buildTaskList(
|
||||
context,
|
||||
inProgressTasks,
|
||||
'Nessun task in lavorazione.',
|
||||
),
|
||||
_buildTaskList(context, doneTasks, 'Nessun task completato.'),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
backgroundColor: Colors.orange,
|
||||
onPressed: () =>
|
||||
context.pushNamed(Routes.taskForm, pathParameters: {'id': 'new'}),
|
||||
icon: const Icon(Icons.add, color: Colors.white),
|
||||
label: const Text(
|
||||
'Nuovo Task',
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// --- WIDGET LISTA ---
|
||||
Widget _buildTaskList(
|
||||
BuildContext context,
|
||||
List<TaskModel> tasks,
|
||||
String emptyMessage,
|
||||
) {
|
||||
if (tasks.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
emptyMessage,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => context.read<TaskListCubit>().loadTasks(),
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16,
|
||||
bottom: 80,
|
||||
left: 16,
|
||||
right: 16,
|
||||
), // Padding bottom per il FAB
|
||||
itemCount: tasks.length,
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, index) {
|
||||
final task = tasks[index];
|
||||
final isOverdue =
|
||||
task.dueDate != null && task.dueDate!.isBefore(DateTime.now());
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).dividerColor.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () => context.pushNamed(
|
||||
Routes.taskForm,
|
||||
pathParameters: {'id': task.id!},
|
||||
extra: task,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Riga 1: Badge Globale/Store + Data Scadenza
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: task.storeId == null
|
||||
? Colors.purple.withValues(alpha: 0.1)
|
||||
: Colors.blue.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
task.storeId == null ? 'GLOBALE' : 'STORE',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: task.storeId == null
|
||||
? Colors.purple
|
||||
: Colors.blue,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (task.dueDate != null)
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.access_time,
|
||||
size: 14,
|
||||
color: isOverdue ? Colors.red : Colors.grey,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${task.dueDate!.day}/${task.dueDate!.month}/${task.dueDate!.year}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: isOverdue
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color: isOverdue ? Colors.red : Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Riga 2: Titolo
|
||||
Text(
|
||||
task.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
// Riga 3 (Opzionale): Descrizione breve
|
||||
if (task.description != null &&
|
||||
task.description!.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
task.description!,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Riga 4: Assegnatari
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.people_outline,
|
||||
size: 16,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
(task.assignedToStaff.isEmpty)
|
||||
? 'Nessun assegnatario'
|
||||
: task.assignedToStaff
|
||||
.map((s) => s.name)
|
||||
.join(', '),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user