Worklog, anti geo spoofing and UI Polish
This commit is contained in:
@@ -328,9 +328,10 @@ final tasksProvider = StreamProvider<List<Task>>((ref) {
|
||||
onOfflineData: () async {
|
||||
if (!AppRepository.isConfigured) return [];
|
||||
try {
|
||||
return await AppRepository.instance.get<Task>(
|
||||
final all = await AppRepository.instance.get<Task>(
|
||||
policy: OfflineFirstGetPolicy.localOnly,
|
||||
);
|
||||
return {for (final t in all) t.id: t}.values.toList();
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
@@ -363,6 +364,7 @@ final tasksProvider = StreamProvider<List<Task>>((ref) {
|
||||
'[tasksProvider] reconnected — syncing ${pending.length} pending task(s)',
|
||||
);
|
||||
}
|
||||
final failedTaskIds = <String>{};
|
||||
for (final task in pending) {
|
||||
try {
|
||||
final syncPayload = <String, dynamic>{
|
||||
@@ -393,18 +395,26 @@ final tasksProvider = StreamProvider<List<Task>>((ref) {
|
||||
} catch (e) {
|
||||
final msg = e.toString();
|
||||
if (msg.contains('23505') || msg.contains('duplicate key')) {
|
||||
// Task already in DB (Brick may have synced it first) — safe to
|
||||
// leave cleanup to the realtime stream listener in tasks_list_screen.
|
||||
// Task already in DB — treat as success so it's cleared from pending.
|
||||
debugPrint(
|
||||
'[tasksProvider] pending task already in DB id=${task.id}',
|
||||
);
|
||||
} else {
|
||||
failedTaskIds.add(task.id);
|
||||
debugPrint(
|
||||
'[tasksProvider] failed to sync pending task id=${task.id}: $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clear successfully synced pending tasks. Don't rely on the
|
||||
// tasks_list_screen listener alone — it only fires when that screen is
|
||||
// active, leaving the banner stuck on other screens indefinitely.
|
||||
if (pending.isNotEmpty) {
|
||||
final remaining =
|
||||
pending.where((t) => failedTaskIds.contains(t.id)).toList();
|
||||
ref.read(offlinePendingTasksProvider.notifier).state = remaining;
|
||||
}
|
||||
|
||||
// ── 2. Sync offline field edits for EXISTING tasks (PATCH) ──────────
|
||||
// pendingUpdates already had pending-task entries removed in step 1.
|
||||
@@ -573,9 +583,10 @@ final tasksProvider = StreamProvider<List<Task>>((ref) {
|
||||
// When offline, seed from Brick local SQLite cache instead of network.
|
||||
if (!ref.read(isOnlineProvider) && AppRepository.isConfigured) {
|
||||
try {
|
||||
final cached = await AppRepository.instance.get<Task>(
|
||||
final all = await AppRepository.instance.get<Task>(
|
||||
policy: OfflineFirstGetPolicy.localOnly,
|
||||
);
|
||||
final cached = {for (final t in all) t.id: t}.values.toList();
|
||||
debugPrint('[tasksProvider] offline seed: ${cached.length} tasks from local cache');
|
||||
final payload = _buildTaskPayload(
|
||||
tasks: cached,
|
||||
@@ -2788,3 +2799,52 @@ class TaskAssignmentsController {
|
||||
ref.read(offlinePendingAssignmentsProvider.notifier).state = current;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Work Log helpers ────────────────────────────────────────────────────────
|
||||
|
||||
/// All task_assignments rows where user_id = [userId].
|
||||
/// Used by the Work Log tab to identify which tasks the user is the assignee of.
|
||||
final taskAssignmentsForUserProvider =
|
||||
FutureProvider.autoDispose.family<List<TaskAssignment>, String>(
|
||||
(ref, userId) async {
|
||||
try {
|
||||
final data = await Supabase.instance.client
|
||||
.from('task_assignments')
|
||||
.select()
|
||||
.eq('user_id', userId);
|
||||
return data.map(TaskAssignment.fromMap).toList();
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/// All task_activity_logs rows for the given task IDs — single batch query.
|
||||
/// Key is a sorted, comma-joined string of task IDs so Riverpod caches the
|
||||
/// provider correctly across rebuilds (List equality is reference-based in Dart).
|
||||
/// Returns logs in DESCENDING order (newest first).
|
||||
final taskActivityLogsForTasksProvider =
|
||||
FutureProvider.autoDispose.family<List<TaskActivityLog>, String>(
|
||||
(ref, taskIdsKey) async {
|
||||
if (taskIdsKey.isEmpty) return [];
|
||||
final taskIds = taskIdsKey.split(',');
|
||||
try {
|
||||
final data = await Supabase.instance.client
|
||||
.from('task_activity_logs')
|
||||
.select()
|
||||
.inFilter('task_id', taskIds)
|
||||
.order('created_at', ascending: false);
|
||||
return data.map(TaskActivityLog.fromMap).toList();
|
||||
} catch (_) {
|
||||
try {
|
||||
final all = await cachedListFromBrick<TaskActivityLog>();
|
||||
final idSet = taskIds.toSet();
|
||||
final filtered = all.where((l) => idSet.contains(l.taskId)).toList();
|
||||
filtered.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||
return filtered;
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user