Dedicated Assigned to me Tasks Tab

This commit is contained in:
Marc Rejohn Castillano 2026-02-24 23:25:27 +08:00
parent 546c254326
commit 7c3a5a1cef

View File

@ -151,9 +151,7 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
dateRange: _selectedDateRange, dateRange: _selectedDateRange,
latestAssigneeByTaskId: latestAssigneeByTaskId, latestAssigneeByTaskId: latestAssigneeByTaskId,
); );
final summaryDashboard = _StatusSummaryRow(
counts: _taskStatusCounts(filteredTasks),
);
final filterHeader = Wrap( final filterHeader = Wrap(
spacing: 12, spacing: 12,
runSpacing: 12, runSpacing: 12,
@ -241,127 +239,67 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
), ),
], ],
); );
final listBody = TasQAdaptiveList<Task>(
items: filteredTasks,
onRowTap: (task) => context.go('/tasks/${task.id}'),
summaryDashboard: summaryDashboard,
filterHeader: filterHeader,
onRequestRefresh: () {
// For server-side pagination, update the query provider
ref.read(tasksQueryProvider.notifier).state = const TaskQuery(
offset: 0,
limit: 50,
);
},
isLoading: false,
columns: [
TasQColumn<Task>(
header: 'Task #',
technical: true,
cellBuilder: (context, task) =>
Text(task.taskNumber ?? task.id),
),
TasQColumn<Task>(
header: 'Subject',
cellBuilder: (context, task) {
final ticket = task.ticketId == null
? null
: ticketById[task.ticketId];
return Text(
task.title.isNotEmpty
? task.title
: (ticket?.subject ?? 'Task ${task.id}'),
);
},
),
TasQColumn<Task>(
header: 'Office',
cellBuilder: (context, task) {
final ticket = task.ticketId == null
? null
: ticketById[task.ticketId];
final officeId = ticket?.officeId ?? task.officeId;
return Text(
officeId == null
? 'Unassigned office'
: (officeById[officeId]?.name ?? officeId),
);
},
),
TasQColumn<Task>(
header: 'Assigned Agent',
cellBuilder: (context, task) {
final assigneeId = latestAssigneeByTaskId[task.id];
return Text(_assignedAgent(profileById, assigneeId));
},
),
TasQColumn<Task>(
header: 'Status',
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',
technical: true,
cellBuilder: (context, task) =>
Text(_formatTimestamp(task.createdAt)),
),
],
mobileTileBuilder: (context, task, actions) {
final ticketId = task.ticketId;
final ticket = ticketId == null ? null : ticketById[ticketId];
final officeId = ticket?.officeId ?? task.officeId;
final officeName = officeId == null
? 'Unassigned office'
: (officeById[officeId]?.name ?? officeId);
final assigned = _assignedAgent(
profileById,
latestAssigneeByTaskId[task.id],
);
final subtitle = _buildSubtitle(officeName, task.status);
final hasMention = _hasTaskMention(notificationsAsync, task);
final typingState = ref.watch(
typingIndicatorProvider(task.id),
);
final showTyping = typingState.userIds.isNotEmpty;
return Card( // reusable helper for rendering a list given a subset of tasks
child: ListTile( Widget makeList(List<Task> tasksList) {
leading: _buildQueueBadge(context, task), final summary = _StatusSummaryRow(
dense: true, counts: _taskStatusCounts(tasksList),
visualDensity: VisualDensity.compact, );
title: Text( return TasQAdaptiveList<Task>(
task.title.isNotEmpty items: tasksList,
? task.title onRowTap: (task) => context.go('/tasks/${task.id}'),
: (ticket?.subject ?? summaryDashboard: summary,
'Task ${task.taskNumber ?? task.id}'), filterHeader: filterHeader,
), onRequestRefresh: () {
subtitle: Column( // For server-side pagination, update the query provider
crossAxisAlignment: CrossAxisAlignment.start, ref.read(tasksQueryProvider.notifier).state =
children: [ const TaskQuery(offset: 0, limit: 50);
Text(subtitle), },
const SizedBox(height: 2), isLoading: false,
Text('Assigned: $assigned'), columns: [
const SizedBox(height: 4), TasQColumn<Task>(
MonoText('ID ${task.taskNumber ?? task.id}'), header: 'Task #',
const SizedBox(height: 2), technical: true,
Text(_formatTimestamp(task.createdAt)), cellBuilder: (context, task) =>
], Text(task.taskNumber ?? task.id),
), ),
trailing: Row( TasQColumn<Task>(
header: 'Subject',
cellBuilder: (context, task) {
final ticket = task.ticketId == null
? null
: ticketById[task.ticketId];
return Text(
task.title.isNotEmpty
? task.title
: (ticket?.subject ?? 'Task ${task.id}'),
);
},
),
TasQColumn<Task>(
header: 'Office',
cellBuilder: (context, task) {
final ticket = task.ticketId == null
? null
: ticketById[task.ticketId];
final officeId = ticket?.officeId ?? task.officeId;
return Text(
officeId == null
? 'Unassigned office'
: (officeById[officeId]?.name ?? officeId),
);
},
),
TasQColumn<Task>(
header: 'Assigned Agent',
cellBuilder: (context, task) {
final assigneeId = latestAssigneeByTaskId[task.id];
return Text(_assignedAgent(profileById, assigneeId));
},
),
TasQColumn<Task>(
header: 'Status',
cellBuilder: (context, task) => Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
_StatusBadge(status: task.status), _StatusBadge(status: task.status),
@ -374,29 +312,108 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
color: Colors.orange, color: Colors.orange,
), ),
], ],
if (showTyping) ...[
const SizedBox(width: 6),
TypingDots(
size: 6,
color: Theme.of(context).colorScheme.primary,
),
],
if (hasMention)
const Padding(
padding: EdgeInsets.only(left: 8),
child: Icon(
Icons.circle,
size: 10,
color: Colors.red,
),
),
], ],
), ),
onTap: () => context.go('/tasks/${task.id}'),
), ),
); TasQColumn<Task>(
}, header: 'Timestamp',
); technical: true,
cellBuilder: (context, task) =>
Text(_formatTimestamp(task.createdAt)),
),
],
mobileTileBuilder: (context, task, actions) {
final ticketId = task.ticketId;
final ticket = ticketId == null
? null
: ticketById[ticketId];
final officeId = ticket?.officeId ?? task.officeId;
final officeName = officeId == null
? 'Unassigned office'
: (officeById[officeId]?.name ?? officeId);
final assigned = _assignedAgent(
profileById,
latestAssigneeByTaskId[task.id],
);
final subtitle = _buildSubtitle(officeName, task.status);
final hasMention = _hasTaskMention(
notificationsAsync,
task,
);
final typingState = ref.watch(
typingIndicatorProvider(task.id),
);
final showTyping = typingState.userIds.isNotEmpty;
return Card(
child: ListTile(
leading: _buildQueueBadge(context, task),
dense: true,
visualDensity: VisualDensity.compact,
title: Text(
task.title.isNotEmpty
? task.title
: (ticket?.subject ??
'Task ${task.taskNumber ?? task.id}'),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(subtitle),
const SizedBox(height: 2),
Text('Assigned: $assigned'),
const SizedBox(height: 4),
MonoText('ID ${task.taskNumber ?? task.id}'),
const SizedBox(height: 2),
Text(_formatTimestamp(task.createdAt)),
],
),
trailing: 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,
),
],
if (showTyping) ...[
const SizedBox(width: 6),
TypingDots(
size: 6,
color: Theme.of(context).colorScheme.primary,
),
],
if (hasMention)
const Padding(
padding: EdgeInsets.only(left: 8),
child: Icon(
Icons.circle,
size: 10,
color: Colors.red,
),
),
],
),
onTap: () => context.go('/tasks/${task.id}'),
),
);
},
);
}
final currentUserId = profileAsync.valueOrNull?.id;
final myTasks = currentUserId == null
? <Task>[]
: filteredTasks
.where(
(t) => latestAssigneeByTaskId[t.id] == currentUserId,
)
.toList();
return Column( return Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
@ -415,7 +432,29 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
), ),
), ),
), ),
Expanded(child: listBody), Expanded(
child: DefaultTabController(
length: 2,
child: Column(
children: [
const TabBar(
tabs: [
Tab(text: 'My Tasks'),
Tab(text: 'All Tasks'),
],
),
Expanded(
child: TabBarView(
children: [
makeList(myTasks),
makeList(filteredTasks),
],
),
),
],
),
),
),
], ],
); );
}, },