x
This commit is contained in:
@@ -14,8 +14,6 @@ import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
|
||||
import 'package:flux/features/notes/data/notes_repository.dart';
|
||||
import 'package:flux/features/notes/models/note_model.dart';
|
||||
import 'package:flux/features/notes/ui/dashboard_notes_widget.dart';
|
||||
import 'package:flux/features/tasks/data/task_repository.dart';
|
||||
import 'package:flux/features/tasks/models/task_model.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -236,11 +234,7 @@ class HomeScreen extends StatelessWidget {
|
||||
label: context.l10n.commonTask,
|
||||
color: Colors.teal,
|
||||
onTap: () {
|
||||
context.pushNamed(
|
||||
Routes.taskForm,
|
||||
pathParameters: {'id': 'new'},
|
||||
extra: (task: null),
|
||||
);
|
||||
context.pushNamed(Routes.taskForm, pathParameters: {'id': 'new'});
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -12,7 +12,22 @@ class StaffCubit extends Cubit<StaffState> {
|
||||
final StaffRepository _repository = GetIt.I.get<StaffRepository>();
|
||||
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
||||
|
||||
StaffCubit() : super(const StaffState());
|
||||
StaffCubit() : super(const StaffState()) {
|
||||
init();
|
||||
}
|
||||
|
||||
Future<void> init() async {
|
||||
emit(state.copyWith(status: StaffStatus.loading, error: null));
|
||||
try {
|
||||
final allStaff = await _repository.getStaffMembers(
|
||||
_sessionCubit.state.company!.id!,
|
||||
);
|
||||
|
||||
emit(state.copyWith(status: StaffStatus.success, allStaff: allStaff));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(status: StaffStatus.error, error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
// Carica tutto lo staff della compagnia
|
||||
Future<void> loadAllStaff() async {
|
||||
|
||||
@@ -15,7 +15,9 @@ class StaffRepository {
|
||||
.from(Tables.staffMembers)
|
||||
.select('''
|
||||
*,
|
||||
stores (*)
|
||||
store_assignments:${Tables.staffInStores} (
|
||||
${Tables.stores}(*)
|
||||
)
|
||||
''')
|
||||
.eq('company_id', companyId)
|
||||
.order('name', ascending: true);
|
||||
@@ -27,7 +29,12 @@ class StaffRepository {
|
||||
try {
|
||||
final response = await _supabase
|
||||
.from(Tables.staffMembers)
|
||||
.select()
|
||||
.select('''
|
||||
*,
|
||||
store_assignments:${Tables.staffInStores} (
|
||||
${Tables.stores}(*)
|
||||
)
|
||||
''')
|
||||
.eq('id', staffId)
|
||||
.single();
|
||||
return StaffMemberModel.fromMap(response);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flux/core/enums_and_consts/consts.dart';
|
||||
import 'package:flux/features/master_data/store/models/store_model.dart';
|
||||
|
||||
// L'Enum magico e blindato per il sistema
|
||||
@@ -27,7 +28,8 @@ class StaffMemberModel extends Equatable {
|
||||
final SystemRole systemRole;
|
||||
final bool isActive;
|
||||
final bool hasJoined;
|
||||
final StoreModel? store;
|
||||
final List<String> assignedStoreIds;
|
||||
final List<StoreModel> assignedStores;
|
||||
|
||||
const StaffMemberModel({
|
||||
this.id,
|
||||
@@ -40,7 +42,8 @@ class StaffMemberModel extends Equatable {
|
||||
this.systemRole = SystemRole.user,
|
||||
this.isActive = true,
|
||||
this.hasJoined = false,
|
||||
this.store,
|
||||
this.assignedStoreIds = const [],
|
||||
this.assignedStores = const [],
|
||||
});
|
||||
|
||||
StaffMemberModel copyWith({
|
||||
@@ -55,7 +58,8 @@ class StaffMemberModel extends Equatable {
|
||||
SystemRole? systemRole,
|
||||
bool? isActive,
|
||||
bool? hasJoined,
|
||||
StoreModel? store,
|
||||
List<String>? assignedStoreIds,
|
||||
List<StoreModel>? assignedStores,
|
||||
}) {
|
||||
return StaffMemberModel(
|
||||
id: id ?? this.id,
|
||||
@@ -68,7 +72,8 @@ class StaffMemberModel extends Equatable {
|
||||
systemRole: systemRole ?? this.systemRole,
|
||||
isActive: isActive ?? this.isActive,
|
||||
hasJoined: hasJoined ?? this.hasJoined,
|
||||
store: store ?? this.store,
|
||||
assignedStoreIds: assignedStoreIds ?? this.assignedStoreIds,
|
||||
assignedStores: assignedStores ?? this.assignedStores,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -84,6 +89,24 @@ class StaffMemberModel extends Equatable {
|
||||
}
|
||||
|
||||
factory StaffMemberModel.fromMap(Map<String, dynamic> map) {
|
||||
// 1. Gestiamo l'array nullo di Supabase trasformandolo in lista vuota
|
||||
final List<String> parsedAssignedStoreIds =
|
||||
map['assigned_store_ids'] != null
|
||||
? List<String>.from(map['assigned_store_ids'])
|
||||
: [];
|
||||
|
||||
// 2. Mappiamo il JOIN degli store, se presente
|
||||
List<StoreModel> storeList = [];
|
||||
|
||||
// Gestione del JSON proveniente dal Join nidificato (es. task_assignments -> staff_members)
|
||||
if (map['store_assignments'] != null) {
|
||||
storeList = (map['store_assignments'] as List)
|
||||
.map((a) => a[Tables.stores])
|
||||
.where((s) => s != null)
|
||||
.map((s) => StoreModel.fromMap(s))
|
||||
.toList();
|
||||
}
|
||||
|
||||
return StaffMemberModel(
|
||||
id: map['id'] as String?,
|
||||
companyId: map['company_id'] ?? '',
|
||||
@@ -95,7 +118,8 @@ class StaffMemberModel extends Equatable {
|
||||
systemRole: SystemRole.fromString(map['system_role']),
|
||||
isActive: map['is_active'] ?? true,
|
||||
hasJoined: map['has_joined'] ?? false,
|
||||
store: map['store'] != null ? StoreModel.fromMap(map['store']) : null,
|
||||
assignedStoreIds: parsedAssignedStoreIds,
|
||||
assignedStores: storeList,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -126,6 +150,7 @@ class StaffMemberModel extends Equatable {
|
||||
systemRole,
|
||||
isActive,
|
||||
hasJoined,
|
||||
store,
|
||||
assignedStoreIds,
|
||||
assignedStores,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flux/core/enums_and_consts/consts.dart';
|
||||
import 'package:flux/core/utils/extensions.dart';
|
||||
import 'package:flux/features/attachments/models/attachment_model.dart';
|
||||
import 'package:flux/features/customers/models/customer_model.dart';
|
||||
@@ -184,10 +185,11 @@ class OperationModel extends Equatable {
|
||||
// I campi relazionali nullabili restano rigorosamente null!
|
||||
providerId: map['provider_id'] as String?,
|
||||
// MAGIA ANTI-CRASH: Usiamo ?['chiave'] per non far esplodere i join vuoti
|
||||
providerDisplayName: (map['provider']?['name'] as String?)?.myFormat(),
|
||||
providerDisplayName: (map[Tables.providers]?['name'] as String?)
|
||||
?.myFormat(),
|
||||
|
||||
modelId: map['model_id'] as String?,
|
||||
modelDisplayName: (map['model']?['name_with_brand'] as String?)
|
||||
modelDisplayName: (map[Tables.models]?['name_with_brand'] as String?)
|
||||
?.myFormat(),
|
||||
|
||||
description: map['description'] as String?,
|
||||
@@ -202,25 +204,26 @@ class OperationModel extends Equatable {
|
||||
storeId:
|
||||
map['store_id'] as String? ??
|
||||
'', // Questo è non-nullable nella tua classe
|
||||
storeDisplayName: (map['store']?['name'] as String?)?.myFormat(),
|
||||
storeDisplayName: (map[Tables.stores]?['name'] as String?)?.myFormat(),
|
||||
|
||||
quantity: map['quantity'] is int
|
||||
? map['quantity']
|
||||
: int.tryParse(map['quantity']?.toString() ?? '1') ?? 1,
|
||||
|
||||
staffId: map['staff_id'] as String?,
|
||||
staffDisplayName: (map['staff_member']?['name'] as String?)?.myFormat(),
|
||||
staffDisplayName: (map[Tables.staffMembers]?['name'] as String?)
|
||||
?.myFormat(),
|
||||
|
||||
lastCampaignId: map['last_campaign_id'] as String?,
|
||||
status: OperationStatus.fromString(map['status'] ?? 'draft'),
|
||||
customerId: map['customer_id'] as String?,
|
||||
|
||||
customer: map['customer'] != null
|
||||
? CustomerModel.fromMap(map['customer'] as Map<String, dynamic>)
|
||||
customer: map[Tables.customers] != null
|
||||
? CustomerModel.fromMap(map[Tables.customers] as Map<String, dynamic>)
|
||||
: null,
|
||||
|
||||
attachments:
|
||||
(map['attachment'] as List?)
|
||||
(map[Tables.attachments] as List?)
|
||||
?.map((x) => AttachmentModel.fromMap(x))
|
||||
.toList() ??
|
||||
const [],
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
@@ -11,55 +10,60 @@ part 'task_form_state.dart';
|
||||
|
||||
class TaskFormCubit extends Cubit<TaskFormState> {
|
||||
final TaskRepository _taskRepository = GetIt.I.get<TaskRepository>();
|
||||
final TaskModel? _initialTask;
|
||||
final String? _initialTaskId;
|
||||
// Cache in memoria proveniente dallo StaffCubit!
|
||||
final List<StaffMemberModel> _globalStaff;
|
||||
|
||||
final String currentCompanyId = GetIt.I
|
||||
.get<SessionCubit>()
|
||||
.state
|
||||
.company!
|
||||
.id!;
|
||||
final String? currentStoreId = GetIt.I
|
||||
.get<SessionCubit>()
|
||||
.state
|
||||
.currentStore
|
||||
?.id;
|
||||
final String currentCompanyId = GetIt.I<SessionCubit>().state.company!.id!;
|
||||
final String? currentStoreId = GetIt.I<SessionCubit>().state.currentStore?.id;
|
||||
|
||||
TaskFormCubit({
|
||||
required List<StaffMemberModel> globalStaff, // Iniettiamo la lista qui
|
||||
TaskModel? initialTask,
|
||||
String? initialTaskId,
|
||||
required List<StaffMemberModel> globalStaff,
|
||||
|
||||
TaskModel? initialTask, // Arriva dalla navigazione interna (extra)
|
||||
String? initialTaskId, // Arriva dal Deep Link (parametro URL)
|
||||
}) : _globalStaff = globalStaff,
|
||||
_initialTask = initialTask,
|
||||
_initialTaskId = initialTaskId,
|
||||
super(const TaskFormState()) {
|
||||
_initForm(task: initialTask, initialTaskId: initialTaskId);
|
||||
_initForm(initialTask, initialTaskId);
|
||||
}
|
||||
|
||||
// --- 1. INIZIALIZZAZIONE SINCRONA ---
|
||||
void _initForm({TaskModel? task, String? initialTaskId}) {
|
||||
final isGlobalMode = task != null
|
||||
? task.storeId == null
|
||||
: currentStoreId == null;
|
||||
Future<void> _initForm(TaskModel? task, String? taskId) async {
|
||||
// 1. Mettiamo subito il form in caricamento
|
||||
emit(state.copyWith(status: TaskFormStatus.loading));
|
||||
|
||||
// MAGIA: Estraiamo gli ID dagli oggetti staff, o facciamo fallback su assignedToIds se c'è
|
||||
TaskModel? taskToLoad = task;
|
||||
|
||||
// 2. SCENARIO DEEP LINK: Non abbiamo l'oggetto, ma abbiamo un ID valido
|
||||
if (taskToLoad == null && taskId != null && taskId != 'new') {
|
||||
try {
|
||||
taskToLoad = await _taskRepository.getTaskById(taskId);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: TaskFormStatus.failure,
|
||||
errorMessage: 'Impossibile caricare il task dal link: $e',
|
||||
),
|
||||
);
|
||||
return; // Ci fermiamo qui
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Popoliamo lo stato con i dati (sia che arrivino dall'extra, sia dal DB, sia nulli)
|
||||
final isGlobalMode = taskToLoad != null
|
||||
? taskToLoad.storeId == null
|
||||
: currentStoreId == null;
|
||||
final existingStaffIds =
|
||||
task?.assignedToStaff.map((s) => s.id!).toList() ??
|
||||
task?.assignedToIds ??
|
||||
taskToLoad?.assignedToStaff.map((s) => s.id!).toList() ??
|
||||
taskToLoad?.assignedToIds ??
|
||||
[];
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
id: task?.id,
|
||||
title: task?.title ?? '',
|
||||
description: task?.description ?? '',
|
||||
dueDate: task?.dueDate,
|
||||
taskStatus: task?.status ?? TaskStatus.open,
|
||||
id: taskToLoad?.id,
|
||||
title: taskToLoad?.title ?? '',
|
||||
description: taskToLoad?.description ?? '',
|
||||
dueDate: taskToLoad?.dueDate,
|
||||
taskStatus: taskToLoad?.status ?? TaskStatus.open,
|
||||
isGlobal: isGlobalMode,
|
||||
selectedStaffIds:
|
||||
existingStaffIds, // Ora non si perde più i dipendenti!
|
||||
selectedStaffIds: existingStaffIds,
|
||||
status: TaskFormStatus.initial, // Caricamento finito, form pronto!
|
||||
),
|
||||
);
|
||||
|
||||
@@ -78,16 +82,28 @@ class TaskFormCubit extends Cubit<TaskFormState> {
|
||||
}
|
||||
|
||||
void _updateStaffScope(bool isGlobal) {
|
||||
// 1. Filtriamo in memoria
|
||||
// 1. Filtriamo in memoria: cerchiamo nell'array degli ID!
|
||||
final filteredStaff = isGlobal
|
||||
? _globalStaff
|
||||
: _globalStaff.where((s) => s.store?.id == currentStoreId).toList();
|
||||
: _globalStaff
|
||||
.where((s) => s.assignedStoreIds.contains(currentStoreId))
|
||||
.toList();
|
||||
|
||||
// 2. Raggruppiamo per nome negozio (usando groupBy del pacchetto collection)
|
||||
final groupedStaff = groupBy(
|
||||
filteredStaff,
|
||||
(StaffMemberModel s) => s.store?.name ?? 'Direzione / HQ',
|
||||
);
|
||||
// 2. Raggruppamento M2M (Ciclo manuale)
|
||||
final Map<String, List<StaffMemberModel>> groupedStaff = {};
|
||||
|
||||
for (final staff in filteredStaff) {
|
||||
// Se non ha nessun negozio assegnato, finisce in Direzione
|
||||
if (staff.assignedStores.isEmpty) {
|
||||
groupedStaff.putIfAbsent('Direzione / HQ', () => []).add(staff);
|
||||
} else {
|
||||
// Se ha più negozi, clona la sua presenza in ogni gruppo!
|
||||
for (final store in staff.assignedStores) {
|
||||
final storeName = store.name;
|
||||
groupedStaff.putIfAbsent(storeName, () => []).add(staff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Emettiamo il nuovo stato all'istante
|
||||
emit(
|
||||
|
||||
@@ -24,7 +24,7 @@ class TaskRepository {
|
||||
.from(Tables.tasks)
|
||||
.select('''
|
||||
*,
|
||||
${Tables.taskAssignments} (
|
||||
task_assignments:${Tables.taskAssignments} (
|
||||
${Tables.staffMembers} (*)
|
||||
)
|
||||
''')
|
||||
@@ -138,9 +138,12 @@ class TaskRepository {
|
||||
try {
|
||||
final response = await _supabase
|
||||
.from(Tables.tasks)
|
||||
.select(
|
||||
'*, assigned_to_staff:${Tables.staffMembers}!${Tables.taskAssignments}(*)',
|
||||
)
|
||||
.select('''
|
||||
*,
|
||||
task_assignments:${Tables.taskAssignments} (
|
||||
${Tables.staffMembers} (*)
|
||||
)
|
||||
''')
|
||||
.eq('id', taskId)
|
||||
.single();
|
||||
return TaskModel.fromMap(response);
|
||||
|
||||
@@ -3,19 +3,53 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/features/tasks/blocs/task_form_cubit.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class TaskFormScreen extends StatelessWidget {
|
||||
class TaskFormScreen extends StatefulWidget {
|
||||
const TaskFormScreen({super.key});
|
||||
|
||||
@override
|
||||
State<TaskFormScreen> createState() => _TaskFormScreenState();
|
||||
}
|
||||
|
||||
class _TaskFormScreenState extends State<TaskFormScreen> {
|
||||
late final TextEditingController _titleController;
|
||||
late final TextEditingController _descController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Leggiamo lo stato iniziale dal Cubit (che ha già i dati del task esistente)
|
||||
final initialState = context.read<TaskFormCubit>().state;
|
||||
|
||||
_titleController = TextEditingController(text: initialState.title);
|
||||
_descController = TextEditingController(text: initialState.description);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_titleController.dispose();
|
||||
_descController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<TaskFormCubit, TaskFormState>(
|
||||
listenWhen: (previous, current) => previous.status != current.status,
|
||||
listener: (context, state) {
|
||||
// GESTIONE DEEP LINK: Se eravamo in caricamento e ora siamo pronti, popoliamo i controller!
|
||||
if (state.status == TaskFormStatus.initial) {
|
||||
if (_titleController.text != state.title) {
|
||||
_titleController.text = state.title;
|
||||
}
|
||||
if (_descController.text != state.description) {
|
||||
_descController.text = state.description;
|
||||
}
|
||||
}
|
||||
if (state.status == TaskFormStatus.success) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Task salvato con successo! 🎉')),
|
||||
);
|
||||
context.pop(); // Usciamo dalla pagina
|
||||
context.pop();
|
||||
} else if (state.status == TaskFormStatus.failure) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
@@ -33,7 +67,6 @@ class TaskFormScreen extends StatelessWidget {
|
||||
appBar: AppBar(
|
||||
title: Text(isEditing ? 'Modifica Task' : 'Nuovo Task'),
|
||||
actions: [
|
||||
// Tasto Salva (abilitato solo se il form è valido)
|
||||
if (state.status == TaskFormStatus.submitting)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
@@ -46,81 +79,84 @@ class TaskFormScreen extends StatelessWidget {
|
||||
else
|
||||
TextButton.icon(
|
||||
onPressed: state.isFormValid
|
||||
? () =>
|
||||
cubit.saveTask(
|
||||
currentUserId: 'TODO_USER_ID',
|
||||
) // Passa l'id utente loggato dal SessionCubit
|
||||
? () => cubit.saveTask(currentUserId: 'TODO_USER_ID')
|
||||
: null,
|
||||
icon: const Icon(Icons.save),
|
||||
label: const Text('Salva'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.orange, // Il tuo colore primario
|
||||
foregroundColor: Colors.orange,
|
||||
disabledForegroundColor: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isWideScreen = constraints.maxWidth > 800;
|
||||
body: state.status == TaskFormStatus.loading
|
||||
// Se sta scaricando i dati dal Deep Link, mostriamo un bel loader centrato
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isWideScreen = constraints.maxWidth > 800;
|
||||
|
||||
if (isWideScreen) {
|
||||
// --- VISTA DESKTOP / TABLET LARGHI (2 Colonne) ---
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: _buildFormFields(context, state, cubit),
|
||||
),
|
||||
),
|
||||
VerticalDivider(color: Theme.of(context).dividerColor),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: _buildStaffSelectorInline(context, state, cubit),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
if (isWideScreen) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: _buildFormFields(context, state, cubit),
|
||||
),
|
||||
),
|
||||
VerticalDivider(
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: _buildStaffSelectorInline(
|
||||
context,
|
||||
state,
|
||||
cubit,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// --- VISTA MOBILE (1 Colonna + BottomSheet) ---
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildFormFields(context, state, cubit),
|
||||
const SizedBox(height: 30),
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildFormFields(context, state, cubit),
|
||||
const SizedBox(height: 30),
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () =>
|
||||
_showStaffBottomSheet(context, cubit),
|
||||
icon: const Icon(Icons.group_add),
|
||||
label: Text(
|
||||
state.selectedStaffIds.isEmpty
|
||||
? 'Assegna Staff'
|
||||
: 'Assegnato a ${state.selectedStaffIds.length} persone',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () => _showStaffBottomSheet(context, cubit),
|
||||
icon: const Icon(Icons.group_add),
|
||||
label: Text(
|
||||
state.selectedStaffIds.isEmpty
|
||||
? 'Assegna Staff'
|
||||
: 'Assegnato a ${state.selectedStaffIds.length} persone',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 1. I CAMPI DEL FORM PRINCIPALE
|
||||
// =========================================================================
|
||||
// --- I CAMPI DEL FORM (Aggiornati con i Controller) ---
|
||||
Widget _buildFormFields(
|
||||
BuildContext context,
|
||||
TaskFormState state,
|
||||
@@ -129,7 +165,6 @@ class TaskFormScreen extends StatelessWidget {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// --- SCOPE TOGGLE ---
|
||||
Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -153,9 +188,9 @@ class TaskFormScreen extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// --- TITOLO E DESCRIZIONE ---
|
||||
// Addio initialValue, benvenuto controller!
|
||||
TextFormField(
|
||||
initialValue: state.title,
|
||||
controller: _titleController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Titolo del Task*',
|
||||
border: OutlineInputBorder(),
|
||||
@@ -165,7 +200,7 @@ class TaskFormScreen extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
initialValue: state.description,
|
||||
controller: _descController,
|
||||
maxLines: 4,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Descrizione (opzionale)',
|
||||
|
||||
Reference in New Issue
Block a user