Dedicated Assigned to me Tasks Tab
This commit is contained in:
parent
546c254326
commit
7c3a5a1cef
|
|
@ -151,9 +151,7 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
|
|||
dateRange: _selectedDateRange,
|
||||
latestAssigneeByTaskId: latestAssigneeByTaskId,
|
||||
);
|
||||
final summaryDashboard = _StatusSummaryRow(
|
||||
counts: _taskStatusCounts(filteredTasks),
|
||||
);
|
||||
|
||||
final filterHeader = Wrap(
|
||||
spacing: 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(
|
||||
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(
|
||||
// reusable helper for rendering a list given a subset of tasks
|
||||
Widget makeList(List<Task> tasksList) {
|
||||
final summary = _StatusSummaryRow(
|
||||
counts: _taskStatusCounts(tasksList),
|
||||
);
|
||||
return TasQAdaptiveList<Task>(
|
||||
items: tasksList,
|
||||
onRowTap: (task) => context.go('/tasks/${task.id}'),
|
||||
summaryDashboard: summary,
|
||||
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),
|
||||
|
|
@ -374,29 +312,108 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
|
|||
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(
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user