My App

Logs Refactoring Plan

SmartCrew Admin - Logs Refactoring Plan

Admin Panel Refactoring Plan

Overview

This document outlines the refactoring plan for the admin panel to support the new DailyLog architecture with standalone collections and optimized caching.

Current Architecture Issues

  1. Daily logs stored as subcollection under users collection
  2. Inefficient querying for admin reporting across multiple users
  3. No caching mechanism for frequently accessed admin data
  4. Performance issues when loading user logs in admin panel

Target Architecture

daily_logs (top-level collection)
  ├── documentId: "userId_date" (e.g., "user123_20231025")
  │   ├── userId: string (reference to user document)
  │   ├── companyId: string (reference to company document)
  │   ├── logDate: timestamp
  │   ├── status: string
  │   ├── isDayOff: boolean
  │   ├── dayOffDetails: map
  │   ├── activities: subcollection
  │   └── clockEvents: subcollection

Required Changes

1. Update Data Fetching Methods

Current Implementation (to be changed):

final dailyLogs = await fireStore
    .collection('users')
    .doc(user.firebaseUid)
    .collection('dailyLogs')
    .get();

New Implementation:

final dailyLogs = await fireStore
    .collection('daily_logs')
    .where('userId', isEqualTo: user.firebaseUid)
    .where('companyId', isEqualTo: workspaceId)
    .get();

2. Implement Admin-Specific Caching

class AdminLogCache {
  final Map<String, List<DailyLog>> _userLogsCache = {}; // Key: userId
  final Map<String, Map<DateTime, DailyLog>> _dateRangeCache = {}; // Key: "startDate_endDate"
  final Map<String, DateTime> _cacheTimestamps = {};
  
  static const Duration userLogsCacheDuration = Duration(minutes: 30);
  static const Duration dateRangeCacheDuration = Duration(hours: 2);
  
  // Cache user logs
  void cacheUserLogs(String userId, List<DailyLog> logs) {
    _userLogsCache[userId] = logs;
    _cacheTimestamps['user_$userId'] = DateTime.now();
  }
  
  // Get cached user logs
  List<DailyLog>? getUserLogs(String userId) {
    final key = 'user_$userId';
    final timestamp = _cacheTimestamps[key];
    
    if (timestamp == null || 
        DateTime.now().difference(timestamp) > userLogsCacheDuration) {
      return null;
    }
    
    return _userLogsCache[userId];
  }
  
  // Similar methods for date range caching
  // ...
}

3. Update User Log Fetching

Future<void> _fetchLogs(AppUser user, String workspaceId) async {
  final List<DailyLog> userLogs = List.empty(growable: true);
  
  // Check cache first
  final cachedLogs = _adminCache.getUserLogs(user.firebaseUid);
  if (cachedLogs != null) {
    userLogs.addAll(cachedLogs);
    _laborRepository.addUserLog(UserLogWrapper(user: user, logs: userLogs));
    return;
  }
  
  // Fetch from new daily_logs collection
  final querySnapshot = await fireStore.collection('daily_logs')
      .where('userId', isEqualTo: user.firebaseUid)
      .where('companyId', isEqualTo: workspaceId)
      .get();
  
  for (final doc in querySnapshot.docs) {
    try {
      final dailyLog = DailyLog.fromJson(doc.data());
      userLogs.add(dailyLog);
    } catch (e) {
      NotificationsHelper().printIfDebugMode(
        'Error parsing log: ${doc.id} for user: ${user.firebaseUid} with error: $e'
      );
    }
  }
  
  // Cache the results
  _adminCache.cacheUserLogs(user.firebaseUid, userLogs);
  _laborRepository.addUserLog(UserLogWrapper(user: user, logs: userLogs));
}

4. Update Task Management Methods

