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/data/notes_repository.dart';
|
||||||
import 'package:flux/features/notes/models/note_model.dart';
|
import 'package:flux/features/notes/models/note_model.dart';
|
||||||
import 'package:flux/features/notes/ui/dashboard_notes_widget.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:get_it/get_it.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
@@ -236,11 +234,7 @@ class HomeScreen extends StatelessWidget {
|
|||||||
label: context.l10n.commonTask,
|
label: context.l10n.commonTask,
|
||||||
color: Colors.teal,
|
color: Colors.teal,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed(
|
context.pushNamed(Routes.taskForm, pathParameters: {'id': 'new'});
|
||||||
Routes.taskForm,
|
|
||||||
pathParameters: {'id': 'new'},
|
|
||||||
extra: (task: null),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -12,7 +12,22 @@ class StaffCubit extends Cubit<StaffState> {
|
|||||||
final StaffRepository _repository = GetIt.I.get<StaffRepository>();
|
final StaffRepository _repository = GetIt.I.get<StaffRepository>();
|
||||||
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
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
|
// Carica tutto lo staff della compagnia
|
||||||
Future<void> loadAllStaff() async {
|
Future<void> loadAllStaff() async {
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ class StaffRepository {
|
|||||||
.from(Tables.staffMembers)
|
.from(Tables.staffMembers)
|
||||||
.select('''
|
.select('''
|
||||||
*,
|
*,
|
||||||
stores (*)
|
store_assignments:${Tables.staffInStores} (
|
||||||
|
${Tables.stores}(*)
|
||||||
|
)
|
||||||
''')
|
''')
|
||||||
.eq('company_id', companyId)
|
.eq('company_id', companyId)
|
||||||
.order('name', ascending: true);
|
.order('name', ascending: true);
|
||||||
@@ -27,7 +29,12 @@ class StaffRepository {
|
|||||||
try {
|
try {
|
||||||
final response = await _supabase
|
final response = await _supabase
|
||||||
.from(Tables.staffMembers)
|
.from(Tables.staffMembers)
|
||||||
.select()
|
.select('''
|
||||||
|
*,
|
||||||
|
store_assignments:${Tables.staffInStores} (
|
||||||
|
${Tables.stores}(*)
|
||||||
|
)
|
||||||
|
''')
|
||||||
.eq('id', staffId)
|
.eq('id', staffId)
|
||||||
.single();
|
.single();
|
||||||
return StaffMemberModel.fromMap(response);
|
return StaffMemberModel.fromMap(response);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
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';
|
import 'package:flux/features/master_data/store/models/store_model.dart';
|
||||||
|
|
||||||
// L'Enum magico e blindato per il sistema
|
// L'Enum magico e blindato per il sistema
|
||||||
@@ -27,7 +28,8 @@ class StaffMemberModel extends Equatable {
|
|||||||
final SystemRole systemRole;
|
final SystemRole systemRole;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
final bool hasJoined;
|
final bool hasJoined;
|
||||||
final StoreModel? store;
|
final List<String> assignedStoreIds;
|
||||||
|
final List<StoreModel> assignedStores;
|
||||||
|
|
||||||
const StaffMemberModel({
|
const StaffMemberModel({
|
||||||
this.id,
|
this.id,
|
||||||
@@ -40,7 +42,8 @@ class StaffMemberModel extends Equatable {
|
|||||||
this.systemRole = SystemRole.user,
|
this.systemRole = SystemRole.user,
|
||||||
this.isActive = true,
|
this.isActive = true,
|
||||||
this.hasJoined = false,
|
this.hasJoined = false,
|
||||||
this.store,
|
this.assignedStoreIds = const [],
|
||||||
|
this.assignedStores = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
StaffMemberModel copyWith({
|
StaffMemberModel copyWith({
|
||||||
@@ -55,7 +58,8 @@ class StaffMemberModel extends Equatable {
|
|||||||
SystemRole? systemRole,
|
SystemRole? systemRole,
|
||||||
bool? isActive,
|
bool? isActive,
|
||||||
bool? hasJoined,
|
bool? hasJoined,
|
||||||
StoreModel? store,
|
List<String>? assignedStoreIds,
|
||||||
|
List<StoreModel>? assignedStores,
|
||||||
}) {
|
}) {
|
||||||
return StaffMemberModel(
|
return StaffMemberModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@@ -68,7 +72,8 @@ class StaffMemberModel extends Equatable {
|
|||||||
systemRole: systemRole ?? this.systemRole,
|
systemRole: systemRole ?? this.systemRole,
|
||||||
isActive: isActive ?? this.isActive,
|
isActive: isActive ?? this.isActive,
|
||||||
hasJoined: hasJoined ?? this.hasJoined,
|
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) {
|
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(
|
return StaffMemberModel(
|
||||||
id: map['id'] as String?,
|
id: map['id'] as String?,
|
||||||
companyId: map['company_id'] ?? '',
|
companyId: map['company_id'] ?? '',
|
||||||
@@ -95,7 +118,8 @@ class StaffMemberModel extends Equatable {
|
|||||||
systemRole: SystemRole.fromString(map['system_role']),
|
systemRole: SystemRole.fromString(map['system_role']),
|
||||||
isActive: map['is_active'] ?? true,
|
isActive: map['is_active'] ?? true,
|
||||||
hasJoined: map['has_joined'] ?? false,
|
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,
|
systemRole,
|
||||||
isActive,
|
isActive,
|
||||||
hasJoined,
|
hasJoined,
|
||||||
store,
|
assignedStoreIds,
|
||||||
|
assignedStores,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flux/core/enums_and_consts/consts.dart';
|
||||||
import 'package:flux/core/utils/extensions.dart';
|
import 'package:flux/core/utils/extensions.dart';
|
||||||
import 'package:flux/features/attachments/models/attachment_model.dart';
|
import 'package:flux/features/attachments/models/attachment_model.dart';
|
||||||
import 'package:flux/features/customers/models/customer_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!
|
// I campi relazionali nullabili restano rigorosamente null!
|
||||||
providerId: map['provider_id'] as String?,
|
providerId: map['provider_id'] as String?,
|
||||||
// MAGIA ANTI-CRASH: Usiamo ?['chiave'] per non far esplodere i join vuoti
|
// 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?,
|
modelId: map['model_id'] as String?,
|
||||||
modelDisplayName: (map['model']?['name_with_brand'] as String?)
|
modelDisplayName: (map[Tables.models]?['name_with_brand'] as String?)
|
||||||
?.myFormat(),
|
?.myFormat(),
|
||||||
|
|
||||||
description: map['description'] as String?,
|
description: map['description'] as String?,
|
||||||
@@ -202,25 +204,26 @@ class OperationModel extends Equatable {
|
|||||||
storeId:
|
storeId:
|
||||||
map['store_id'] as String? ??
|
map['store_id'] as String? ??
|
||||||
'', // Questo è non-nullable nella tua classe
|
'', // 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
|
quantity: map['quantity'] is int
|
||||||
? map['quantity']
|
? map['quantity']
|
||||||
: int.tryParse(map['quantity']?.toString() ?? '1') ?? 1,
|
: int.tryParse(map['quantity']?.toString() ?? '1') ?? 1,
|
||||||
|
|
||||||
staffId: map['staff_id'] as String?,
|
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?,
|
lastCampaignId: map['last_campaign_id'] as String?,
|
||||||
status: OperationStatus.fromString(map['status'] ?? 'draft'),
|
status: OperationStatus.fromString(map['status'] ?? 'draft'),
|
||||||
customerId: map['customer_id'] as String?,
|
customerId: map['customer_id'] as String?,
|
||||||
|
|
||||||
customer: map['customer'] != null
|
customer: map[Tables.customers] != null
|
||||||
? CustomerModel.fromMap(map['customer'] as Map<String, dynamic>)
|
? CustomerModel.fromMap(map[Tables.customers] as Map<String, dynamic>)
|
||||||
: null,
|
: null,
|
||||||
|
|
||||||
attachments:
|
attachments:
|
||||||
(map['attachment'] as List?)
|
(map[Tables.attachments] as List?)
|
||||||
?.map((x) => AttachmentModel.fromMap(x))
|
?.map((x) => AttachmentModel.fromMap(x))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||||
@@ -11,55 +10,60 @@ part 'task_form_state.dart';
|
|||||||
|
|
||||||
class TaskFormCubit extends Cubit<TaskFormState> {
|
class TaskFormCubit extends Cubit<TaskFormState> {
|
||||||
final TaskRepository _taskRepository = GetIt.I.get<TaskRepository>();
|
final TaskRepository _taskRepository = GetIt.I.get<TaskRepository>();
|
||||||
final TaskModel? _initialTask;
|
|
||||||
final String? _initialTaskId;
|
|
||||||
// Cache in memoria proveniente dallo StaffCubit!
|
|
||||||
final List<StaffMemberModel> _globalStaff;
|
final List<StaffMemberModel> _globalStaff;
|
||||||
|
final String currentCompanyId = GetIt.I<SessionCubit>().state.company!.id!;
|
||||||
final String currentCompanyId = GetIt.I
|
final String? currentStoreId = GetIt.I<SessionCubit>().state.currentStore?.id;
|
||||||
.get<SessionCubit>()
|
|
||||||
.state
|
|
||||||
.company!
|
|
||||||
.id!;
|
|
||||||
final String? currentStoreId = GetIt.I
|
|
||||||
.get<SessionCubit>()
|
|
||||||
.state
|
|
||||||
.currentStore
|
|
||||||
?.id;
|
|
||||||
|
|
||||||
TaskFormCubit({
|
TaskFormCubit({
|
||||||
required List<StaffMemberModel> globalStaff, // Iniettiamo la lista qui
|
required List<StaffMemberModel> globalStaff,
|
||||||
TaskModel? initialTask,
|
|
||||||
String? initialTaskId,
|
TaskModel? initialTask, // Arriva dalla navigazione interna (extra)
|
||||||
|
String? initialTaskId, // Arriva dal Deep Link (parametro URL)
|
||||||
}) : _globalStaff = globalStaff,
|
}) : _globalStaff = globalStaff,
|
||||||
_initialTask = initialTask,
|
|
||||||
_initialTaskId = initialTaskId,
|
|
||||||
super(const TaskFormState()) {
|
super(const TaskFormState()) {
|
||||||
_initForm(task: initialTask, initialTaskId: initialTaskId);
|
_initForm(initialTask, initialTaskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 1. INIZIALIZZAZIONE SINCRONA ---
|
Future<void> _initForm(TaskModel? task, String? taskId) async {
|
||||||
void _initForm({TaskModel? task, String? initialTaskId}) {
|
// 1. Mettiamo subito il form in caricamento
|
||||||
final isGlobalMode = task != null
|
emit(state.copyWith(status: TaskFormStatus.loading));
|
||||||
? task.storeId == null
|
|
||||||
: currentStoreId == null;
|
|
||||||
|
|
||||||
// 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 =
|
final existingStaffIds =
|
||||||
task?.assignedToStaff.map((s) => s.id!).toList() ??
|
taskToLoad?.assignedToStaff.map((s) => s.id!).toList() ??
|
||||||
task?.assignedToIds ??
|
taskToLoad?.assignedToIds ??
|
||||||
[];
|
[];
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
id: task?.id,
|
id: taskToLoad?.id,
|
||||||
title: task?.title ?? '',
|
title: taskToLoad?.title ?? '',
|
||||||
description: task?.description ?? '',
|
description: taskToLoad?.description ?? '',
|
||||||
dueDate: task?.dueDate,
|
dueDate: taskToLoad?.dueDate,
|
||||||
taskStatus: task?.status ?? TaskStatus.open,
|
taskStatus: taskToLoad?.status ?? TaskStatus.open,
|
||||||
isGlobal: isGlobalMode,
|
isGlobal: isGlobalMode,
|
||||||
selectedStaffIds:
|
selectedStaffIds: existingStaffIds,
|
||||||
existingStaffIds, // Ora non si perde più i dipendenti!
|
status: TaskFormStatus.initial, // Caricamento finito, form pronto!
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -78,16 +82,28 @@ class TaskFormCubit extends Cubit<TaskFormState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _updateStaffScope(bool isGlobal) {
|
void _updateStaffScope(bool isGlobal) {
|
||||||
// 1. Filtriamo in memoria
|
// 1. Filtriamo in memoria: cerchiamo nell'array degli ID!
|
||||||
final filteredStaff = isGlobal
|
final filteredStaff = isGlobal
|
||||||
? _globalStaff
|
? _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)
|
// 2. Raggruppamento M2M (Ciclo manuale)
|
||||||
final groupedStaff = groupBy(
|
final Map<String, List<StaffMemberModel>> groupedStaff = {};
|
||||||
filteredStaff,
|
|
||||||
(StaffMemberModel s) => s.store?.name ?? 'Direzione / HQ',
|
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
|
// 3. Emettiamo il nuovo stato all'istante
|
||||||
emit(
|
emit(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class TaskRepository {
|
|||||||
.from(Tables.tasks)
|
.from(Tables.tasks)
|
||||||
.select('''
|
.select('''
|
||||||
*,
|
*,
|
||||||
${Tables.taskAssignments} (
|
task_assignments:${Tables.taskAssignments} (
|
||||||
${Tables.staffMembers} (*)
|
${Tables.staffMembers} (*)
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
@@ -138,9 +138,12 @@ class TaskRepository {
|
|||||||
try {
|
try {
|
||||||
final response = await _supabase
|
final response = await _supabase
|
||||||
.from(Tables.tasks)
|
.from(Tables.tasks)
|
||||||
.select(
|
.select('''
|
||||||
'*, assigned_to_staff:${Tables.staffMembers}!${Tables.taskAssignments}(*)',
|
*,
|
||||||
|
task_assignments:${Tables.taskAssignments} (
|
||||||
|
${Tables.staffMembers} (*)
|
||||||
)
|
)
|
||||||
|
''')
|
||||||
.eq('id', taskId)
|
.eq('id', taskId)
|
||||||
.single();
|
.single();
|
||||||
return TaskModel.fromMap(response);
|
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:flux/features/tasks/blocs/task_form_cubit.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class TaskFormScreen extends StatelessWidget {
|
class TaskFormScreen extends StatefulWidget {
|
||||||
const TaskFormScreen({super.key});
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocConsumer<TaskFormCubit, TaskFormState>(
|
return BlocConsumer<TaskFormCubit, TaskFormState>(
|
||||||
listenWhen: (previous, current) => previous.status != current.status,
|
listenWhen: (previous, current) => previous.status != current.status,
|
||||||
listener: (context, state) {
|
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) {
|
if (state.status == TaskFormStatus.success) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Task salvato con successo! 🎉')),
|
const SnackBar(content: Text('Task salvato con successo! 🎉')),
|
||||||
);
|
);
|
||||||
context.pop(); // Usciamo dalla pagina
|
context.pop();
|
||||||
} else if (state.status == TaskFormStatus.failure) {
|
} else if (state.status == TaskFormStatus.failure) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
@@ -33,7 +67,6 @@ class TaskFormScreen extends StatelessWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(isEditing ? 'Modifica Task' : 'Nuovo Task'),
|
title: Text(isEditing ? 'Modifica Task' : 'Nuovo Task'),
|
||||||
actions: [
|
actions: [
|
||||||
// Tasto Salva (abilitato solo se il form è valido)
|
|
||||||
if (state.status == TaskFormStatus.submitting)
|
if (state.status == TaskFormStatus.submitting)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
@@ -46,26 +79,25 @@ class TaskFormScreen extends StatelessWidget {
|
|||||||
else
|
else
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: state.isFormValid
|
onPressed: state.isFormValid
|
||||||
? () =>
|
? () => cubit.saveTask(currentUserId: 'TODO_USER_ID')
|
||||||
cubit.saveTask(
|
|
||||||
currentUserId: 'TODO_USER_ID',
|
|
||||||
) // Passa l'id utente loggato dal SessionCubit
|
|
||||||
: null,
|
: null,
|
||||||
icon: const Icon(Icons.save),
|
icon: const Icon(Icons.save),
|
||||||
label: const Text('Salva'),
|
label: const Text('Salva'),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
foregroundColor: Colors.orange, // Il tuo colore primario
|
foregroundColor: Colors.orange,
|
||||||
disabledForegroundColor: Colors.grey,
|
disabledForegroundColor: Colors.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: LayoutBuilder(
|
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) {
|
builder: (context, constraints) {
|
||||||
final isWideScreen = constraints.maxWidth > 800;
|
final isWideScreen = constraints.maxWidth > 800;
|
||||||
|
|
||||||
if (isWideScreen) {
|
if (isWideScreen) {
|
||||||
// --- VISTA DESKTOP / TABLET LARGHI (2 Colonne) ---
|
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -76,16 +108,21 @@ class TaskFormScreen extends StatelessWidget {
|
|||||||
child: _buildFormFields(context, state, cubit),
|
child: _buildFormFields(context, state, cubit),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
VerticalDivider(color: Theme.of(context).dividerColor),
|
VerticalDivider(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 4,
|
flex: 4,
|
||||||
child: _buildStaffSelectorInline(context, state, cubit),
|
child: _buildStaffSelectorInline(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
|
cubit,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- VISTA MOBILE (1 Colonna + BottomSheet) ---
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -100,7 +137,8 @@ class TaskFormScreen extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () => _showStaffBottomSheet(context, cubit),
|
onPressed: () =>
|
||||||
|
_showStaffBottomSheet(context, cubit),
|
||||||
icon: const Icon(Icons.group_add),
|
icon: const Icon(Icons.group_add),
|
||||||
label: Text(
|
label: Text(
|
||||||
state.selectedStaffIds.isEmpty
|
state.selectedStaffIds.isEmpty
|
||||||
@@ -118,9 +156,7 @@ class TaskFormScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// --- I CAMPI DEL FORM (Aggiornati con i Controller) ---
|
||||||
// 1. I CAMPI DEL FORM PRINCIPALE
|
|
||||||
// =========================================================================
|
|
||||||
Widget _buildFormFields(
|
Widget _buildFormFields(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
TaskFormState state,
|
TaskFormState state,
|
||||||
@@ -129,7 +165,6 @@ class TaskFormScreen extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// --- SCOPE TOGGLE ---
|
|
||||||
Card(
|
Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
@@ -153,9 +188,9 @@ class TaskFormScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// --- TITOLO E DESCRIZIONE ---
|
// Addio initialValue, benvenuto controller!
|
||||||
TextFormField(
|
TextFormField(
|
||||||
initialValue: state.title,
|
controller: _titleController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Titolo del Task*',
|
labelText: 'Titolo del Task*',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
@@ -165,7 +200,7 @@ class TaskFormScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
initialValue: state.description,
|
controller: _descController,
|
||||||
maxLines: 4,
|
maxLines: 4,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Descrizione (opzionale)',
|
labelText: 'Descrizione (opzionale)',
|
||||||
|
|||||||
Reference in New Issue
Block a user