feat(programmer): dated work-log corrections
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../models/programmer_task_work_log.model.dart';
|
||||
import '../utils/programmer_daily_time.dart';
|
||||
import 'programmer_day_sheets_provider.dart';
|
||||
import 'supabase_provider.dart';
|
||||
|
||||
/// Work-log entries for a task (newest first), streamed live.
|
||||
@@ -26,19 +28,27 @@ class ProgrammerTaskWorkLogsController {
|
||||
|
||||
/// Adds a work-log entry. [minutes] is null for the assignee's progress notes
|
||||
/// (their time comes from the running clock) and set for a helper's
|
||||
/// Time Consumed.
|
||||
/// Time Consumed. [workDate] overrides the date the entry is attributed to;
|
||||
/// when null the DB default applies (today's Manila date).
|
||||
Future<void> addWorkLog({
|
||||
required String taskId,
|
||||
required String description,
|
||||
int? minutes,
|
||||
DateTime? workDate,
|
||||
}) async {
|
||||
final userId = _client.auth.currentUser?.id;
|
||||
if (userId == null) throw Exception('Not authenticated');
|
||||
await _client.from('programmer_task_work_logs').insert({
|
||||
'task_id': taskId,
|
||||
'author_id': userId,
|
||||
'description': description,
|
||||
'minutes': ?minutes,
|
||||
});
|
||||
try {
|
||||
await _client.from('programmer_task_work_logs').insert({
|
||||
'task_id': taskId,
|
||||
'author_id': userId,
|
||||
'description': description,
|
||||
'minutes': ?minutes,
|
||||
if (workDate != null) 'work_date': formatDayKey(workDate),
|
||||
});
|
||||
} catch (e) {
|
||||
if (e is DaySheetActionException) rethrow;
|
||||
throw DaySheetActionException(mapDaySheetError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,15 @@ import '../programmer_task_prompts.dart';
|
||||
/// a non-assignee helper gets Hours + Minutes fields and is prompted to deduct
|
||||
/// that time from their own running task. The assignee is prompted to pause
|
||||
/// after logging.
|
||||
///
|
||||
/// When [workDate] is supplied the entry is a correction for a past day:
|
||||
/// the header names that date and the pause/deduct prompts are skipped.
|
||||
class WorkLogSection extends ConsumerStatefulWidget {
|
||||
const WorkLogSection({super.key, required this.task, required this.isAssignee});
|
||||
const WorkLogSection({super.key, required this.task, required this.isAssignee, this.workDate});
|
||||
|
||||
final ProgrammerTask task;
|
||||
final bool isAssignee;
|
||||
final DateTime? workDate;
|
||||
|
||||
@override
|
||||
ConsumerState<WorkLogSection> createState() => _WorkLogSectionState();
|
||||
@@ -68,15 +72,18 @@ class _WorkLogSectionState extends ConsumerState<WorkLogSection> {
|
||||
taskId: widget.task.id,
|
||||
description: quillToDeltaJson(_descQuill),
|
||||
minutes: minutes,
|
||||
workDate: widget.workDate,
|
||||
);
|
||||
_descQuill.clear();
|
||||
_hoursCtrl.clear();
|
||||
_minutesCtrl.clear();
|
||||
if (!mounted) return;
|
||||
if (widget.isAssignee) {
|
||||
await maybePromptPauseTimer(context, ref, widget.task);
|
||||
} else if (minutes != null) {
|
||||
await maybePromptDeductHelperTime(context, ref, minutes, widget.task.id);
|
||||
if (widget.workDate == null) {
|
||||
if (widget.isAssignee) {
|
||||
await maybePromptPauseTimer(context, ref, widget.task);
|
||||
} else if (minutes != null) {
|
||||
await maybePromptDeductHelperTime(context, ref, minutes, widget.task.id);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
messenger.showSnackBar(SnackBar(content: Text('Could not save: $e')));
|
||||
@@ -97,7 +104,11 @@ class _WorkLogSectionState extends ConsumerState<WorkLogSection> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const AppSectionHeader('Work Log'),
|
||||
AppSectionHeader(
|
||||
widget.workDate != null
|
||||
? 'Correction for ${AppTime.formatDate(widget.workDate!)}'
|
||||
: 'Work Log',
|
||||
),
|
||||
QuillFieldEditor(
|
||||
controller: _descQuill,
|
||||
placeholder: widget.isAssignee
|
||||
|
||||
Reference in New Issue
Block a user