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,
|
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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user