Allow task completion even if signatories are still empty, put an indicator for completed tasks with incomplete details.
This commit is contained in:
@@ -53,6 +53,19 @@ class Task {
|
||||
// JSON serialized rich text for action taken (Quill Delta JSON encoded)
|
||||
final String? actionTaken;
|
||||
|
||||
/// Helper that indicates whether a completed task still has missing
|
||||
/// metadata such as signatories or action details. The parameter is used
|
||||
/// by UI to surface a warning icon/banner when a task has been closed but
|
||||
/// the user skipped filling out all fields.
|
||||
bool get hasIncompleteDetails {
|
||||
if (status != 'completed') return false;
|
||||
bool empty(String? v) => v == null || v.trim().isEmpty;
|
||||
return empty(requestedBy) ||
|
||||
empty(notedBy) ||
|
||||
empty(receivedBy) ||
|
||||
empty(actionTaken);
|
||||
}
|
||||
|
||||
factory Task.fromMap(Map<String, dynamic> map) {
|
||||
return Task(
|
||||
id: map['id'] as String,
|
||||
|
||||
@@ -562,7 +562,9 @@ class TasksController {
|
||||
.from('tasks')
|
||||
// include all columns that must be non-null/empty before completing
|
||||
.select(
|
||||
'request_type, request_category, requested_by, noted_by, received_by, action_taken',
|
||||
// signatories are not needed for validation; action_taken is still
|
||||
// required so we include it alongside the type/category fields.
|
||||
'request_type, request_category, action_taken',
|
||||
)
|
||||
.eq('id', taskId)
|
||||
.maybeSingle();
|
||||
@@ -572,9 +574,6 @@ class TasksController {
|
||||
|
||||
final rt = row['request_type'];
|
||||
final rc = row['request_category'];
|
||||
final requested = row['requested_by'];
|
||||
final noted = row['noted_by'];
|
||||
final received = row['received_by'];
|
||||
final action = row['action_taken'];
|
||||
|
||||
final missing = <String>[];
|
||||
@@ -584,17 +583,13 @@ class TasksController {
|
||||
if (rc == null || (rc is String && rc.trim().isEmpty)) {
|
||||
missing.add('request category');
|
||||
}
|
||||
if (requested == null ||
|
||||
(requested is String && requested.trim().isEmpty)) {
|
||||
missing.add('requested by');
|
||||
}
|
||||
if (noted == null || (noted is String && noted.trim().isEmpty)) {
|
||||
missing.add('noted by');
|
||||
}
|
||||
if (received == null ||
|
||||
(received is String && received.trim().isEmpty)) {
|
||||
missing.add('received by');
|
||||
}
|
||||
// signatories are no longer required for completion; they can be
|
||||
// filled in later. we still require action taken to document what
|
||||
// was done.
|
||||
|
||||
// if you want to enforce action taken you can uncomment this block,
|
||||
// but current business rule only mandates request metadata. we keep
|
||||
// action taken non-null for clarity.
|
||||
if (action == null || (action is String && action.trim().isEmpty)) {
|
||||
missing.add('action taken');
|
||||
}
|
||||
|
||||
@@ -426,6 +426,28 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
|
||||
const SizedBox(height: 12),
|
||||
Text(description),
|
||||
],
|
||||
|
||||
// warning banner for completed tasks with missing metadata
|
||||
if (task.status == 'completed' && task.hasIncompleteDetails) ...[
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
color: Colors.orange,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Task completed but some details are still empty.',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
// Collapsible tabbed details: Assignees / Type & Category / Signatories
|
||||
ExpansionTile(
|
||||
|
||||
@@ -297,8 +297,21 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
|
||||
),
|
||||
TasQColumn<Task>(
|
||||
header: 'Status',
|
||||
cellBuilder: (context, task) =>
|
||||
cellBuilder: (context, task) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_StatusBadge(status: task.status),
|
||||
if (task.status == 'completed' &&
|
||||
task.hasIncompleteDetails) ...[
|
||||
const SizedBox(width: 4),
|
||||
const Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
size: 16,
|
||||
color: Colors.orange,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
TasQColumn<Task>(
|
||||
header: 'Timestamp',
|
||||
@@ -352,6 +365,15 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_StatusBadge(status: task.status),
|
||||
if (task.status == 'completed' &&
|
||||
task.hasIncompleteDetails) ...[
|
||||
const SizedBox(width: 4),
|
||||
const Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
size: 16,
|
||||
color: Colors.orange,
|
||||
),
|
||||
],
|
||||
if (showTyping) ...[
|
||||
const SizedBox(width: 6),
|
||||
TypingDots(
|
||||
|
||||
Reference in New Issue
Block a user