This commit is contained in:
2026-05-29 12:26:41 +02:00
parent 6211cc6729
commit 5ad3e12b1f
18 changed files with 1303 additions and 372 deletions

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/features/tasks/blocs/task_form_cubit.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
class TaskFormScreen extends StatefulWidget {
@@ -31,6 +33,62 @@ class _TaskFormScreenState extends State<TaskFormScreen> {
super.dispose();
}
void _showAddReminderDialog(BuildContext context, TaskFormCubit cubit) {
int minutes = 15;
String channel = 'push';
showDialog(
context: context,
builder: (dialogContext) {
return AlertDialog(
title: const Text('Aggiungi Promemoria'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
DropdownButtonFormField<int>(
initialValue: minutes,
decoration: const InputDecoration(labelText: 'Preavviso'),
items: const [
DropdownMenuItem(value: 5, child: Text('5 minuti prima')),
DropdownMenuItem(value: 15, child: Text('15 minuti prima')),
DropdownMenuItem(value: 60, child: Text('1 ora prima')),
DropdownMenuItem(value: 1440, child: Text('1 giorno prima')),
],
onChanged: (v) => {if (v != null) minutes = v},
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
initialValue: channel,
decoration: const InputDecoration(labelText: 'Canale'),
items: const [
DropdownMenuItem(value: 'push', child: Text('Notifica Push')),
DropdownMenuItem(value: 'email', child: Text('Email')),
],
onChanged: (v) => {if (v != null) channel = v},
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text('Annulla'),
),
TextButton(
onPressed: () {
cubit.addReminderRule(minutes, channel);
Navigator.pop(dialogContext);
},
child: const Text(
'Inserisci',
style: TextStyle(color: Colors.orange),
),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return BlocConsumer<TaskFormCubit, TaskFormState>(
@@ -79,7 +137,13 @@ class _TaskFormScreenState extends State<TaskFormScreen> {
else
TextButton.icon(
onPressed: state.isFormValid
? () => cubit.saveTask(currentUserId: 'TODO_USER_ID')
? () => cubit.saveTask(
currentUserId: GetIt.I
.get<SessionCubit>()
.state
.currentStaffMember!
.id!,
)
: null,
icon: const Icon(Icons.save),
label: const Text('Salva'),
@@ -162,84 +226,181 @@ class _TaskFormScreenState extends State<TaskFormScreen> {
TaskFormState state,
TaskFormCubit cubit,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: Theme.of(context).dividerColor.withValues(alpha: 0.2),
return FocusTraversalGroup(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: Theme.of(context).dividerColor.withValues(alpha: 0.2),
),
),
child: SwitchListTile(
title: const Text(
'Task Globale Aziendale',
style: TextStyle(fontWeight: FontWeight.bold),
),
subtitle: const Text(
'Visibile a tutta l\'azienda, non legato a un negozio specifico.',
),
value: state.isGlobal,
activeThumbColor: Colors.orange,
onChanged: (val) => cubit.toggleGlobalScope(val),
),
),
child: SwitchListTile(
title: const Text(
'Task Globale Aziendale',
style: TextStyle(fontWeight: FontWeight.bold),
),
subtitle: const Text(
'Visibile a tutta l\'azienda, non legato a un negozio specifico.',
),
value: state.isGlobal,
activeThumbColor: Colors.orange,
onChanged: (val) => cubit.toggleGlobalScope(val),
),
),
const SizedBox(height: 24),
const SizedBox(height: 24),
// Addio initialValue, benvenuto controller!
TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: 'Titolo del Task*',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.title),
// Addio initialValue, benvenuto controller!
TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: 'Titolo del Task*',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.title),
),
onChanged: cubit.updateTitle,
),
onChanged: cubit.updateTitle,
),
const SizedBox(height: 16),
TextFormField(
controller: _descController,
maxLines: 4,
decoration: const InputDecoration(
labelText: 'Descrizione (opzionale)',
border: OutlineInputBorder(),
alignLabelWithHint: true,
const SizedBox(height: 16),
TextFormField(
controller: _descController,
maxLines: 4,
decoration: const InputDecoration(
labelText: 'Descrizione (opzionale)',
border: OutlineInputBorder(),
alignLabelWithHint: true,
),
onChanged: cubit.updateDescription,
),
onChanged: cubit.updateDescription,
),
const SizedBox(height: 24),
const SizedBox(height: 24),
// --- SCADENZA ---
ListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Theme.of(context).dividerColor),
// --- SCADENZA ---
ListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Theme.of(context).dividerColor),
),
leading: const Icon(Icons.calendar_today, color: Colors.orange),
title: Text(
state.dueDate != null
// Formattiamo aggiungendo gli zeri (es. 05/09/2026 alle 09:05)
? 'Scadenza: ${state.dueDate!.day.toString().padLeft(2, '0')}/${state.dueDate!.month.toString().padLeft(2, '0')}/${state.dueDate!.year} alle ${state.dueDate!.hour.toString().padLeft(2, '0')}:${state.dueDate!.minute.toString().padLeft(2, '0')}'
: 'Nessuna scadenza impostata',
),
trailing: state.dueDate != null
? IconButton(
icon: const Icon(Icons.close),
onPressed: () => cubit.updateDueDate(null),
)
: null,
onTap: () async {
// 1. Chiediamo prima la Data
final date = await showDatePicker(
context: context,
initialDate: state.dueDate ?? DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime(2100),
);
// Se l'utente chiude il calendario senza scegliere, ci fermiamo
if (date == null || !context.mounted) return;
// 2. Chiediamo subito dopo l'Orario
final time = await showTimePicker(
context: context,
initialTime: state.dueDate != null
? TimeOfDay.fromDateTime(state.dueDate!)
: const TimeOfDay(hour: 9, minute: 0), // Default ore 09:00
);
// Se l'utente chiude l'orologio senza scegliere, ci fermiamo
if (time == null) return;
// 3. Fondiamo Data e Ora in un nuovo oggetto DateTime
final finalDateTime = DateTime(
date.year,
date.month,
date.day,
time.hour,
time.minute,
);
// Aggiorniamo lo stato tramite il Cubit
cubit.updateDueDate(finalDateTime);
},
),
leading: const Icon(Icons.calendar_today, color: Colors.orange),
title: Text(
state.dueDate != null
? 'Scadenza: ${state.dueDate!.day}/${state.dueDate!.month}/${state.dueDate!.year}'
: 'Nessuna scadenza impostata',
),
trailing: state.dueDate != null
? IconButton(
icon: const Icon(Icons.close),
onPressed: () => cubit.updateDueDate(null),
)
: null,
onTap: () async {
final date = await showDatePicker(
context: context,
initialDate: state.dueDate ?? DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime(2100),
);
if (date != null) cubit.updateDueDate(date);
},
),
],
if (state.dueDate != null) ...[
const SizedBox(height: 24),
Text(
'Promemoria del Task',
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: Theme.of(context).dividerColor.withValues(alpha: 0.3),
),
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
// Elenco dei promemoria attuali del form
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: state.reminders.length,
itemBuilder: (context, index) {
final reminder = state.reminders[index];
final isPush = reminder.channel == 'push';
return ListTile(
dense: true,
leading: Icon(
isPush
? Icons.notifications_active_outlined
: Icons.mail_outline,
color: isPush ? Colors.orange : Colors.blue,
),
title: Text(
reminder.friendlyTime,
style: const TextStyle(fontWeight: FontWeight.w600),
),
trailing: IconButton(
icon: const Icon(
Icons.close,
size: 18,
color: Colors.redAccent,
),
onPressed: () => cubit.removeReminderRule(index),
),
);
},
),
const Divider(),
// Tasto di aggiunta rapida promemoria
TextButton.icon(
onPressed: () => _showAddReminderDialog(context, cubit),
icon: const Icon(Icons.add, size: 18),
label: const Text('Aggiungi un promemoria a questo task'),
style: TextButton.styleFrom(
foregroundColor: Colors.orange,
),
),
],
),
),
),
],
],
),
);
}