b
This commit is contained in:
@@ -553,7 +553,7 @@ class AppRouter {
|
|||||||
BlocProvider<TaskFormCubit>(
|
BlocProvider<TaskFormCubit>(
|
||||||
create: (context) => TaskFormCubit(
|
create: (context) => TaskFormCubit(
|
||||||
globalStaff: allStaffList,
|
globalStaff: allStaffList,
|
||||||
initialTask: task,
|
existingTask: task,
|
||||||
initialTaskId: realTaskId,
|
initialTaskId: realTaskId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import 'package:get_it/get_it.dart';
|
|||||||
part 'dashboard_task_list_state.dart';
|
part 'dashboard_task_list_state.dart';
|
||||||
|
|
||||||
class DashboardTaskListCubit extends Cubit<DashboardTaskListState> {
|
class DashboardTaskListCubit extends Cubit<DashboardTaskListState> {
|
||||||
final TaskRepository _repository = GetIt.I.get<TaskRepository>();
|
final TasksRepository _repository = GetIt.I.get<TasksRepository>();
|
||||||
final String staffId;
|
final String staffId;
|
||||||
final String companyId;
|
final String companyId;
|
||||||
StreamSubscription<void>? _taskSubscription;
|
StreamSubscription<void>? _taskSubscription;
|
||||||
|
|||||||
@@ -11,40 +11,105 @@ import 'package:get_it/get_it.dart';
|
|||||||
part 'task_form_state.dart';
|
part 'task_form_state.dart';
|
||||||
|
|
||||||
class TaskFormCubit extends Cubit<TaskFormState> {
|
class TaskFormCubit extends Cubit<TaskFormState> {
|
||||||
final TaskRepository _repository = GetIt.I.get<TaskRepository>();
|
final TasksRepository _repository = GetIt.I.get<TasksRepository>();
|
||||||
final SettingsRepository _settingsRepository = GetIt.I
|
final SettingsRepository _settingsRepository = GetIt.I
|
||||||
.get<SettingsRepository>();
|
.get<SettingsRepository>();
|
||||||
final SessionCubit _sessionCubit = GetIt.I.get<SessionCubit>();
|
final SessionCubit _sessionCubit = GetIt.I.get<SessionCubit>();
|
||||||
|
|
||||||
TaskFormCubit({TaskModel? existingTask})
|
TaskFormCubit({
|
||||||
: super(
|
String? initialTaskId, // <-- RIPRISTINATO PER DEEP LINK
|
||||||
TaskFormState(
|
TaskModel? existingTask,
|
||||||
id: existingTask?.id,
|
}) : super(const TaskFormState()) {
|
||||||
title: existingTask?.title ?? '',
|
// Avviamo l'inizializzazione centralizzata (gestisce sia mem, sia deep link, sia nuovo)
|
||||||
description: existingTask?.description ?? '',
|
initForm(initialTaskId: initialTaskId, existingTask: existingTask);
|
||||||
dueDate: existingTask?.dueDate,
|
|
||||||
isGlobal: existingTask?.isGlobal ?? false,
|
|
||||||
selectedStaffIds: existingTask?.assignedToIds ?? [],
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
if (existingTask == null) {
|
|
||||||
_initializeNewTaskReminders();
|
|
||||||
} else {
|
|
||||||
_loadExistingTaskReminders(existingTask.id!);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String get _companyId => _sessionCubit.state.company!.id!;
|
String get _companyId => _sessionCubit.state.company!.id!;
|
||||||
String get _currentUserId => _sessionCubit.state.currentStaffMember!.id!;
|
String get _currentUserId => _sessionCubit.state.currentStaffMember!.id!;
|
||||||
|
String? get _currentStoreId => _sessionCubit.state.currentStore?.id;
|
||||||
|
|
||||||
// --- INIT REMINDER NUOVO TASK ---
|
// --- ARMED INITIALIZATION (Nuovo, Esistente o Deep Link) ---
|
||||||
|
Future<void> initForm({
|
||||||
|
String? initialTaskId,
|
||||||
|
TaskModel? existingTask,
|
||||||
|
}) async {
|
||||||
|
emit(state.copyWith(status: TaskFormStatus.loading));
|
||||||
|
try {
|
||||||
|
TaskModel? task = existingTask;
|
||||||
|
|
||||||
|
// 1. Se arriviamo da Deep Link col solo ID, lo scarichiamo dal DB
|
||||||
|
if (initialTaskId != null && task == null) {
|
||||||
|
task = await _repository.fetchTaskById(initialTaskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task != null) {
|
||||||
|
// CASO: TASK ESISTENTE (Modifica o Deep Link pronto)
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
id: task.id,
|
||||||
|
title: task.title,
|
||||||
|
description: task.description,
|
||||||
|
dueDate: task.dueDate,
|
||||||
|
isGlobal: task.isGlobal, // Sfrutta il tuo getter storeId == null
|
||||||
|
selectedStaffIds: task.assignedToIds,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await _loadExistingTaskReminders(task.id!);
|
||||||
|
} else {
|
||||||
|
// CASO: NUOVO TASK
|
||||||
|
await _initializeNewTaskReminders();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Carichiamo e raggruppiamo il personale (Global o Store)
|
||||||
|
await _loadAndGroupStaff();
|
||||||
|
|
||||||
|
// Mandiamo lo status a 'initial' così il FormScreen sincronizza i controller di testo!
|
||||||
|
emit(state.copyWith(status: TaskFormStatus.initial));
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: TaskFormStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- LOGICA GESTIONE STAFF (GLOBAL STAFF / STORE STAFF) ---
|
||||||
|
Future<void> _loadAndGroupStaff() async {
|
||||||
|
// Se isGlobal è true, passiamo null come storeId al repo per tirare giù tutta l'azienda
|
||||||
|
final List<StaffMemberModel> staffList = await _repository
|
||||||
|
.fetchAvailableStaff(
|
||||||
|
companyId: _companyId,
|
||||||
|
storeId: state.isGlobal ? null : _currentStoreId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Raggruppamento per nome del negozio (Mappa { "Nome Negozio": [Membri] })
|
||||||
|
final Map<String, List<StaffMemberModel>> grouped = {};
|
||||||
|
for (var staff in staffList) {
|
||||||
|
final storeName = staff.storeName ?? 'Senza Sede';
|
||||||
|
grouped.putIfAbsent(storeName, () => []).add(staff);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(state.copyWith(groupedAvailableStaff: grouped));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se l'utente switcha su "Globale Aziendale", ricarichiamo lo staff di conseguenza
|
||||||
|
void toggleGlobalScope(bool g) async {
|
||||||
|
emit(state.copyWith(isGlobal: g, status: TaskFormStatus.loading));
|
||||||
|
await _loadAndGroupStaff();
|
||||||
|
emit(
|
||||||
|
state.copyWith(status: TaskFormStatus.initial),
|
||||||
|
); // Ri-notifichiamo la UI
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- INIT REMINDER ---
|
||||||
Future<void> _initializeNewTaskReminders() async {
|
Future<void> _initializeNewTaskReminders() async {
|
||||||
try {
|
try {
|
||||||
final defaults = await _settingsRepository.getMyReminderDefaults(
|
final defaults = await _settingsRepository.getMyReminderDefaults(
|
||||||
companyId: _companyId,
|
companyId: _companyId,
|
||||||
staffId: _currentUserId,
|
staffId: _currentUserId,
|
||||||
);
|
);
|
||||||
|
|
||||||
final initialReminders = defaults
|
final initialReminders = defaults
|
||||||
.map(
|
.map(
|
||||||
(d) => TaskReminderConfig(
|
(d) => TaskReminderConfig(
|
||||||
@@ -53,10 +118,8 @@ class TaskFormCubit extends Cubit<TaskFormState> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
emit(state.copyWith(reminders: initialReminders));
|
emit(state.copyWith(reminders: initialReminders));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Fallback in caso di errore
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
reminders: const [
|
reminders: const [
|
||||||
@@ -67,10 +130,8 @@ class TaskFormCubit extends Cubit<TaskFormState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- INIT REMINDER TASK ESISTENTE ---
|
|
||||||
Future<void> _loadExistingTaskReminders(String taskId) async {
|
Future<void> _loadExistingTaskReminders(String taskId) async {
|
||||||
try {
|
try {
|
||||||
// Recuperiamo SOLO i reminder non forzati dell'utente loggato per popolare il form
|
|
||||||
final existingConfigs = await _repository.fetchPersonalReminders(
|
final existingConfigs = await _repository.fetchPersonalReminders(
|
||||||
taskId: taskId,
|
taskId: taskId,
|
||||||
staffId: _currentUserId,
|
staffId: _currentUserId,
|
||||||
@@ -81,11 +142,10 @@ class TaskFormCubit extends Cubit<TaskFormState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- UPDATE CAMPI BASE ---
|
// --- AGGIORNAMENTO CAMPI ---
|
||||||
void updateTitle(String t) => emit(state.copyWith(title: t));
|
void updateTitle(String t) => emit(state.copyWith(title: t));
|
||||||
void updateDescription(String d) => emit(state.copyWith(description: d));
|
void updateDescription(String d) => emit(state.copyWith(description: d));
|
||||||
void updateDueDate(DateTime? d) => emit(state.copyWith(dueDate: d));
|
void updateDueDate(DateTime? d) => emit(state.copyWith(dueDate: d));
|
||||||
void toggleGlobalScope(bool g) => emit(state.copyWith(isGlobal: g));
|
|
||||||
|
|
||||||
void toggleStaffSelection(String staffId) {
|
void toggleStaffSelection(String staffId) {
|
||||||
final updated = List<String>.from(state.selectedStaffIds);
|
final updated = List<String>.from(state.selectedStaffIds);
|
||||||
@@ -93,7 +153,22 @@ class TaskFormCubit extends Cubit<TaskFormState> {
|
|||||||
emit(state.copyWith(selectedStaffIds: updated));
|
emit(state.copyWith(selectedStaffIds: updated));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GESTIONE REMINDER NEL FORM ---
|
void toggleStoreSelection(String storeName, bool selectAll) {
|
||||||
|
final updated = List<String>.from(state.selectedStaffIds);
|
||||||
|
final storeStaff = state.groupedAvailableStaff[storeName] ?? [];
|
||||||
|
|
||||||
|
for (var staff in storeStaff) {
|
||||||
|
if (staff.id == null) continue;
|
||||||
|
if (selectAll) {
|
||||||
|
if (!updated.contains(staff.id)) updated.add(staff.id!);
|
||||||
|
} else {
|
||||||
|
updated.remove(staff.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit(state.copyWith(selectedStaffIds: updated));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- AZIONI REMINDER ---
|
||||||
void addReminderRule(int minutesBefore, String channel) {
|
void addReminderRule(int minutesBefore, String channel) {
|
||||||
final updated = List<TaskReminderConfig>.from(state.reminders);
|
final updated = List<TaskReminderConfig>.from(state.reminders);
|
||||||
final newConfig = TaskReminderConfig(
|
final newConfig = TaskReminderConfig(
|
||||||
@@ -114,7 +189,7 @@ class TaskFormCubit extends Cubit<TaskFormState> {
|
|||||||
emit(state.copyWith(reminders: updated));
|
emit(state.copyWith(reminders: updated));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- SALVATAGGIO FINALE ---
|
// --- SALVATAGGIO ---
|
||||||
Future<void> saveTask() async {
|
Future<void> saveTask() async {
|
||||||
if (!state.isFormValid) return;
|
if (!state.isFormValid) return;
|
||||||
emit(state.copyWith(status: TaskFormStatus.submitting));
|
emit(state.copyWith(status: TaskFormStatus.submitting));
|
||||||
@@ -122,18 +197,18 @@ class TaskFormCubit extends Cubit<TaskFormState> {
|
|||||||
final taskToSave = TaskModel(
|
final taskToSave = TaskModel(
|
||||||
id: state.id,
|
id: state.id,
|
||||||
companyId: _companyId,
|
companyId: _companyId,
|
||||||
createdBy: _currentUserId,
|
createdById: _currentUserId,
|
||||||
title: state.title.trim(),
|
title: state.title.trim(),
|
||||||
description: state.description.trim(),
|
description: state.description.trim(),
|
||||||
dueDate: state.dueDate,
|
dueDate: state.dueDate,
|
||||||
isGlobal: state.isGlobal,
|
storeId: state.isGlobal
|
||||||
|
? null
|
||||||
|
: _currentStoreId, // Gestione nativa basata sulla tua logica
|
||||||
assignedToIds: state.selectedStaffIds,
|
assignedToIds: state.selectedStaffIds,
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (state.id == null) {
|
if (state.id == null) {
|
||||||
// NUOVO TASK -> CREATE
|
|
||||||
// Qui potresti passare un managerForcedOverride se implementi la UI per quello
|
|
||||||
await _repository.createTask(
|
await _repository.createTask(
|
||||||
task: taskToSave,
|
task: taskToSave,
|
||||||
assignedStaffIds: state.selectedStaffIds,
|
assignedStaffIds: state.selectedStaffIds,
|
||||||
@@ -141,7 +216,6 @@ class TaskFormCubit extends Cubit<TaskFormState> {
|
|||||||
currentUserCustomReminders: state.reminders,
|
currentUserCustomReminders: state.reminders,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// VECCHIO TASK -> UPDATE
|
|
||||||
await _repository.updateTask(
|
await _repository.updateTask(
|
||||||
task: taskToSave,
|
task: taskToSave,
|
||||||
assignedStaffIds: state.selectedStaffIds,
|
assignedStaffIds: state.selectedStaffIds,
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ class TaskFormState extends Equatable {
|
|||||||
final DateTime? dueDate;
|
final DateTime? dueDate;
|
||||||
final bool isGlobal;
|
final bool isGlobal;
|
||||||
final List<String> selectedStaffIds;
|
final List<String> selectedStaffIds;
|
||||||
final List<TaskReminderConfig>
|
final List<TaskReminderConfig> reminders;
|
||||||
reminders; // I promemoria (solo dell'utente loggato)
|
final Map<String, List<StaffMemberModel>>
|
||||||
|
groupedAvailableStaff; // <-- RIPRISTINATO
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
|
||||||
const TaskFormState({
|
const TaskFormState({
|
||||||
@@ -23,6 +24,7 @@ class TaskFormState extends Equatable {
|
|||||||
this.isGlobal = false,
|
this.isGlobal = false,
|
||||||
this.selectedStaffIds = const [],
|
this.selectedStaffIds = const [],
|
||||||
this.reminders = const [],
|
this.reminders = const [],
|
||||||
|
this.groupedAvailableStaff = const {},
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -37,6 +39,7 @@ class TaskFormState extends Equatable {
|
|||||||
bool? isGlobal,
|
bool? isGlobal,
|
||||||
List<String>? selectedStaffIds,
|
List<String>? selectedStaffIds,
|
||||||
List<TaskReminderConfig>? reminders,
|
List<TaskReminderConfig>? reminders,
|
||||||
|
Map<String, List<StaffMemberModel>>? groupedAvailableStaff,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
}) {
|
}) {
|
||||||
return TaskFormState(
|
return TaskFormState(
|
||||||
@@ -48,6 +51,8 @@ class TaskFormState extends Equatable {
|
|||||||
isGlobal: isGlobal ?? this.isGlobal,
|
isGlobal: isGlobal ?? this.isGlobal,
|
||||||
selectedStaffIds: selectedStaffIds ?? this.selectedStaffIds,
|
selectedStaffIds: selectedStaffIds ?? this.selectedStaffIds,
|
||||||
reminders: reminders ?? this.reminders,
|
reminders: reminders ?? this.reminders,
|
||||||
|
groupedAvailableStaff:
|
||||||
|
groupedAvailableStaff ?? this.groupedAvailableStaff,
|
||||||
errorMessage: errorMessage,
|
errorMessage: errorMessage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -62,6 +67,7 @@ class TaskFormState extends Equatable {
|
|||||||
isGlobal,
|
isGlobal,
|
||||||
selectedStaffIds,
|
selectedStaffIds,
|
||||||
reminders,
|
reminders,
|
||||||
|
groupedAvailableStaff,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'package:get_it/get_it.dart';
|
|||||||
part 'task_list_state.dart';
|
part 'task_list_state.dart';
|
||||||
|
|
||||||
class TaskListCubit extends Cubit<TaskListState> {
|
class TaskListCubit extends Cubit<TaskListState> {
|
||||||
final TaskRepository _repository = GetIt.I.get<TaskRepository>();
|
final TasksRepository _repository = GetIt.I.get<TasksRepository>();
|
||||||
final String currentCompanyId;
|
final String currentCompanyId;
|
||||||
final String? currentStoreId;
|
final String? currentStoreId;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
|
||||||
import 'package:flux/core/enums_and_consts/consts.dart';
|
import 'package:flux/core/enums_and_consts/consts.dart';
|
||||||
import 'package:flux/features/tasks/models/task_reminder_config.dart';
|
import 'package:flux/features/tasks/models/task_reminder_config.dart';
|
||||||
import 'package:flux/features/tasks/models/task_status.dart';
|
import 'package:flux/features/tasks/models/task_status.dart';
|
||||||
@@ -10,46 +9,38 @@ import 'package:supabase_flutter/supabase_flutter.dart';
|
|||||||
// Sostituisci con i percorsi corretti di FLUX
|
// Sostituisci con i percorsi corretti di FLUX
|
||||||
import 'package:flux/features/tasks/models/task_model.dart';
|
import 'package:flux/features/tasks/models/task_model.dart';
|
||||||
|
|
||||||
class TaskRepository {
|
class TasksRepository {
|
||||||
final SupabaseClient _supabase;
|
final _supabase = GetIt.I.get<SupabaseClient>();
|
||||||
|
|
||||||
TaskRepository({SupabaseClient? supabase})
|
// =========================================================================
|
||||||
: _supabase = supabase ?? Supabase.instance.client;
|
// LETTURA REMINDER (Per il form in edit)
|
||||||
|
// =========================================================================
|
||||||
|
Future<List<TaskReminderConfig>> fetchPersonalReminders({
|
||||||
|
required String taskId,
|
||||||
|
required String staffId,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await _supabase
|
||||||
|
.from('task_reminders')
|
||||||
|
.select()
|
||||||
|
.eq('task_id', taskId)
|
||||||
|
.eq('staff_id', staffId)
|
||||||
|
.eq(
|
||||||
|
'is_forced',
|
||||||
|
false,
|
||||||
|
); // Peschiamo SOLO quelli modificabili dall'utente
|
||||||
|
|
||||||
// --- LOGICA REAL-TIME (Il Campanello) ---
|
return (response as List)
|
||||||
Stream<void> watchCompanyTasks(String companyId) {
|
.map(
|
||||||
// Usiamo un broadcast nel caso più bloc volessero ascoltarlo in futuro
|
(r) => TaskReminderConfig(
|
||||||
final controller = StreamController<void>.broadcast();
|
minutesBefore: r['minutes_before'],
|
||||||
|
channel: r['channel'],
|
||||||
final channel = _supabase.channel('public:tasks_company_$companyId');
|
),
|
||||||
|
)
|
||||||
channel
|
.toList();
|
||||||
.onPostgresChanges(
|
} catch (e) {
|
||||||
event: PostgresChangeEvent.all,
|
throw Exception('Errore fetch personal reminders: $e');
|
||||||
schema: 'public',
|
}
|
||||||
table: Tables.tasks,
|
|
||||||
filter: PostgresChangeFilter(
|
|
||||||
type: PostgresChangeFilterType.eq,
|
|
||||||
column: 'company_id',
|
|
||||||
value: companyId,
|
|
||||||
),
|
|
||||||
callback: (payload) {
|
|
||||||
if (!controller.isClosed) {
|
|
||||||
controller.add(
|
|
||||||
null,
|
|
||||||
); // Suoniamo il campanello! Nessun dato, solo il "ding"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.subscribe();
|
|
||||||
|
|
||||||
// Quando il Cubit smette di ascoltare, puliamo il canale Supabase in automatico
|
|
||||||
controller.onCancel = () {
|
|
||||||
channel.unsubscribe();
|
|
||||||
controller.close();
|
|
||||||
};
|
|
||||||
|
|
||||||
return controller.stream;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- RECUPERO DEI TASK FILTRATI ---
|
// --- RECUPERO DEI TASK FILTRATI ---
|
||||||
@@ -107,385 +98,257 @@ class TaskRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// REALTIME STREAM (La sentinella per la bacheca)
|
||||||
|
// =========================================================================
|
||||||
|
Stream<List<TaskModel>> watchCompanyTasks(String companyId) {
|
||||||
|
return _supabase
|
||||||
|
.from('tasks')
|
||||||
|
.stream(primaryKey: ['id'])
|
||||||
|
.eq('company_id', companyId)
|
||||||
|
.map((listOfMaps) {
|
||||||
|
return listOfMaps.map((map) => TaskModel.fromMap(map)).toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// CREAZIONE (Insert)
|
||||||
|
// =========================================================================
|
||||||
Future<void> createTask({
|
Future<void> createTask({
|
||||||
required TaskModel task,
|
required TaskModel task,
|
||||||
required List<String> assignedStaffIds,
|
required List<String> assignedStaffIds,
|
||||||
required String currentUserId,
|
required String currentUserId,
|
||||||
required List<TaskReminderConfig> currentUserCustomReminders,
|
required List<TaskReminderConfig> currentUserCustomReminders,
|
||||||
TaskReminderConfig?
|
TaskReminderConfig? managerForcedOverride,
|
||||||
managerForcedOverride, // Opzionale: l'avviso forzato del manager
|
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
// 1. Inserimento del Task principale -> otteniamo il taskId
|
// 1. Inseriamo il Task principale per farci generare l'ID dal DB
|
||||||
// 2. Inserimento dei record in task_assignments
|
final taskResponse = await _supabase
|
||||||
final String taskId = task.id!;
|
.from('tasks')
|
||||||
|
.insert(task.toMap()) // Assicurati che toMap() escluda l'id se è null
|
||||||
List<Map<String, dynamic>> remindersToInsert = [];
|
.select('id')
|
||||||
|
|
||||||
// 3. Recuperiamo i default degli ALTRI utenti assegnati
|
|
||||||
final otherStaffIds = assignedStaffIds
|
|
||||||
.where((id) => id != currentUserId)
|
|
||||||
.toList();
|
|
||||||
List<dynamic> otherDefaults = [];
|
|
||||||
if (otherStaffIds.isNotEmpty) {
|
|
||||||
otherDefaults = await _supabase
|
|
||||||
.from('staff_task_reminder_defaults')
|
|
||||||
.select()
|
|
||||||
.inFilter('staff_id', otherStaffIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. CICLO DI COSTRUZIONE DELLA CODA REMINDER
|
|
||||||
for (var staffId in assignedStaffIds) {
|
|
||||||
// CASO A: È l'utente loggato che sta creando/partecipando al task
|
|
||||||
if (staffId == currentUserId) {
|
|
||||||
for (var config in currentUserCustomReminders) {
|
|
||||||
final triggerAt = task.dueDate?.subtract(
|
|
||||||
Duration(minutes: config.minutesBefore),
|
|
||||||
);
|
|
||||||
if (triggerAt != null && triggerAt.isAfter(DateTime.now())) {
|
|
||||||
remindersToInsert.add({
|
|
||||||
'company_id': task.companyId,
|
|
||||||
'task_id': taskId,
|
|
||||||
'staff_id': currentUserId,
|
|
||||||
'minutes_before': config.minutesBefore,
|
|
||||||
'channel': config.channel,
|
|
||||||
'trigger_at': triggerAt.toIso8601String(),
|
|
||||||
'is_forced': false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// CASO B: Sono gli altri assegnatari -> ereditano i loro default personali dal DB
|
|
||||||
else {
|
|
||||||
final staffRules = otherDefaults.where(
|
|
||||||
(row) => row['staff_id'] == staffId,
|
|
||||||
);
|
|
||||||
for (var rule in staffRules) {
|
|
||||||
final minutesBefore = rule['minutes_before'] as int;
|
|
||||||
final triggerAt = task.dueDate?.subtract(
|
|
||||||
Duration(minutes: minutesBefore),
|
|
||||||
);
|
|
||||||
if (triggerAt != null && triggerAt.isAfter(DateTime.now())) {
|
|
||||||
remindersToInsert.add({
|
|
||||||
'company_id': task.companyId,
|
|
||||||
'task_id': taskId,
|
|
||||||
'staff_id': staffId,
|
|
||||||
'minutes_before': minutesBefore,
|
|
||||||
'channel': rule['channel'],
|
|
||||||
'trigger_at': triggerAt.toIso8601String(),
|
|
||||||
'is_forced': false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CASO C: Il creatore ha impostato un avviso forzato (Override molto importante)
|
|
||||||
if (managerForcedOverride != null && task.dueDate != null) {
|
|
||||||
final triggerAt = task.dueDate!.subtract(
|
|
||||||
Duration(minutes: managerForcedOverride.minutesBefore),
|
|
||||||
);
|
|
||||||
if (triggerAt.isAfter(DateTime.now())) {
|
|
||||||
remindersToInsert.add({
|
|
||||||
'company_id': task.companyId,
|
|
||||||
'task_id': taskId,
|
|
||||||
'staff_id': staffId, // Lo beccheranno tutti
|
|
||||||
'minutes_before': managerForcedOverride.minutesBefore,
|
|
||||||
'channel': managerForcedOverride.channel,
|
|
||||||
'trigger_at': triggerAt.toIso8601String(),
|
|
||||||
'is_forced':
|
|
||||||
true, // Chiude la possibilità di cancellarlo lato utente
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Sparata unica di Bulk Insert su task_reminders
|
|
||||||
if (remindersToInsert.isNotEmpty) {
|
|
||||||
await _supabase.from('task_reminders').insert(remindersToInsert);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Errore creazione task: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 3. AGGIORNAMENTO DEL TASK ---
|
|
||||||
Future<TaskModel> updateTask(TaskModel task) async {
|
|
||||||
if (task.id == null) {
|
|
||||||
throw Exception('ID Task mancante. Impossibile aggiornare.');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final taskData = task.toMap();
|
|
||||||
taskData.remove(
|
|
||||||
'assigned_to_ids',
|
|
||||||
); // Sempre via l'array dal body principale
|
|
||||||
|
|
||||||
// 1. Aggiorniamo i dati base del task (titolo, stato, scadenza, ecc.)
|
|
||||||
await _supabase.from(Tables.tasks).update(taskData).eq('id', task.id!);
|
|
||||||
|
|
||||||
// 2. Sincronizziamo in modo distruttivo gli assegnatari nella tabella di giunzione
|
|
||||||
await _syncAssignments(task.id!, task.assignedToIds);
|
|
||||||
|
|
||||||
// 3. Ritorniamo il modello fresco di DB
|
|
||||||
return await getTaskById(task.id!);
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Errore nell\'aggiornamento del task: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createTask({
|
|
||||||
required TaskModel task,
|
|
||||||
required List<String> assignedStaffIds,
|
|
||||||
required String currentUserId,
|
|
||||||
required List<TaskReminderConfig> currentUserCustomReminders,
|
|
||||||
TaskReminderConfig?
|
|
||||||
managerForcedOverride, // Opzionale: l'avviso forzato del manager
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
// 1. Inserimento del Task principale -> otteniamo il taskId
|
|
||||||
// 2. Inserimento dei record in task_assignments
|
|
||||||
final String taskId = task.id;
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> remindersToInsert = [];
|
|
||||||
|
|
||||||
// 3. Recuperiamo i default degli ALTRI utenti assegnati
|
|
||||||
final otherStaffIds = assignedStaffIds
|
|
||||||
.where((id) => id != currentUserId)
|
|
||||||
.toList();
|
|
||||||
List<dynamic> otherDefaults = [];
|
|
||||||
if (otherStaffIds.isNotEmpty) {
|
|
||||||
otherDefaults = await _supabase
|
|
||||||
.from('staff_task_reminder_defaults')
|
|
||||||
.select()
|
|
||||||
.inFilter('staff_id', otherStaffIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. CICLO DI COSTRUZIONE DELLA CODA REMINDER
|
|
||||||
for (var staffId in assignedStaffIds) {
|
|
||||||
// CASO A: È l'utente loggato che sta creando/partecipando al task
|
|
||||||
if (staffId == currentUserId) {
|
|
||||||
for (var config in currentUserCustomReminders) {
|
|
||||||
final triggerAt = task.dueDate?.subtract(
|
|
||||||
Duration(minutes: config.minutesBefore),
|
|
||||||
);
|
|
||||||
if (triggerAt != null && triggerAt.isAfter(DateTime.now())) {
|
|
||||||
remindersToInsert.add({
|
|
||||||
'company_id': task.companyId,
|
|
||||||
'task_id': taskId,
|
|
||||||
'staff_id': currentUserId,
|
|
||||||
'minutes_before': config.minutesBefore,
|
|
||||||
'channel': config.channel,
|
|
||||||
'trigger_at': triggerAt.toIso8601String(),
|
|
||||||
'is_forced': false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// CASO B: Sono gli altri assegnatari -> ereditano i loro default personali dal DB
|
|
||||||
else {
|
|
||||||
final staffRules = otherDefaults.where(
|
|
||||||
(row) => row['staff_id'] == staffId,
|
|
||||||
);
|
|
||||||
for (var rule in staffRules) {
|
|
||||||
final minutesBefore = rule['minutes_before'] as int;
|
|
||||||
final triggerAt = task.dueDate?.subtract(
|
|
||||||
Duration(minutes: minutesBefore),
|
|
||||||
);
|
|
||||||
if (triggerAt != null && triggerAt.isAfter(DateTime.now())) {
|
|
||||||
remindersToInsert.add({
|
|
||||||
'company_id': task.companyId,
|
|
||||||
'task_id': taskId,
|
|
||||||
'staff_id': staffId,
|
|
||||||
'minutes_before': minutesBefore,
|
|
||||||
'channel': rule['channel'],
|
|
||||||
'trigger_at': triggerAt.toIso8601String(),
|
|
||||||
'is_forced': false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CASO C: Il creatore ha impostato un avviso forzato (Override molto importante)
|
|
||||||
if (managerForcedOverride != null && task.dueDate != null) {
|
|
||||||
final triggerAt = task.dueDate!.subtract(
|
|
||||||
Duration(minutes: managerForcedOverride.minutesBefore),
|
|
||||||
);
|
|
||||||
if (triggerAt.isAfter(DateTime.now())) {
|
|
||||||
remindersToInsert.add({
|
|
||||||
'company_id': task.companyId,
|
|
||||||
'task_id': taskId,
|
|
||||||
'staff_id': staffId, // Lo beccheranno tutti
|
|
||||||
'minutes_before': managerForcedOverride.minutesBefore,
|
|
||||||
'channel': managerForcedOverride.channel,
|
|
||||||
'trigger_at': triggerAt.toIso8601String(),
|
|
||||||
'is_forced':
|
|
||||||
true, // Chiude la possibilità di cancellarlo lato utente
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Sparata unica di Bulk Insert su task_reminders
|
|
||||||
if (remindersToInsert.isNotEmpty) {
|
|
||||||
await _supabase.from('task_reminders').insert(remindersToInsert);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Errore creazione task: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 4. ELIMINAZIONE DEL TASK ---
|
|
||||||
Future<void> deleteTask(String taskId) async {
|
|
||||||
try {
|
|
||||||
// Eliminando il task, se la Foreign Key su Supabase ha "ON DELETE CASCADE",
|
|
||||||
// le righe nella tabella di giunzione si distruggeranno da sole in automatico!
|
|
||||||
await _supabase.from(Tables.tasks).delete().eq('id', taskId);
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Errore nell\'eliminazione del task: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- HELPER: RECUPERO SINGOLO TASK ---
|
|
||||||
Future<TaskModel> getTaskById(String taskId) async {
|
|
||||||
try {
|
|
||||||
final response = await _supabase
|
|
||||||
.from(Tables.tasks)
|
|
||||||
.select('''
|
|
||||||
*,
|
|
||||||
task_assignments:${Tables.taskAssignments} (
|
|
||||||
${Tables.staffMembers} (*)
|
|
||||||
)
|
|
||||||
''')
|
|
||||||
.eq('id', taskId)
|
|
||||||
.single();
|
.single();
|
||||||
return TaskModel.fromMap(response);
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Errore nel recupero del singolo task: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- HELPER: SINCRONIZZAZIONE DELLA TABELLA DI GIUNZIONE ---
|
final String taskId = taskResponse['id'];
|
||||||
Future<void> _syncAssignments(String taskId, List<String> staffIds) async {
|
|
||||||
// TECNICA "WIPE & REPLACE":
|
|
||||||
// Invece di capire chi è stato aggiunto e chi tolto, eliminiamo tutti i record
|
|
||||||
// di questo task e li reinseriamo. È fulmineo ed esclude a priori bug di disallineamento.
|
|
||||||
|
|
||||||
// 1. Facciamo piazza pulita
|
// 2. Inseriamo le Assegnazioni (tabella task_assignments)
|
||||||
await _supabase.from(Tables.taskAssignments).delete().eq('task_id', taskId);
|
if (assignedStaffIds.isNotEmpty) {
|
||||||
|
final assignmentsToInsert = assignedStaffIds
|
||||||
// 2. Inseriamo le nuove assegnazioni (se ce ne sono)
|
.map(
|
||||||
if (staffIds.isNotEmpty) {
|
(staffId) => {
|
||||||
final List<Map<String, dynamic>> assignments = staffIds
|
|
||||||
.map(
|
|
||||||
(staffId) => {
|
|
||||||
'task_id': taskId,
|
|
||||||
'staff_id': staffId,
|
|
||||||
'company_id': GetIt.I.get<SessionCubit>().state.company!.id!,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
await _supabase.from(Tables.taskAssignments).insert(assignments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- IL MOTORE DELLA MAGIA ---
|
|
||||||
|
|
||||||
Future<void> generateTaskReminders({
|
|
||||||
required String taskId,
|
|
||||||
required String companyId,
|
|
||||||
required List<String> assignedStaffIds,
|
|
||||||
required DateTime? taskDueDate,
|
|
||||||
}) async {
|
|
||||||
if (assignedStaffIds.isEmpty) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. Recuperiamo i default di TUTTI i collaboratori coinvolti in un colpo solo
|
|
||||||
final response = await _supabase
|
|
||||||
.from('staff_task_reminder_defaults')
|
|
||||||
.select()
|
|
||||||
.eq('company_id', companyId)
|
|
||||||
.inFilter('staff_id', assignedStaffIds);
|
|
||||||
|
|
||||||
final List<Map<String, dynamic>> remindersToInsert = [];
|
|
||||||
|
|
||||||
for (var staffId in assignedStaffIds) {
|
|
||||||
// Cerchiamo le preferenze di questo specifico membro dello staff
|
|
||||||
final staffDefaults = response.where(
|
|
||||||
(row) => row['staff_id'] == staffId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (staffDefaults.isEmpty) {
|
|
||||||
// STRATEGIA FALLBACK: Se l'utente non ha mai configurato i suoi default,
|
|
||||||
// creiamo un reminder standard (es. una push 15 min prima) per non lasciarlo scoperto.
|
|
||||||
if (taskDueDate != null) {
|
|
||||||
remindersToInsert.add({
|
|
||||||
'company_id': companyId,
|
|
||||||
'task_id': taskId,
|
|
||||||
'staff_id': staffId,
|
|
||||||
'minutes_before': 15,
|
|
||||||
'channel': 'push',
|
|
||||||
'trigger_at': taskDueDate
|
|
||||||
.subtract(const Duration(minutes: 15))
|
|
||||||
.toIso8601String(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// E spariamo la push di creazione immediata come comportamento standard
|
|
||||||
_triggerImmediateNotification(staffId, taskId, 'push_creation');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. GESTIONE NOTIFICHE ISTANTANEE (EVENT-DRIVEN)
|
|
||||||
// Prendiamo la prima riga delle impostazioni dell'utente (tanto le colonne nuove sono speculari)
|
|
||||||
final userSetting = staffDefaults.first;
|
|
||||||
if (userSetting['notify_on_creation_push'] == true) {
|
|
||||||
_triggerImmediateNotification(staffId, taskId, 'push_creation');
|
|
||||||
}
|
|
||||||
if (userSetting['notify_on_creation_email'] == true) {
|
|
||||||
_triggerImmediateNotification(staffId, taskId, 'email_creation');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. GESTIONE REMINDER TEMPORIZZATI (TIME-DRIVEN)
|
|
||||||
if (taskDueDate != null) {
|
|
||||||
for (var rule in staffDefaults) {
|
|
||||||
final minutesBefore = rule['minutes_before'] as int;
|
|
||||||
final triggerAt = taskDueDate.subtract(
|
|
||||||
Duration(minutes: minutesBefore),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Se il task scade tra 5 minuti e il reminder è impostato a 1 ora prima,
|
|
||||||
// il trigger_at sarebbe nel passato. Lo inseriamo solo se è nel futuro!
|
|
||||||
if (triggerAt.isAfter(DateTime.now())) {
|
|
||||||
remindersToInsert.add({
|
|
||||||
'company_id': companyId,
|
|
||||||
'task_id': taskId,
|
'task_id': taskId,
|
||||||
'staff_id': staffId,
|
'staff_id': staffId,
|
||||||
'minutes_before': minutesBefore,
|
'company_id': task.companyId,
|
||||||
'channel': rule['channel'],
|
},
|
||||||
'trigger_at': triggerAt.toIso8601String(),
|
)
|
||||||
});
|
.toList();
|
||||||
|
await _supabase.from('task_assignments').insert(assignmentsToInsert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se non c'è data di scadenza, niente promemoria a tempo
|
||||||
|
if (task.dueDate == null || assignedStaffIds.isEmpty) return;
|
||||||
|
|
||||||
|
// 3. Setup Reminder: Peschiamo i default degli ALTRI dipendenti coinvolti
|
||||||
|
final otherStaffIds = assignedStaffIds
|
||||||
|
.where((id) => id != currentUserId)
|
||||||
|
.toList();
|
||||||
|
List<dynamic> otherDefaults = [];
|
||||||
|
if (otherStaffIds.isNotEmpty) {
|
||||||
|
otherDefaults = await _supabase
|
||||||
|
.from('staff_task_reminder_defaults')
|
||||||
|
.select()
|
||||||
|
.inFilter('staff_id', otherStaffIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Creiamo la lista Bulk Insert per la tabella task_reminders
|
||||||
|
List<Map<String, dynamic>> remindersToInsert = [];
|
||||||
|
|
||||||
|
for (var staffId in assignedStaffIds) {
|
||||||
|
// A) Se è l'utente loggato -> usa i reminder configurati nel form
|
||||||
|
if (staffId == currentUserId) {
|
||||||
|
for (var config in currentUserCustomReminders) {
|
||||||
|
final triggerAt = task.dueDate!.subtract(
|
||||||
|
Duration(minutes: config.minutesBefore),
|
||||||
|
);
|
||||||
|
if (triggerAt.isAfter(DateTime.now())) {
|
||||||
|
remindersToInsert.add(
|
||||||
|
_buildReminderRow(
|
||||||
|
task,
|
||||||
|
taskId,
|
||||||
|
staffId,
|
||||||
|
config,
|
||||||
|
triggerAt,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// B) Se è un collega -> eredita i suoi default preimpostati
|
||||||
|
else {
|
||||||
|
final staffRules = otherDefaults.where(
|
||||||
|
(row) => row['staff_id'] == staffId,
|
||||||
|
);
|
||||||
|
for (var rule in staffRules) {
|
||||||
|
final config = TaskReminderConfig(
|
||||||
|
minutesBefore: rule['minutes_before'],
|
||||||
|
channel: rule['channel'],
|
||||||
|
);
|
||||||
|
final triggerAt = task.dueDate!.subtract(
|
||||||
|
Duration(minutes: config.minutesBefore),
|
||||||
|
);
|
||||||
|
if (triggerAt.isAfter(DateTime.now())) {
|
||||||
|
remindersToInsert.add(
|
||||||
|
_buildReminderRow(
|
||||||
|
task,
|
||||||
|
taskId,
|
||||||
|
staffId,
|
||||||
|
config,
|
||||||
|
triggerAt,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// C) Override forzato del manager (per tutti)
|
||||||
|
if (managerForcedOverride != null) {
|
||||||
|
final triggerAt = task.dueDate!.subtract(
|
||||||
|
Duration(minutes: managerForcedOverride.minutesBefore),
|
||||||
|
);
|
||||||
|
if (triggerAt.isAfter(DateTime.now())) {
|
||||||
|
remindersToInsert.add(
|
||||||
|
_buildReminderRow(
|
||||||
|
task,
|
||||||
|
taskId,
|
||||||
|
staffId,
|
||||||
|
managerForcedOverride,
|
||||||
|
triggerAt,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Bulk Insert dei reminder temporizzati nella coda operativa
|
// 5. Inserimento massivo finale
|
||||||
if (remindersToInsert.isNotEmpty) {
|
if (remindersToInsert.isNotEmpty) {
|
||||||
await _supabase.from('task_reminders').insert(remindersToInsert);
|
await _supabase.from('task_reminders').insert(remindersToInsert);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Errore nella generazione dei reminder: $e');
|
throw Exception('Errore durante la creazione del task: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _triggerImmediateNotification(
|
// =========================================================================
|
||||||
String staffId,
|
// AGGIORNAMENTO (Update)
|
||||||
|
// =========================================================================
|
||||||
|
Future<void> updateTask({
|
||||||
|
required TaskModel task,
|
||||||
|
required List<String> assignedStaffIds,
|
||||||
|
required String currentUserId,
|
||||||
|
required List<TaskReminderConfig> currentUserCustomReminders,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final taskId = task.id!;
|
||||||
|
|
||||||
|
// 1. Aggiornamento dati Task Base
|
||||||
|
await _supabase
|
||||||
|
.from('tasks')
|
||||||
|
.update({
|
||||||
|
'title': task.title,
|
||||||
|
'description': task.description,
|
||||||
|
'due_date': task.dueDate?.toIso8601String(),
|
||||||
|
'store_id': task.storeId,
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
})
|
||||||
|
.eq('id', taskId);
|
||||||
|
|
||||||
|
// 2. Aggiornamento Assegnazioni: eliminiamo le vecchie, inseriamo le nuove
|
||||||
|
await _supabase.from('task_assignments').delete().eq('task_id', taskId);
|
||||||
|
if (assignedStaffIds.isNotEmpty) {
|
||||||
|
final assignmentsToInsert = assignedStaffIds
|
||||||
|
.map(
|
||||||
|
(staffId) => {
|
||||||
|
'task_id': taskId,
|
||||||
|
'staff_id': staffId,
|
||||||
|
'company_id': task.companyId,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
await _supabase.from('task_assignments').insert(assignmentsToInsert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se non c'è una data, eliminiamo tutti i vecchi promemoria dell'utente loggato per pulizia
|
||||||
|
if (task.dueDate == null) {
|
||||||
|
await _supabase
|
||||||
|
.from('task_reminders')
|
||||||
|
.delete()
|
||||||
|
.eq('task_id', taskId)
|
||||||
|
.eq('staff_id', currentUserId)
|
||||||
|
.eq('is_forced', false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. GESTIONE REMINDER: Puliamo SOLO quelli modificabili dall'utente loggato
|
||||||
|
await _supabase
|
||||||
|
.from('task_reminders')
|
||||||
|
.delete()
|
||||||
|
.eq('task_id', taskId)
|
||||||
|
.eq('staff_id', currentUserId)
|
||||||
|
.eq('is_forced', false); // NON tocchiamo quelli forzati dal manager!
|
||||||
|
|
||||||
|
// 4. Inseriamo le nuove configurazioni salvate dal Cubit (solo se è ancora tra gli assegnatari)
|
||||||
|
if (assignedStaffIds.contains(currentUserId) &&
|
||||||
|
currentUserCustomReminders.isNotEmpty) {
|
||||||
|
final List<Map<String, dynamic>> toInsert = [];
|
||||||
|
|
||||||
|
for (var config in currentUserCustomReminders) {
|
||||||
|
final triggerAt = task.dueDate!.subtract(
|
||||||
|
Duration(minutes: config.minutesBefore),
|
||||||
|
);
|
||||||
|
if (triggerAt.isAfter(DateTime.now())) {
|
||||||
|
toInsert.add(
|
||||||
|
_buildReminderRow(
|
||||||
|
task,
|
||||||
|
taskId,
|
||||||
|
currentUserId,
|
||||||
|
config,
|
||||||
|
triggerAt,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toInsert.isNotEmpty) {
|
||||||
|
await _supabase.from('task_reminders').insert(toInsert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Errore durante l\'aggiornamento del task: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- HELPER PRIVATO PER LA MAPPA DEL REMINDER ---
|
||||||
|
Map<String, dynamic> _buildReminderRow(
|
||||||
|
TaskModel task,
|
||||||
String taskId,
|
String taskId,
|
||||||
String type,
|
String staffId,
|
||||||
|
TaskReminderConfig config,
|
||||||
|
DateTime triggerAt,
|
||||||
|
bool isForced,
|
||||||
) {
|
) {
|
||||||
// Questa funzione chiamerà direttamente l'Edge Function di Supabase
|
return {
|
||||||
// per far squillare il telefono o mandare la mail ADESSO.
|
'company_id': task.companyId,
|
||||||
// La implementeremo appena il backend sarà pronto!
|
'task_id': taskId,
|
||||||
|
'staff_id': staffId,
|
||||||
|
'minutes_before': config.minutesBefore,
|
||||||
|
'channel': config.channel,
|
||||||
|
'trigger_at': triggerAt.toIso8601String(),
|
||||||
|
'is_forced': isForced,
|
||||||
|
'is_sent': false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ class TaskModel extends Equatable {
|
|||||||
this.storeId,
|
this.storeId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bool get isGlobal => storeId == null;
|
||||||
|
|
||||||
// --- FACTORY: MODELLO VUOTO (Per le creazioni) ---
|
// --- FACTORY: MODELLO VUOTO (Per le creazioni) ---
|
||||||
factory TaskModel.empty({String? companyId, String? createdById}) {
|
factory TaskModel.empty({String? companyId, String? createdById}) {
|
||||||
return TaskModel(
|
return TaskModel(
|
||||||
|
|||||||
Reference in New Issue
Block a user