a
This commit is contained in:
57
lib/features/tracking/blocs/tracking_cubit.dart
Normal file
57
lib/features/tracking/blocs/tracking_cubit.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/features/tracking/data/tracking_repository.dart';
|
||||
import 'package:flux/features/tracking/models/tracking_model.dart';
|
||||
|
||||
// Stati base: initial, loading, loaded, error
|
||||
class TrackingState {
|
||||
final bool isLoading;
|
||||
final List<TrackingModel> logs;
|
||||
|
||||
TrackingState({this.isLoading = false, this.logs = const []});
|
||||
}
|
||||
|
||||
class TrackingCubit extends Cubit<TrackingState> {
|
||||
final TrackingRepository _repo;
|
||||
final String parentId;
|
||||
final TrackingParentType parentType;
|
||||
final String companyId;
|
||||
|
||||
TrackingCubit({
|
||||
required TrackingRepository repo,
|
||||
required this.parentId,
|
||||
required this.parentType,
|
||||
required this.companyId,
|
||||
}) : _repo = repo,
|
||||
super(TrackingState()) {
|
||||
loadTrackings();
|
||||
}
|
||||
|
||||
Future<void> loadTrackings() async {
|
||||
emit(TrackingState(isLoading: true, logs: state.logs));
|
||||
final trackings = await _repo.getTrackingsByParent(
|
||||
parentId: parentId,
|
||||
parentType: parentType,
|
||||
);
|
||||
emit(TrackingState(isLoading: false, logs: trackings));
|
||||
}
|
||||
|
||||
Future<void> addManualNote(
|
||||
String message,
|
||||
bool isInternal, {
|
||||
String? staffId,
|
||||
}) async {
|
||||
// Aggiungiamo un feedback visivo immediato (Optimistic UI) se vogliamo,
|
||||
// oppure semplicemente mostriamo il loading
|
||||
await _repo.logQuickEvent(
|
||||
companyId: companyId,
|
||||
message: message,
|
||||
type: TrackingType.manualNote,
|
||||
parentId: parentId,
|
||||
parentType: parentType,
|
||||
staffId: staffId,
|
||||
isInternal: isInternal,
|
||||
);
|
||||
// Ricarichiamo la lista fresca dal server
|
||||
await loadTrackings();
|
||||
}
|
||||
}
|
||||
57
lib/features/tracking/data/tracking_repository.dart
Normal file
57
lib/features/tracking/data/tracking_repository.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'package:flux/features/tracking/models/tracking_model.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class TrackingRepository {
|
||||
final SupabaseClient _supabase = GetIt.I.get<SupabaseClient>();
|
||||
|
||||
TrackingRepository();
|
||||
|
||||
/// Recupera la cronologia di un'entità (Ticket o Operazione)
|
||||
Future<List<TrackingModel>> getTrackingsByParent({
|
||||
required String parentId, // <-- Reso obbligatorio
|
||||
required TrackingParentType parentType, // <-- Reso obbligatorio
|
||||
}) async {
|
||||
// Facciamo la query con la JOIN per recuperare il nome dello staff al volo
|
||||
final response = await _supabase
|
||||
.from('tracking')
|
||||
.select('*, staff_member(name)')
|
||||
.eq('parent_id', parentId)
|
||||
.eq('parent_type', parentType.name)
|
||||
.order(
|
||||
'created_at',
|
||||
ascending: true,
|
||||
); // ascending: true per avere la timeline dall'alto (vecchi) al basso (nuovi)
|
||||
|
||||
return response.map((map) => TrackingModel.fromMap(map)).toList();
|
||||
}
|
||||
|
||||
/// Inserisce un nuovo evento di tracking
|
||||
Future<void> logEvent(TrackingModel tracking) async {
|
||||
await _supabase.from('tracking').insert(tracking.toMap());
|
||||
}
|
||||
|
||||
/// Metodo helper rapido per loggare un cambio di stato o una nota
|
||||
Future<void> logQuickEvent({
|
||||
required String companyId,
|
||||
required String message,
|
||||
required TrackingType type,
|
||||
required String parentId, // <-- Reso obbligatorio
|
||||
required TrackingParentType parentType, // <-- Reso obbligatorio
|
||||
String? staffId,
|
||||
bool isInternal = true,
|
||||
}) async {
|
||||
final log = TrackingModel(
|
||||
createdAt:
|
||||
DateTime.now(), // Questo verrà ignorato dal toMap in fase di insert, ma serve al modello
|
||||
companyId: companyId,
|
||||
staffId: staffId,
|
||||
parentId: parentId,
|
||||
parentType: parentType,
|
||||
eventType: type,
|
||||
isInternal: isInternal,
|
||||
message: message,
|
||||
);
|
||||
await logEvent(log);
|
||||
}
|
||||
}
|
||||
132
lib/features/tracking/models/tracking_model.dart
Normal file
132
lib/features/tracking/models/tracking_model.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
enum TrackingType {
|
||||
statusChange,
|
||||
manualNote,
|
||||
systemAlert,
|
||||
customerContact,
|
||||
assignment;
|
||||
|
||||
static TrackingType fromString(String value) {
|
||||
return TrackingType.values.firstWhere(
|
||||
(e) => e.name == value,
|
||||
orElse: () => TrackingType.manualNote,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum TrackingParentType {
|
||||
ticket,
|
||||
operation;
|
||||
|
||||
String get value => name;
|
||||
|
||||
static TrackingParentType fromString(String val) {
|
||||
return TrackingParentType.values.firstWhere(
|
||||
(e) => e.name == val,
|
||||
orElse: () => TrackingParentType.ticket, // Default di sicurezza
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TrackingModel extends Equatable {
|
||||
final String? id;
|
||||
final DateTime createdAt;
|
||||
final String companyId;
|
||||
final String? staffId;
|
||||
final String? staffName; // Per non fare mille join, lo prendiamo dal repo
|
||||
final String parentId;
|
||||
final TrackingParentType parentType;
|
||||
final TrackingType eventType;
|
||||
final bool isInternal;
|
||||
final String message;
|
||||
|
||||
const TrackingModel({
|
||||
this.id,
|
||||
required this.createdAt,
|
||||
required this.companyId,
|
||||
this.staffId,
|
||||
this.staffName,
|
||||
required this.parentId,
|
||||
required this.parentType,
|
||||
required this.eventType,
|
||||
required this.isInternal,
|
||||
required this.message,
|
||||
});
|
||||
|
||||
TrackingModel copyWith({
|
||||
String? id,
|
||||
DateTime? createdAt,
|
||||
String? companyId,
|
||||
String? staffId,
|
||||
String? staffName,
|
||||
TrackingParentType? parentType,
|
||||
String? parentId,
|
||||
TrackingType? eventType,
|
||||
bool? isInternal,
|
||||
String? message,
|
||||
}) {
|
||||
return TrackingModel(
|
||||
id: id ?? this.id,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
companyId: companyId ?? this.companyId,
|
||||
staffId: staffId ?? this.staffId,
|
||||
staffName: staffName ?? this.staffName,
|
||||
parentId: parentId ?? this.parentId,
|
||||
parentType: parentType ?? this.parentType,
|
||||
eventType: eventType ?? this.eventType,
|
||||
isInternal: isInternal ?? this.isInternal,
|
||||
message: message ?? this.message,
|
||||
);
|
||||
}
|
||||
|
||||
factory TrackingModel.fromMap(Map<String, dynamic> map) {
|
||||
return TrackingModel(
|
||||
id: map['id'],
|
||||
createdAt: DateTime.parse(map['created_at']),
|
||||
companyId: map['company_id'],
|
||||
staffId: map['staff_id'],
|
||||
staffName: map['staff_member']?['name'], // Se fai la join su staff_member
|
||||
parentId: map['parent_id'] as String,
|
||||
parentType: TrackingParentType.fromString(map['parent_type']),
|
||||
eventType: TrackingType.fromString(map['event_type']),
|
||||
isInternal: map['is_internal'] ?? true,
|
||||
message: map['message'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
final map = <String, dynamic>{
|
||||
'company_id': companyId,
|
||||
'staff_id': staffId,
|
||||
'parent_id': parentId,
|
||||
'parent_type': parentType.name,
|
||||
'event_type': eventType.name,
|
||||
'is_internal': isInternal,
|
||||
'message': message,
|
||||
};
|
||||
|
||||
// Aggiungiamo id e data SOLO se stiamo aggiornando un record esistente.
|
||||
// In fase di creazione (insert), li omettiamo così Supabase usa i valori di default (gen_random_uuid e now()).
|
||||
if (id != null) {
|
||||
map['id'] = id;
|
||||
map['created_at'] = createdAt.toIso8601String();
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
createdAt,
|
||||
companyId,
|
||||
staffId,
|
||||
staffName,
|
||||
parentId,
|
||||
parentType,
|
||||
eventType,
|
||||
isInternal,
|
||||
message,
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user