@@ -30,7 +30,7 @@ import {
3030 SelectValue ,
3131} from '@uipath/apollo-wind/components/ui/select'
3232import { Spinner } from '@uipath/apollo-wind/components/ui/spinner'
33- import { useTask , useTaskUsers } from '../hooks/useTasks'
33+ import { useFolders , useTask , useTaskUsers } from '../hooks/useTasks'
3434import {
3535 statusBadge ,
3636 priorityBadge ,
@@ -55,10 +55,15 @@ type Sub = 'assign' | 'reassign' | 'complete' | null
5555/** Task detail panel (Tasks.getById) with lifecycle actions via task-attached methods. */
5656export function TaskDetail ( { taskId, folderId, isManage, onClose, onChanged } : Props ) {
5757 const { task, loading, error, reload } = useTask ( taskId , folderId )
58+ const { folders } = useFolders ( )
5859 const [ sub , setSub ] = useState < Sub > ( null )
5960 const [ busy , setBusy ] = useState ( false )
6061 const [ actionError , setActionError ] = useState < string | null > ( null )
6162
63+ // Resolve folder display name; fall back to id.
64+ const folderPath =
65+ folders . find ( ( f ) => f . id === folderId ) ?. fullyQualifiedName ?? String ( folderId )
66+
6267 const afterChange = ( ) => {
6368 reload ( )
6469 onChanged ( )
@@ -73,7 +78,7 @@ export function TaskDetail({ taskId, folderId, isManage, onClose, onChanged }: P
7378 if ( result . success ) afterChange ( )
7479 else setActionError ( 'Unassign did not complete successfully' )
7580 } catch ( err ) {
76- setActionError ( err instanceof UiPathError ? err . message : 'Unassign failed' )
81+ setActionError ( extractServerMessage ( err ) ?? 'Unassign failed' )
7782 } finally {
7883 setBusy ( false )
7984 }
@@ -119,28 +124,50 @@ export function TaskDetail({ taskId, folderId, isManage, onClose, onChanged }: P
119124 ) }
120125 </ div >
121126
122- < dl className = "grid grid-cols-[7rem_1fr] gap-x-4 gap-y-1.5" >
123- < Field
124- label = "Assignee"
125- value = { task . assignedToUser ?. displayName ?? 'Unassigned' }
126- />
127+ { /* Task Summary — mirrors Action Center's right pane. */ }
128+ < dl className = "grid grid-cols-[7.5rem_1fr] gap-x-4 gap-y-1.5" >
129+ < Field label = "Task ID" value = { `#${ task . id } ` } mono />
127130 { task . taskSource ?. sourceName && (
128131 < Field label = "Source" value = { task . taskSource . sourceName } />
129132 ) }
130- < Field label = "Created" value = { formatDateTime ( task . createdTime ) } />
133+ < Field label = "Folder path" value = { folderPath } />
134+ < Field label = "Created on" value = { formatDateTime ( task . createdTime ) } />
131135 { task . completedTime && (
132136 < Field label = "Completed" value = { formatDateTime ( task . completedTime ) } />
133137 ) }
134138 { task . completedByUser ?. displayName && (
135139 < Field label = "Completed by" value = { task . completedByUser . displayName } />
136140 ) }
141+ { task . taskAssignmentCriteria && (
142+ < Field label = "Assignment criteria" value = { task . taskAssignmentCriteria } />
143+ ) }
144+ < Field
145+ label = "Assigned to"
146+ value = {
147+ task . assignedToUser ?. displayName ??
148+ ( assignedToGroupKey ( task ) ? 'Directory group' : 'Unassigned' )
149+ }
150+ />
137151 { ( task . actionLabel || task . action ) && (
138152 < Field label = "Action" value = { task . actionLabel ?? task . action ?? '' } />
139153 ) }
140154 { task . externalTag && < Field label = "External tag" value = { task . externalTag } mono /> }
141155 < Field label = "Key" value = { task . key } mono />
142156 </ dl >
143157
158+ { task . tags && task . tags . length > 0 && (
159+ < Section title = "Labels" >
160+ < div className = "flex flex-wrap gap-1.5" >
161+ { task . tags . map ( ( t ) => (
162+ < Badge key = { t . name } variant = "secondary" >
163+ { t . displayName || t . name }
164+ { t . displayValue ? `: ${ t . displayValue } ` : '' }
165+ </ Badge >
166+ ) ) }
167+ </ div >
168+ </ Section >
169+ ) }
170+
144171 { task . data && Object . keys ( task . data ) . length > 0 && (
145172 < Section title = "Data" >
146173 < dl className = "grid grid-cols-[10rem_1fr] gap-x-4 gap-y-1 rounded-lg border bg-muted/40 p-3" >
@@ -156,24 +183,6 @@ export function TaskDetail({ taskId, folderId, isManage, onClose, onChanged }: P
156183 </ Section >
157184 ) }
158185
159- { task . activities && task . activities . length > 0 && (
160- < Section title = "Activity" >
161- < ul className = "space-y-1.5" >
162- { task . activities . map ( ( a , i ) => (
163- < li
164- key = { `${ a . taskId } -${ i } ` }
165- className = "flex items-center justify-between gap-2"
166- >
167- < span > { a . activityType } </ span >
168- < span className = "shrink-0 text-xs text-muted-foreground" >
169- { formatDateTime ( a . createdTime ) }
170- </ span >
171- </ li >
172- ) ) }
173- </ ul >
174- </ Section >
175- ) }
176-
177186 { actionError && (
178187 < Alert variant = "destructive" >
179188 < AlertDescription > { actionError } </ AlertDescription >
@@ -243,6 +252,42 @@ function Section({ title, children }: { title: string; children: React.ReactNode
243252 )
244253}
245254
255+ /** Assigned to a directory group (no user object, but a key). */
256+ function assignedToGroupKey ( task : TaskGetResponse ) : boolean {
257+ if ( task . assignedToUser ) return false
258+ const key = ( task as unknown as { assignedToUserKey ?: string | null } ) . assignedToUserKey
259+ return ! ! key
260+ }
261+
262+ /** First server message from a failed assign payload. */
263+ function extractAssignError ( data : unknown ) : string {
264+ if ( Array . isArray ( data ) ) {
265+ for ( const item of data ) {
266+ if ( item && typeof item === 'object' && 'errorMessage' in item ) {
267+ const msg = String ( ( item as { errorMessage ?: string } ) . errorMessage ?? '' ) . trim ( )
268+ if ( msg ) return msg
269+ }
270+ }
271+ }
272+ return 'The assignment did not complete successfully'
273+ }
274+
275+ /** Server error string from a thrown UiPath/HTTP error. */
276+ function extractServerMessage ( err : unknown ) : string | null {
277+ if ( ! err ) return null
278+ if ( err instanceof UiPathError && err . message ) return err . message
279+ const e = err as Record < string , unknown >
280+ const candidates = [
281+ e . message ,
282+ ( e . body as Record < string , unknown > | undefined ) ?. message ,
283+ ( ( e . response as Record < string , unknown > | undefined ) ?. data as Record < string , unknown > | undefined ) ?. message ,
284+ ]
285+ for ( const c of candidates ) {
286+ if ( typeof c === 'string' && c . trim ( ) ) return c . trim ( )
287+ }
288+ return null
289+ }
290+
246291function renderValue ( value : unknown ) : string {
247292 if ( value === null || value === undefined ) return '—'
248293 if ( typeof value === 'object' ) {
@@ -292,10 +337,11 @@ function AssignForm({
292337 onDone ( )
293338 onClose ( )
294339 } else {
295- setSubmitError ( 'The assignment did not complete successfully' )
340+ // Server-supplied error (e.g. "Already assigned to another user").
341+ setSubmitError ( extractAssignError ( result . data ) )
296342 }
297343 } catch ( err ) {
298- setSubmitError ( err instanceof UiPathError ? err . message : 'Assignment failed' )
344+ setSubmitError ( extractServerMessage ( err ) ?? 'Assignment failed' )
299345 } finally {
300346 setBusy ( false )
301347 }
@@ -387,7 +433,7 @@ function CompleteForm({
387433 setSubmitError ( 'The task did not complete successfully' )
388434 }
389435 } catch ( err ) {
390- setSubmitError ( err instanceof UiPathError ? err . message : 'Completion failed' )
436+ setSubmitError ( extractServerMessage ( err ) ?? 'Completion failed' )
391437 } finally {
392438 setBusy ( false )
393439 }
@@ -399,8 +445,7 @@ function CompleteForm({
399445 < DialogHeader >
400446 < DialogTitle > Complete task</ DialogTitle >
401447 < DialogDescription >
402- The action maps to the outcome the workflow expects. Use Reject, or type a custom
403- action and choose Complete.
448+ Type the action your workflow expects (e.g. < span className = "font-medium" > Approve</ span > or < span className = "font-medium" > Reject</ span > ) and confirm.
404449 </ DialogDescription >
405450 </ DialogHeader >
406451
@@ -426,11 +471,8 @@ function CompleteForm({
426471 < Button variant = "outline" onClick = { onClose } disabled = { busy } >
427472 Cancel
428473 </ Button >
429- < Button variant = "destructive" onClick = { ( ) => complete ( 'Reject' ) } disabled = { busy } >
430- Reject
431- </ Button >
432474 < Button onClick = { ( ) => complete ( action ) } disabled = { busy || ! action . trim ( ) } >
433- { busy ? 'Completing…' : ` Complete ( ${ action . trim ( ) || '—' } )` }
475+ { busy ? 'Completing…' : ' Complete' }
434476 </ Button >
435477 </ DialogFooter >
436478 </ DialogContent >
0 commit comments