Skip to content

Commit 16c9805

Browse files
feat(samples/action-center-app): render validation station for DocumentValidation tasks
Selecting a Document Validation task now opens the @uipath/ui-widgets-validation-station widget instead of the generic complete dialog; all other task types are unchanged. - DocumentValidationDialog hosts the widget, loads the full payload via getById({ taskType: DocumentValidation }), and Save/Submit through the widget's validated-save path (Submit completes the task). - useTask gains an optional taskType param (backward compatible). - TaskList branches the My-Tasks row click on type; TaskDetail offers a "Validate document" action for DV tasks. - vite.config copies the widget's du-assets into the build; package.json adds the widget + du-validation-station-wc deps; scope adds OR.Buckets. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8db4192 commit 16c9805

9 files changed

Lines changed: 406 additions & 15 deletions

File tree

samples/action-center-app/README.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ A minimal React + TypeScript [UiPath Coded Web App](https://docs.uipath.com/)
44
demonstrating the `@uipath/uipath-typescript` SDK's **Tasks (Action Center)**
55
service. It's an operational dashboard for triaging human tasks: list and
66
filter them across your folders, view detail, assign / reassign / unassign,
7-
complete (approve / reject), and create external tasks.
7+
complete (approve / reject), and create external tasks. **Document Validation
8+
tasks** open the
9+
[`@uipath/ui-widgets-validation-station`](https://github.com/UiPath/uipath-ui-widgets/pkgs/npm/ui-widgets-validation-station)
10+
widget so a reviewer can validate the extracted document data inline.
811

912
Built on UiPath's [Apollo Vertex](https://apollo-vertex.vercel.app/) design
1013
system via [`@uipath/apollo-wind`](https://www.npmjs.com/package/@uipath/apollo-wind).
@@ -21,6 +24,7 @@ Every method on [`TaskServiceModel`](https://uipath.github.io/uipath-typescript/
2124
| `Tasks.create({ title, priority }, folderId)` | `components/TaskList.tsx` (create form) |
2225
| `task.assign` / `task.reassign` / `task.unassign` | `components/TaskDetail.tsx` |
2326
| `task.complete(options)` | `components/TaskDetail.tsx` (+ `taskUtils.ts` builds the discriminated-union options) |
27+
| `Tasks.getById(id, { taskType: TaskType.DocumentValidation }, folderId)` + `task.complete({ type: TaskType.DocumentValidation, … })` | `components/DocumentValidationDialog.tsx` — loads the validation payload and submits it |
2428

2529
By default the app lists tasks across **all folders** you can view/edit. The
2630
toolbar's **Folder** dropdown (populated from `/odata/Folders`) lets you scope
@@ -37,15 +41,18 @@ src/
3741
hooks/useAuth.tsx — OAuth/PKCE via the SDK (Coded Apps template)
3842
hooks/useTasks.ts — useTasks + useTask + useTaskUsers
3943
components/Theme.tsx, TaskList.tsx, TaskDetail.tsx
44+
components/DocumentValidationDialog.tsx — validation-station host for Document Validation tasks
4045
```
4146

4247
## Prerequisites
4348

4449
- Node.js 20+
45-
- A UiPath OAuth External Application (client ID) with **`OR.Tasks`** and
46-
**`OR.Folders.Read`** scopes (the latter powers the folder dropdown — the
47-
SDK doesn't expose a Folders service yet, so the app calls
48-
`/odata/Folders` directly with the SDK's access token)
50+
- A UiPath OAuth External Application (client ID) with **`OR.Tasks`**,
51+
**`OR.Folders.Read`**, and **`OR.Buckets`** scopes. `OR.Folders.Read` powers
52+
the folder dropdown (the SDK doesn't expose a Folders service yet, so the app
53+
calls `/odata/Folders` directly with the SDK's access token); `OR.Buckets`
54+
lets the validation-station widget fetch the document binary referenced by a
55+
Document Validation task.
4956
- An Orchestrator folder that contains Action Center tasks (only needed for
5057
testing — leaving the filter on "All folders" still works)
5158

@@ -78,6 +85,8 @@ uip codedapp deploy -n action-center-app --folder-key <FOLDER_KEY>
7885
- **My Tasks** — clicking a row opens a quick-complete dialog (Approve /
7986
Reject / custom action). An info icon in that dialog opens the full
8087
detail sheet on demand. The streamlined flow is the daily worker view.
88+
A **Document Validation** task instead opens the validation-station widget
89+
(see below).
8190
- **Manage Tasks** (`asTaskAdmin: true`) — clicking rows selects them
8291
(multi-select), and bulk **Assign** / **Unassign** appear in the
8392
toolbar. Surfaces `Tasks.assign(payload[])` and `Tasks.unassign(ids)`
@@ -89,3 +98,11 @@ uip codedapp deploy -n action-center-app --folder-key <FOLDER_KEY>
8998
response holds every row.
9099
- **Creation** through the API only supports **external** tasks — form and
91100
app tasks are created by the system through workflows.
101+
- **Document Validation:** selecting a task of type `DocumentValidationTask`
102+
(from a My Tasks row, or via **Validate document** in the detail sheet) opens
103+
a near-fullscreen dialog hosting `@uipath/ui-widgets-validation-station`. The
104+
widget renders the source document next to the extracted fields; **Save**
105+
validates and keeps the task open, **Submit** validates then completes it via
106+
`task.complete({ type: TaskType.DocumentValidation, action: 'Completed' })`.
107+
`vite.config.ts` copies the widget's `du-assets` (PDF renderer + translations)
108+
into the production bundle so document rendering works after deploy.

samples/action-center-app/package-lock.json

Lines changed: 114 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

samples/action-center-app/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
"dependencies": {
1313
"@uipath/apollo-core": "^5.10.0",
1414
"@uipath/apollo-wind": "^2.19.0",
15+
"@uipath/du-validation-station-wc": "1.0.0-beta.2",
16+
"@uipath/ui-widgets-validation-station": "^1.0.1-beta.6",
1517
"@uipath/uipath-typescript": "file:../..",
1618
"lucide-react": "^0.469.0",
1719
"next-themes": "^0.4.6",
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { useCallback, useState } from 'react'
2+
import { useTheme } from 'next-themes'
3+
import {
4+
ValidationStation,
5+
ValidationStationLanguage,
6+
type ValidationStationProps,
7+
} from '@uipath/ui-widgets-validation-station'
8+
import { TaskType } from '@uipath/uipath-typescript/tasks'
9+
import type { DuFramework } from '@uipath/uipath-typescript/document-understanding'
10+
import { UiPathError } from '@uipath/uipath-typescript/core'
11+
import { Alert, AlertDescription } from '@uipath/apollo-wind/components/ui/alert'
12+
import { Badge } from '@uipath/apollo-wind/components/ui/badge'
13+
import { Button } from '@uipath/apollo-wind/components/ui/button'
14+
import {
15+
Dialog,
16+
DialogContent,
17+
DialogHeader,
18+
DialogTitle,
19+
} from '@uipath/apollo-wind/components/ui/dialog'
20+
import { Spinner } from '@uipath/apollo-wind/components/ui/spinner'
21+
import { toast } from '@uipath/apollo-wind/components/ui/sonner'
22+
import { useAuth } from '../context/AuthContext'
23+
import { useTask } from '../hooks/useTasks'
24+
import { priorityBadge, statusBadge } from '../taskUtils'
25+
26+
/**
27+
* The single argument the validation station hands back to `onSaveComplete`.
28+
* Derived from the widget's own prop type so it tracks the package version.
29+
*/
30+
type SaveValidatedDataResult = Parameters<
31+
NonNullable<ValidationStationProps['onSaveComplete']>
32+
>[0]
33+
34+
/** Which button kicked off the in-flight save. */
35+
type PendingAction = 'save' | 'submit'
36+
37+
interface Props {
38+
/** Lightweight task from the list — used for the header before the full payload loads. */
39+
taskId: number
40+
folderId: number
41+
title: string
42+
onClose: () => void
43+
/** Called after the task is submitted (completed) so the list can refresh. */
44+
onCompleted: () => void
45+
}
46+
47+
/**
48+
* Near-fullscreen host for `@uipath/ui-widgets-validation-station`, shown when
49+
* a selected task is a Document Validation task. The widget renders the source
50+
* document alongside the extracted fields; the reviewer edits, then **Save**
51+
* (validate + keep open) or **Submit** (validate + complete the task).
52+
*
53+
* Both buttons go through the widget's validated-save path: we flip the `save`
54+
* prop to `{ validate: true }`, and the widget reports back via
55+
* `onSaveComplete`. Only the `submit` flow then completes the Action Center task.
56+
*/
57+
export function DocumentValidationDialog({
58+
taskId,
59+
folderId,
60+
title,
61+
onClose,
62+
onCompleted,
63+
}: Props) {
64+
const { sdk } = useAuth()
65+
const { resolvedTheme } = useTheme()
66+
67+
// Full DU payload — `getById` with the task type returns the validation data
68+
// the widget needs (the list response carries only summary fields).
69+
const { task, loading, error } = useTask(taskId, folderId, TaskType.DocumentValidation)
70+
71+
const [save, setSave] = useState<{ validate: boolean } | undefined>(undefined)
72+
const [pendingAction, setPendingAction] = useState<PendingAction | null>(null)
73+
const [actionError, setActionError] = useState<string | null>(null)
74+
75+
const handleSaveComplete = useCallback(
76+
async (result: SaveValidatedDataResult) => {
77+
// Always clear the trigger so a later Save/Submit re-arms the widget.
78+
setSave(undefined)
79+
const action = pendingAction
80+
setPendingAction(null)
81+
82+
// Widget validation failed, the payload is missing, or this was a plain
83+
// Save — nothing further to do; the widget keeps the edits in view.
84+
if (!result.success || !task || action !== 'submit') return
85+
86+
try {
87+
await task.complete({
88+
type: TaskType.DocumentValidation,
89+
action: 'Completed',
90+
data: task.data ?? {},
91+
})
92+
toast.success('Document validated and submitted')
93+
onCompleted()
94+
} catch (err) {
95+
setActionError(
96+
err instanceof UiPathError ? err.message : 'Failed to submit the task',
97+
)
98+
}
99+
},
100+
[pendingAction, task, onCompleted],
101+
)
102+
103+
const trigger = (action: PendingAction) => {
104+
setActionError(null)
105+
setPendingAction(action)
106+
setSave({ validate: true })
107+
}
108+
109+
// Widget needs a loaded payload, and we block re-entry while a save is in flight.
110+
const canAct = !!task?.data && pendingAction === null
111+
112+
return (
113+
<Dialog open onOpenChange={(open) => !open && onClose()}>
114+
<DialogContent
115+
className="flex h-[92vh] w-[96vw] max-w-[96vw] flex-col gap-0 p-0 sm:max-w-[96vw]"
116+
// The widget owns scrolling/focus inside the document viewer.
117+
onInteractOutside={(e) => e.preventDefault()}
118+
>
119+
<DialogHeader className="flex-row items-center justify-between gap-4 space-y-0 border-b px-6 py-4">
120+
<div className="flex min-w-0 items-center gap-2">
121+
<DialogTitle className="truncate">{task?.title ?? title}</DialogTitle>
122+
{task && (
123+
<div className="flex shrink-0 items-center gap-1.5">
124+
<Badge variant={statusBadge(task.status).variant}>
125+
{statusBadge(task.status).label}
126+
</Badge>
127+
<Badge variant={priorityBadge(task.priority).variant}>
128+
{priorityBadge(task.priority).label}
129+
</Badge>
130+
</div>
131+
)}
132+
</div>
133+
<div className="flex shrink-0 items-center gap-2 pr-8">
134+
<Button variant="outline" onClick={() => trigger('save')} disabled={!canAct}>
135+
{pendingAction === 'save' ? 'Saving…' : 'Save'}
136+
</Button>
137+
<Button onClick={() => trigger('submit')} disabled={!canAct}>
138+
{pendingAction === 'submit' ? 'Submitting…' : 'Submit'}
139+
</Button>
140+
</div>
141+
</DialogHeader>
142+
143+
{actionError && (
144+
<Alert variant="destructive" className="mx-6 mt-3 w-auto">
145+
<AlertDescription>{actionError}</AlertDescription>
146+
</Alert>
147+
)}
148+
149+
<div className="min-h-0 flex-1 overflow-hidden">
150+
{loading ? (
151+
<div className="flex h-full items-center justify-center">
152+
<Spinner label="Loading document…" showLabel />
153+
</div>
154+
) : error ? (
155+
<Alert variant="destructive" className="m-6 w-auto">
156+
<AlertDescription>{error}</AlertDescription>
157+
</Alert>
158+
) : !task?.data ? (
159+
<div className="flex h-full items-center justify-center p-6 text-sm text-muted-foreground">
160+
This task has no validation payload to display.
161+
</div>
162+
) : (
163+
<ValidationStation
164+
sdk={sdk}
165+
data={task.data as DuFramework.ContentValidationData}
166+
folderId={task.folderId}
167+
theme={resolvedTheme === 'dark' ? 'dark' : 'light'}
168+
language={ValidationStationLanguage.English}
169+
save={save}
170+
onSaveComplete={handleSaveComplete}
171+
/>
172+
)}
173+
</div>
174+
</DialogContent>
175+
</Dialog>
176+
)
177+
}

0 commit comments

Comments
 (0)