Add PM extension (Projects, Work Logs, Comments) + Quill rich-text UI
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>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../models/programmer_task.model.dart';
|
||||
import '../../providers/programmer_tasks_provider.dart';
|
||||
import '../../widgets/quill_field.dart';
|
||||
|
||||
/// Edit a task's Title (plain) and Description (Quill). Saves via the existing
|
||||
/// [ProgrammerTasksController.updateTask].
|
||||
Future<void> showEditProgrammerTaskDialog(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ProgrammerTask task,
|
||||
) async {
|
||||
final titleCtrl = TextEditingController(text: task.title);
|
||||
final descQuill = quillControllerFromDelta(task.description);
|
||||
|
||||
final ok = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final dialogWidth =
|
||||
math.min(480.0, MediaQuery.of(context).size.width - 48);
|
||||
return AlertDialog(
|
||||
semanticLabel: 'Edit task',
|
||||
title: const Text('Edit Task'),
|
||||
content: SizedBox(
|
||||
width: dialogWidth,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
controller: titleCtrl,
|
||||
autofocus: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
decoration: const InputDecoration(labelText: 'Title'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('Description',
|
||||
style: Theme.of(context).textTheme.bodySmall),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
QuillFieldEditor(
|
||||
controller: descQuill,
|
||||
placeholder: 'Describe the task…',
|
||||
height: 180,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: const Text('Save'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final title = titleCtrl.text.trim();
|
||||
titleCtrl.dispose();
|
||||
final description =
|
||||
quillPlainText(descQuill).isEmpty ? '' : quillToDeltaJson(descQuill);
|
||||
descQuill.dispose();
|
||||
if (ok != true || title.isEmpty || !context.mounted) return;
|
||||
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
try {
|
||||
await ref.read(programmerTasksControllerProvider).updateTask(
|
||||
taskId: task.id,
|
||||
title: title,
|
||||
description: description,
|
||||
);
|
||||
messenger.showSnackBar(const SnackBar(content: Text('Task updated')));
|
||||
} catch (e) {
|
||||
messenger.showSnackBar(SnackBar(content: Text('Could not update: $e')));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user