Skip to content

Commit f5094f2

Browse files
committed
Refine task setup and task list UX for large task sets
add tasking API env flags and real-data config defaults update AOI upload warning threshold to 5000 square kilometers tighten project/task typings for API responses switch save-tasks CTA to a clearer non-download icon show 6 tasks per page and fix lock menu clipping in tasks list collapse long pagination ranges with ellipses for large task counts
1 parent 9edab60 commit f5094f2

4 files changed

Lines changed: 106 additions & 18 deletions

File tree

components/workspace-project-details/TaskSetupPanel.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
>
106106
<app-spinner v-if="saving" size="sm" />
107107
<template v-else>
108-
<app-icon variant="save_alt" size="18" no-margin />
108+
<app-icon variant="task_alt" size="18" no-margin />
109109
Save Tasks
110110
</template>
111111
</button>

components/workspace-project-details/TasksTab.vue

Lines changed: 101 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,25 @@
110110
<app-icon variant="chevron_left" size="22" no-margin />
111111
</button>
112112

113-
<button
114-
v-for="page in totalPages"
115-
:key="page"
116-
type="button"
117-
:class="{ 'project-detail-task-pagination-active': page === currentPage }"
118-
:aria-current="page === currentPage ? 'page' : undefined"
119-
@click="currentPage = page"
120-
>
121-
{{ page }}
122-
</button>
113+
<template v-for="item in visiblePaginationItems" :key="item.key">
114+
<span
115+
v-if="item.type === 'ellipsis'"
116+
class="project-detail-task-pagination-ellipsis"
117+
aria-hidden="true"
118+
>
119+
...
120+
</span>
121+
122+
<button
123+
v-else
124+
type="button"
125+
:class="{ 'project-detail-task-pagination-active': item.page === currentPage }"
126+
:aria-current="item.page === currentPage ? 'page' : undefined"
127+
@click="currentPage = item.page"
128+
>
129+
{{ item.page }}
130+
</button>
131+
</template>
123132

