b307f66f2c
Projects: Brick-backed model with list/detail screens and nav entry. Work Logs: per-contributor time ledger, assignee running-clock + helper logged minutes with Hours/Minutes input; pause-after-save and helper-deduct prompts. Comments: threaded comments with file attachments and Quill composer. Detail screen: tabbed layout (Work Log / Comments / Activity), editable title + description via edit dialog, Quill-rendered descriptions with legacy plain-text fallback. Create dialog: fixed-width (480px max), Quill description editor. Shared QuillFieldEditor + QuillReadOnly widgets extracted for reuse. RLS: hardened activity-log actor binding, immutable creator_id/ created_at triggers, SECURITY INVOKER visibility helper, append-only work-log and scoped comment policies. Brick migration for project_id FK on programmer_tasks. Co-Authored-By: claude-flow <ruv@ruv.net>
79 lines
2.3 KiB
Dart
79 lines
2.3 KiB
Dart
import '../models/programmer_task_work_log.model.dart';
|
|
|
|
/// Formats a worked duration compactly, e.g. `2h 15m` or `45m`.
|
|
String formatDurationHm(Duration d) {
|
|
final h = d.inHours;
|
|
final m = d.inMinutes.remainder(60);
|
|
if (h > 0) return '${h}h ${m}m';
|
|
return '${m}m';
|
|
}
|
|
|
|
/// One contributor's share of a task's time.
|
|
class LedgerEntry {
|
|
final String userId;
|
|
final Duration duration;
|
|
final bool isAssignee;
|
|
|
|
const LedgerEntry({
|
|
required this.userId,
|
|
required this.duration,
|
|
required this.isAssignee,
|
|
});
|
|
}
|
|
|
|
/// Per-contributor time breakdown for a task: the assignee's effective running
|
|
/// duration plus each helper's summed logged minutes.
|
|
class TaskTimeLedger {
|
|
final LedgerEntry? assignee;
|
|
final List<LedgerEntry> helpers;
|
|
|
|
const TaskTimeLedger({required this.assignee, required this.helpers});
|
|
|
|
Duration get total =>
|
|
(assignee?.duration ?? Duration.zero) +
|
|
helpers.fold(Duration.zero, (a, e) => a + e.duration);
|
|
|
|
List<LedgerEntry> get all => [?assignee, ...helpers];
|
|
}
|
|
|
|
/// Builds the per-contributor ledger for a task.
|
|
///
|
|
/// [assigneeDuration] is the assignee's effective running duration (already
|
|
/// computed from the activity log, including any adjustments). Helper entries
|
|
/// sum the `minutes` of work logs authored by someone other than the assignee;
|
|
/// assignee work logs (minutes == null progress notes) are excluded because the
|
|
/// assignee's time comes from the running clock.
|
|
TaskTimeLedger computeTaskLedger({
|
|
required String? assigneeId,
|
|
required Duration assigneeDuration,
|
|
required List<ProgrammerTaskWorkLog> workLogs,
|
|
}) {
|
|
final byHelper = <String, int>{};
|
|
for (final log in workLogs) {
|
|
final minutes = log.minutes;
|
|
if (minutes == null || minutes <= 0) continue;
|
|
if (log.authorId == assigneeId) continue;
|
|
byHelper[log.authorId] = (byHelper[log.authorId] ?? 0) + minutes;
|
|
}
|
|
|
|
final helpers = byHelper.entries
|
|
.map((e) => LedgerEntry(
|
|
userId: e.key,
|
|
duration: Duration(minutes: e.value),
|
|
isAssignee: false,
|
|
))
|
|
.toList()
|
|
..sort((a, b) => b.duration.compareTo(a.duration));
|
|
|
|
return TaskTimeLedger(
|
|
assignee: assigneeId == null
|
|
? null
|
|
: LedgerEntry(
|
|
userId: assigneeId,
|
|
duration: assigneeDuration,
|
|
isAssignee: true,
|
|
),
|
|
helpers: helpers,
|
|
);
|
|
}
|