From bdfa9e29d5ea29be3e2401c104af08e9a54f3de9 Mon Sep 17 00:00:00 2001 From: ShenzheZhu Date: Fri, 10 Jul 2026 14:06:24 -0400 Subject: [PATCH] fix: add assigned task view controls (#1006) --- .../frontend-user/src/app/documents/page.tsx | 249 +++++++++++++++++- 1 file changed, 237 insertions(+), 12 deletions(-) diff --git a/packages/frontend-user/src/app/documents/page.tsx b/packages/frontend-user/src/app/documents/page.tsx index 3333dbde..f0322809 100644 --- a/packages/frontend-user/src/app/documents/page.tsx +++ b/packages/frontend-user/src/app/documents/page.tsx @@ -77,10 +77,12 @@ import { } from '@humanly/shared'; type SortOption = 'lastEdited' | 'title' | 'characterCount'; +type TaskSortOption = 'recentlyJoined' | 'title' | 'deadline'; type WorkspaceTab = 'documents' | 'tasks'; type DocumentViewMode = 'cards' | 'list'; const DOCUMENT_VIEW_MODE_STORAGE_KEY = 'humanly:documents:view-mode'; +const TASK_VIEW_MODE_STORAGE_KEY = 'humanly:assigned-tasks:view-mode'; const SORT_LABELS: Record = { lastEdited: 'Last edited', @@ -88,6 +90,12 @@ const SORT_LABELS: Record = { characterCount: 'Character count', }; +const TASK_SORT_LABELS: Record = { + recentlyJoined: 'Recently joined', + title: 'Task name', + deadline: 'Deadline', +}; + const DOCUMENT_SORT_PARAMS: Record< SortOption, { @@ -334,6 +342,10 @@ export default function DocumentsPage() { const { toast } = useToast(); const [documentViewMode, setDocumentViewMode] = useState('list'); + const [taskViewMode, setTaskViewMode] = + useState('cards'); + const [taskSortBy, setTaskSortBy] = + useState('recentlyJoined'); const [activeWorkspaceTab, setActiveWorkspaceTab] = useState('documents'); const [taskSearchQuery, setTaskSearchQuery] = useState(''); @@ -361,6 +373,13 @@ export default function DocumentsPage() { if (isDocumentViewMode(storedViewMode)) { setDocumentViewMode(storedViewMode); } + + const storedTaskViewMode = window.localStorage.getItem( + TASK_VIEW_MODE_STORAGE_KEY + ); + if (isDocumentViewMode(storedTaskViewMode)) { + setTaskViewMode(storedTaskViewMode); + } }, []); const handleDocumentViewModeChange = useCallback( @@ -371,6 +390,14 @@ export default function DocumentsPage() { [] ); + const handleTaskViewModeChange = useCallback( + (nextViewMode: DocumentViewMode) => { + setTaskViewMode(nextViewMode); + window.localStorage.setItem(TASK_VIEW_MODE_STORAGE_KEY, nextViewMode); + }, + [] + ); + const fetchTaskEnrollments = useCallback(async () => { taskEnrollmentsRequestedRef.current = true; try { @@ -593,6 +620,21 @@ export default function DocumentsPage() { task.inviteCode ) ); + const sortedTaskEnrollments = [...filteredTaskEnrollments].sort((a, b) => { + if (taskSortBy === 'title') { + return getDisplayTaskName(a).localeCompare(getDisplayTaskName(b)); + } + + if (taskSortBy === 'deadline') { + const aDeadline = getTimestampMs(a.endDate) ?? Number.POSITIVE_INFINITY; + const bDeadline = getTimestampMs(b.endDate) ?? Number.POSITIVE_INFINITY; + return aDeadline - bDeadline; + } + + const aJoinedAt = getTimestampMs(a.joinedAt) ?? 0; + const bJoinedAt = getTimestampMs(b.joinedAt) ?? 0; + return bJoinedAt - aJoinedAt; + }); const hasStartedWritingTimer = [ ...validTaskEnrollments, ...personalDocuments, @@ -956,15 +998,73 @@ export default function DocumentsPage() { {!isLoadingTaskEnrollments && !taskEnrollmentsError && (validTaskEnrollments.length > 0 || hasTaskSearchQuery) ? ( -
- - setTaskSearchQuery(event.target.value)} - /> +
+
+ + + setTaskSearchQuery(event.target.value) + } + /> +
+
+ + + + + + + {( + Object.keys(TASK_SORT_LABELS) as TaskSortOption[] + ).map((option) => ( + setTaskSortBy(option)} + > + + {TASK_SORT_LABELS[option]} + + ))} + + +
) : null} @@ -998,7 +1098,7 @@ export default function DocumentsPage() { Retry
- ) : filteredTaskEnrollments.length === 0 ? ( + ) : sortedTaskEnrollments.length === 0 ? (

@@ -1021,9 +1121,9 @@ export default function DocumentsPage() { ) : null}

- ) : ( + ) : taskViewMode === 'cards' ? (
- {filteredTaskEnrollments.map((task) => { + {sortedTaskEnrollments.map((task) => { const taskName = getDisplayTaskName(task); const taskDescription = getDisplayTaskDescription(task); const timerState = getWritingTimerState( @@ -1185,6 +1285,131 @@ export default function DocumentsPage() { ); })}
+ ) : ( +
+
+ Task name + Status + Deadline + Actions +
+
+ {sortedTaskEnrollments.map((task) => { + const taskName = getDisplayTaskName(task); + const timerState = getWritingTimerState( + task, + dashboardNowMs, + { + expiredDetail: + 'Submission opens in read-only mode.', + } + ); + const statusBadge = getTaskStatusBadge( + task, + dashboardNowMs, + timerState + ); + const latestCertificateId = + task.latestCertificateId || null; + + return ( +
+
+ +

+ {getTaskAttemptLabel(task)} +

+
+
+ + Status + + + {statusBadge.label} + +
+
+ + Deadline + + {formatTaskDateLabel(task.endDate, 'No deadline')} +
+
+ {latestCertificateId ? ( + + ) : null} + + {canStartNewTaskAttempt(task) ? ( + + ) : null} + +
+
+ ); + })} +
+
)}