124133
<button
125134
type="button"
@@ -151,6 +160,9 @@ import type { WorkspaceProjectTaskListItem, WorkspaceProjectTaskStatus } from '~
151160
152161
type TaskSortOption = 'latest' | 'oldest' | 'task_asc' | 'task_desc';
153162
type TaskStatusFilter = WorkspaceProjectTaskStatus | 'all';
163+
type PaginationItem =
164+
| { type: 'page'; key: string; page: number }
165+
| { type: 'ellipsis'; key: string };
154166
155167
interface SelectOption {
156168
label: string;
@@ -174,8 +186,10 @@ const emit = defineEmits<{
174186
'unlock-task': [taskNumber: number];
175187
}>();
176188
177-
/** Number of tasks shown per page. Change this if you want larger or smaller pages. */
178-
const pageSize = 5;
189+
/** Keep the page short enough to fit the panel while matching the requested 6-row layout. */
190+
const pageSize = 6;
191+
/** Limit how many numbered pagination buttons we render before collapsing with ellipses. */
192+
const maxVisiblePaginationButtons = 7;
179193
const searchQuery = ref('');
180194
const selectedStatus = ref<TaskStatusFilter>('all');
181195
const sortBy = ref<TaskSortOption>('latest');
@@ -259,6 +273,55 @@ const paginationSummary = computed(() => {
259273
return `Showing ${start} to ${end} of ${sortedTasks.value.length} entries`;
260274
});
261275
276+
/**
277+
* Compress long pagination runs so the footer stays readable:
278+
* 1 ... 7 8 9 ... 17
279+
* instead of rendering every page button in a single line.
280+
*/
281+
const visiblePaginationItems = computed<PaginationItem[]>(() => {
282+
const pages = totalPages.value;
283+
284+
if (pages <= maxVisiblePaginationButtons) {
285+
return Array.from({ length: pages }, (_, index) => ({
286+
type: 'page' as const,
287+
key: `page-${index + 1}`,
288+
page: index + 1,
289+
}));
290+
}
291+
292+
const firstPage = 1;
293+
const lastPage = pages;
294+
const current = currentPage.value;
295+
const interiorSlots = maxVisiblePaginationButtons - 2;
296+
let windowStart = Math.max(firstPage + 1, current - Math.floor(interiorSlots / 2));
297+
let windowEnd = windowStart + interiorSlots - 1;
298+
299+
if (windowEnd >= lastPage) {
300+
windowEnd = lastPage - 1;
301+
windowStart = windowEnd - interiorSlots + 1;
302+
}
303+
304+
const items: PaginationItem[] = [
305+
{ type: 'page', key: `page-${firstPage}`, page: firstPage },
306+
];
307+
308+
if (windowStart > firstPage + 1) {
309+
items.push({ type: 'ellipsis', key: 'ellipsis-start' });
310+
}
311+
312+
for (let page = windowStart; page <= windowEnd; page += 1) {
313+
items.push({ type: 'page', key: `page-${page}`, page });
314+
}
315+
316+
if (windowEnd < lastPage - 1) {
317+
items.push({ type: 'ellipsis', key: 'ellipsis-end' });
318+
}
319+
320+
items.push({ type: 'page', key: `page-${lastPage}`, page: lastPage });
321+
322+
return items;
323+
});
324+
262325
/**
263326
* Reset to page 1 whenever the user changes the search, status filter, or sort.
264327
* Without this, the user could be on page 3 and change the filter to show only 2 tasks,
@@ -354,11 +417,12 @@ function formatTaskStatus(status: WorkspaceProjectTaskStatus) {
354417
}
355418
356419
.project-detail-task-list-wrap {
420+
position: relative;
357421
display: grid;
358422
gap: 0;
359423
border: 1px solid rgba($text-navy, 0.08);
360424
border-radius: 0.85rem;
361-
overflow: hidden;
425+
overflow: visible;
362426
}
363427
364428
.project-detail-task-list-header,
@@ -380,6 +444,7 @@ function formatTaskStatus(status: WorkspaceProjectTaskStatus) {
380444
margin: 0;
381445
padding: 0;
382446
list-style: none;
447+
overflow: visible;
383448
}
384449
385450
.project-detail-task-item {
@@ -485,22 +550,28 @@ function formatTaskStatus(status: WorkspaceProjectTaskStatus) {
485550
align-items: center;
486551
justify-content: space-between;
487552
gap: 1rem;
553+
flex-wrap: wrap;
488554
padding-top: 1rem;
489555
}
490556
491557
.project-detail-task-footer p {
492558
margin: 0;
493559
color: #5a607b;
494560
font-size: 0.95rem;
561+
flex: 0 1 auto;
495562
}
496563
497564
.project-detail-task-pagination {
498565
display: flex;
499566
align-items: center;
500567
gap: 0.25rem;
568+
flex: 1 1 auto;
569+
flex-wrap: wrap;
570+
justify-content: flex-end;
501571
}
502572
503573
.project-detail-task-pagination button {
574+
min-width: 2.15rem;
504575
width: 2.15rem;
505576
height: 2.15rem;
506577
display: inline-flex;
@@ -517,6 +588,15 @@ function formatTaskStatus(status: WorkspaceProjectTaskStatus) {
517588
background: $primary !important;
518589
}
519590
591+
.project-detail-task-pagination-ellipsis {
592+
min-width: 1.5rem;
593+
display: inline-flex;
594+
align-items: center;
595+
justify-content: center;
596+
color: #7b819c;
597+
font-size: 1rem;
598+
}
599+
520600
.project-detail-task-pagination button:disabled {
521601
opacity: 0.45;
522602
}
@@ -563,6 +643,10 @@ function formatTaskStatus(status: WorkspaceProjectTaskStatus) {
563643
flex-direction: column;
564644
align-items: flex-start;
565645
}
646+
647+
.project-detail-task-pagination {
648+
justify-content: flex-start;
649+
}
566650
}
567651
568652
@container (max-width: 420px) {
@@ -580,5 +664,9 @@ function formatTaskStatus(status: WorkspaceProjectTaskStatus) {
580664
flex-direction: column;
581665
align-items: flex-start;
582666
}
667+
668+
.project-detail-task-pagination {
669+
justify-content: flex-start;
670+
}
583671
}
584672
</style>

data/project-wizard-step-config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@
5151
"dropzoneActionLabel": "Drop files here or click to upload.",
5252
"dropzoneDescription": "Upload an AOI GeoJSON file.",
5353
"resetLabel": "Reset",
54-
"uploadWarningThresholdSquareKilometers": 1,
55-
"uploadWarningText": "Project area is higher than 1 square kilometer."
54+
"uploadWarningThresholdSquareKilometers": 5000,
55+
"uploadWarningText": "Project area is higher than 5000 squared kilometers."
5656
},
5757
"map": {
5858
"center": [-122.3321, 47.6062],

types/projects.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export type WorkspaceProjectTaskStatus =
2020
| 'needs_more_mapping'
2121
| 'completed';
2222
export type WorkspaceProjectContributorRole = 'validator' | 'mapper' | 'lead';
23-
export type WorkspaceProjectTaskApiStatus = 'to_map' | 'to_validate' | 'more_mapping_needed' | 'done' | string;
23+
export type WorkspaceProjectTaskApiStatus = 'to_map' | 'to_validate' | 'more_mapping_needed' | 'done';
2424
export type WorkspaceProjectAoiFeature = Feature<Polygon | MultiPolygon>;
2525

2626
export interface WorkspaceProjectApiItem {
@@ -99,7 +99,7 @@ export interface WorkspaceProject {
9999
createdBy: string;
100100
createdAt: Date;
101101
updatedAt: Date;
102-
createdByName: string;
102+
createdByName: string | null;
103103
}
104104

105105
export interface WorkspaceProjectDetail extends WorkspaceProject {

0 commit comments

Comments
 (0)