Future<void> updateTaskStatus({
  required String userId,
  required DateTime logDate,
  required int activityIndex,
  required int taskIndex,
  required TaskStatus newStatus,
}) async {
  try {
    // Generate composite ID
    final compositeId = '${userId}_${DateFormat('yyyyMMdd').format(logDate)}';
    
    // Get the document
    final doc = await fireStore.collection('daily_logs').doc(compositeId).get();
    
    if (!doc.exists) return;
    
    // Process the update
    final dailyLog = DailyLog.fromJson(doc.data()!);
    final updatedLog = // ... update logic
    
    // Save back to Firestore
    await fireStore.collection('daily_logs').doc(compositeId).update({
      'activities': updatedLog.activities.map((a) => a.toJson()).toList(),
    });
    
    // Update cache
    _adminCache.updateUserLog(userId, logDate, updatedLog);
    
    notifyListeners();
  } catch (e) {
    // Error handling
  }
}

5. Add Date Range Queries for Reporting

Future<List<DailyLog>> getLogsByDateRange(DateTime startDate, DateTime endDate, {String? companyId}) async {
  final cacheKey = '${startDate.toIso8601String()}_${endDate.toIso8601String()}_$companyId';
  
  // Check cache first
  final cachedLogs = _adminCache.getDateRangeLogs(cacheKey);
  if (cachedLogs != null) return cachedLogs;
  
  // Build query
  Query query = fireStore.collection('daily_logs')
      .where('logDate', isGreaterThanOrEqualTo: Timestamp.fromDate(startDate))
      .where('logDate', isLessThanOrEqualTo: Timestamp.fromDate(endDate));
  
  if (companyId != null) {
    query = query.where('companyId', isEqualTo: companyId);
  }
  
  // Execute query
  final querySnapshot = await query.get();
  final logs = querySnapshot.docs.map((doc) => DailyLog.fromJson(doc.data())).toList();
  
  // Cache results
  _adminCache.cacheDateRangeLogs(cacheKey, logs);
  
  return logs;
}

6. Update Weekly Submission Methods

Future<void> submitWeekWork(String userId, List<DailyLog> data, RequestStatus status) async {
  final batch = fireStore.batch();
  
  for (final log in data) {
    if (log.companyId.isEmpty) continue;
    
    final compositeId = '${userId}_${DateFormat('yyyyMMdd').format(log.logDate)}';
    final docRef = fireStore.collection('daily_logs').doc(compositeId);
    
    batch.update(docRef, {'status': status.name});
  }
  
  await batch.commit();
  
  // Update cache
  for (final log in data) {
    final updatedLog = log.copyWith(status: status);
    _adminCache.updateUserLog(userId, log.logDate, updatedLog);
  }
  
  _updateCurrentLogs(userId, data, status);
  notifyListeners();
}

Success Criteria

1. Performance Metrics

  • Admin panel loads 50% faster when accessing user logs
  • Date range queries complete in under 3 seconds for 30-day periods
  • Task status updates reflect in UI within 1 second

2. Functional Requirements

  • All admin functions work with new DailyLog structure
  • Reporting features maintain or improve current functionality
  • Real-time updates work correctly for admin actions
  • Cache properly invalidates on data changes

3. Data Consistency

  • All existing admin reports generate correctly
  • User log access controls maintained

4. Error Handling

  • Proper error handling for failed queries
  • Graceful fallback when cache is unavailable
  • Clear error messages for admin users

Implementation Checklist

Phase 1: Core Architecture Changes

  • Update DailyLog model to include userId field
  • Create composite ID generation utility
  • Update all Firestore references to use new collection

Phase 2: Caching Implementation

  • Create AdminLogCache class
  • Implement user-specific caching
  • Implement date-range caching for reports
  • Add cache invalidation methods

Phase 3: Provider Refactoring

  • Update _fetchLogs method to use new collection
  • Update task management methods
  • Update weekly submission methods
  • Add date range query methods

Phase 4: UI Updates

  • Ensure all UI components work with new data structure
  • Add loading states for cache misses
  • Update error handling displays

Phase 5: Testing

  • Verify all admin functions work correctly
  • Test cache hit/miss behavior
  • Performance testing with large datasets
  • Security testing for data access controls

Migration Strategy

  1. Phased Rollout: Update admin panel first
  2. Monitoring: Add detailed logging for cache performance and errors

This refactoring plan will ensure the admin panel works efficiently with the new DailyLog architecture while maintaining all existing functionality and providing better performance through strategic caching.

On this page