Add Programmer Tasks module (work-log + time tracking)
Separate feature for the `programmer` role to record dev work by category, distinct from the IT help-desk `tasks` feature. Modeled on the IT Service Requests pattern (Brick offline-first model -> Riverpod stream/controller -> list/detail screens); seed of a future Project Management workflow. - programmer_tasks + programmer_task_activity_logs (category enum, RLS, realtime) via supabase migration - single assignee, self-logged or lead-assigned; assign/reassign logged - pause-on-switch: starting a task while another runs prompts to pause the running one, keeping one active task per user for accurate time attribution - worked-duration math shared with help-desk tasks via a generic core in task_duration.dart (existing callers unchanged) - list distinguishes running vs paused with a per-task Resume shortcut - accessible dialog labels; route /programmer-tasks + nav gated to admin/programmer - unit/widget tests: controller lifecycle, duration math, pause-switch dialog Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart';
|
||||
import 'package:brick_supabase/brick_supabase.dart';
|
||||
|
||||
import '../utils/app_time.dart';
|
||||
|
||||
/// Event log for a programmer task. Mirrors [TaskActivityLog] but keyed to
|
||||
/// `programmer_tasks`. `actionType` values: created, started, paused, resumed,
|
||||
/// completed, cancelled, assigned, reassigned.
|
||||
@ConnectOfflineFirstWithSupabase(
|
||||
supabaseConfig: SupabaseSerializable(tableName: 'programmer_task_activity_logs'),
|
||||
)
|
||||
class ProgrammerTaskActivityLog extends OfflineFirstWithSupabaseModel {
|
||||
final String id;
|
||||
final String taskId;
|
||||
final String? actorId;
|
||||
final String actionType;
|
||||
final Map<String, dynamic>? meta;
|
||||
final DateTime createdAt;
|
||||
|
||||
ProgrammerTaskActivityLog({
|
||||
required this.id,
|
||||
required this.taskId,
|
||||
this.actorId,
|
||||
required this.actionType,
|
||||
this.meta,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory ProgrammerTaskActivityLog.fromMap(Map<String, dynamic> map) {
|
||||
final id = map['id'] == null ? '' : map['id'].toString();
|
||||
final taskId = map['task_id'] == null ? '' : map['task_id'].toString();
|
||||
final actorId = map['actor_id']?.toString();
|
||||
final actionType = (map['action_type'] as String?) ?? 'unknown';
|
||||
|
||||
Map<String, dynamic>? meta;
|
||||
final rawMeta = map['meta'];
|
||||
if (rawMeta is Map<String, dynamic>) {
|
||||
meta = rawMeta;
|
||||
} else if (rawMeta is Map) {
|
||||
meta = rawMeta.map((k, v) => MapEntry(k.toString(), v));
|
||||
} else if (rawMeta is String && rawMeta.isNotEmpty) {
|
||||
try {
|
||||
final decoded = jsonDecode(rawMeta);
|
||||
if (decoded is Map) {
|
||||
meta = decoded.map((k, v) => MapEntry(k.toString(), v));
|
||||
}
|
||||
} catch (_) {
|
||||
meta = null;
|
||||
}
|
||||
}
|
||||
|
||||
final rawCreated = map['created_at'];
|
||||
DateTime createdAt;
|
||||
if (rawCreated is DateTime) {
|
||||
createdAt = AppTime.toAppTime(rawCreated);
|
||||
} else if (rawCreated is String) {
|
||||
try {
|
||||
createdAt = AppTime.parse(rawCreated);
|
||||
} catch (_) {
|
||||
createdAt = AppTime.now();
|
||||
}
|
||||
} else {
|
||||
createdAt = AppTime.now();
|
||||
}
|
||||
|
||||
return ProgrammerTaskActivityLog(
|
||||
id: id,
|
||||
taskId: taskId,
|
||||
actorId: actorId,
|
||||
actionType: actionType,
|
||||
meta: meta,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user