@bpmnkit/user-tasks 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -0
- package/dist/actions.d.ts +11 -0
- package/dist/actions.js +49 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.js +2 -0
- package/dist/widget.d.ts +3 -0
- package/dist/widget.js +228 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://bpmnkit.com"><img src="https://bpmnkit.com/favicon.svg" width="72" height="72" alt="BPMN Kit logo"></a>
|
|
3
|
+
<h1>@bpmnkit/user-tasks</h1>
|
|
4
|
+
<p>Embeddable user task widget for Camunda 8 — form rendering, claim/complete actions, zero dependencies</p>
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/@bpmnkit/user-tasks)
|
|
7
|
+
[](https://github.com/bpmnkit/monorepo/blob/main/LICENSE)
|
|
8
|
+
[](https://github.com/bpmnkit/monorepo)
|
|
9
|
+
|
|
10
|
+
[Website](https://bpmnkit.com) · [Documentation](https://docs.bpmnkit.com) · [GitHub](https://github.com/bpmnkit/monorepo) · [Changelog](https://github.com/bpmnkit/monorepo/blob/main/packages/user-tasks/CHANGELOG.md)
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
`@bpmnkit/user-tasks` is a zero-dependency widget for rendering and interacting with Camunda 8 user tasks. Mount it into any HTML element to get a complete task UI — form rendering via `@bpmnkit/plugins/form-viewer`, claim/unclaim, complete, and optional reject actions.
|
|
18
|
+
|
|
19
|
+
It connects to the `@bpmnkit/proxy` local server to fetch task forms and submit completions via the Camunda REST API.
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Form rendering** — loads the task's linked Camunda Form and renders it via `@bpmnkit/plugins/form-viewer`
|
|
24
|
+
- **Claim / Unclaim** — assigns or removes the task assignee
|
|
25
|
+
- **Complete** — submits collected form variables to the Camunda API
|
|
26
|
+
- **Reject** — optional reject/return action with a reason prompt
|
|
27
|
+
- **Metadata display** — assignee, due date (with overdue highlight), and priority
|
|
28
|
+
- **Theme support** — `light`, `dark`, or `neon` via `@bpmnkit/ui` design tokens
|
|
29
|
+
- **Zero dependencies** — no framework required; mounts into any `HTMLElement`
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npm install @bpmnkit/user-tasks @bpmnkit/proxy
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { createUserTaskWidget } from "@bpmnkit/user-tasks"
|
|
41
|
+
|
|
42
|
+
const widget = createUserTaskWidget({
|
|
43
|
+
container: document.getElementById("task-panel")!,
|
|
44
|
+
task: {
|
|
45
|
+
userTaskKey: "2251799813685281",
|
|
46
|
+
name: "Review order",
|
|
47
|
+
assignee: "alice",
|
|
48
|
+
dueDate: "2025-06-01T12:00:00Z",
|
|
49
|
+
priority: 50,
|
|
50
|
+
},
|
|
51
|
+
proxyUrl: "http://localhost:3033", // default
|
|
52
|
+
theme: "dark",
|
|
53
|
+
onComplete(variables) {
|
|
54
|
+
console.log("Task completed with", variables)
|
|
55
|
+
},
|
|
56
|
+
onClaim() {
|
|
57
|
+
console.log("Task claimed")
|
|
58
|
+
},
|
|
59
|
+
onUnclaim() {
|
|
60
|
+
console.log("Task unclaimed")
|
|
61
|
+
},
|
|
62
|
+
onReject(reason) {
|
|
63
|
+
console.log("Task rejected:", reason)
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Later: update the displayed task
|
|
68
|
+
widget.setTask({ userTaskKey: "2251799813685999", name: "Approve invoice" })
|
|
69
|
+
|
|
70
|
+
// Clean up
|
|
71
|
+
widget.destroy()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## API Reference
|
|
75
|
+
|
|
76
|
+
### `createUserTaskWidget(options)`
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
interface UserTaskWidgetOptions {
|
|
80
|
+
/** The container element to render the widget into. */
|
|
81
|
+
container: HTMLElement
|
|
82
|
+
/** The user task to display. */
|
|
83
|
+
task: UserTask
|
|
84
|
+
/** Base URL of the proxy server. Default: "http://localhost:3033" */
|
|
85
|
+
proxyUrl?: string
|
|
86
|
+
/** Active profile name for x-profile header. */
|
|
87
|
+
profile?: string | null
|
|
88
|
+
/** Visual theme. Default: "neon" */
|
|
89
|
+
theme?: "light" | "dark" | "neon"
|
|
90
|
+
/** Called when the user completes the task. */
|
|
91
|
+
onComplete(variables: Record<string, unknown>): void
|
|
92
|
+
/** Called when the user claims the task. */
|
|
93
|
+
onClaim(): void
|
|
94
|
+
/** Called when the user unclaims the task. */
|
|
95
|
+
onUnclaim(): void
|
|
96
|
+
/** Called when the user rejects/returns the task. Optional — hides the Reject button if omitted. */
|
|
97
|
+
onReject?(reason: string): void
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Returns a `UserTaskWidgetApi`:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
interface UserTaskWidgetApi {
|
|
105
|
+
/** Update the displayed task and reload its form. */
|
|
106
|
+
setTask(task: UserTask): void
|
|
107
|
+
/** Remove the widget from the DOM and clean up. */
|
|
108
|
+
destroy(): void
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `UserTask`
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
interface UserTask {
|
|
116
|
+
userTaskKey: string
|
|
117
|
+
name?: string
|
|
118
|
+
assignee?: string
|
|
119
|
+
candidateGroups?: string[]
|
|
120
|
+
dueDate?: string // ISO 8601 date string
|
|
121
|
+
priority?: number
|
|
122
|
+
processInstanceKey?: string
|
|
123
|
+
processDefinitionKey?: string
|
|
124
|
+
formKey?: string
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Related Packages
|
|
131
|
+
|
|
132
|
+
| Package | Description |
|
|
133
|
+
|---------|-------------|
|
|
134
|
+
| [`@bpmnkit/core`](https://www.npmjs.com/package/@bpmnkit/core) | BPMN/DMN/Form parser, builder, layout engine |
|
|
135
|
+
| [`@bpmnkit/canvas`](https://www.npmjs.com/package/@bpmnkit/canvas) | Zero-dependency SVG BPMN viewer |
|
|
136
|
+
| [`@bpmnkit/editor`](https://www.npmjs.com/package/@bpmnkit/editor) | Full-featured interactive BPMN editor |
|
|
137
|
+
| [`@bpmnkit/engine`](https://www.npmjs.com/package/@bpmnkit/engine) | Lightweight BPMN process execution engine |
|
|
138
|
+
| [`@bpmnkit/feel`](https://www.npmjs.com/package/@bpmnkit/feel) | FEEL expression language parser & evaluator |
|
|
139
|
+
| [`@bpmnkit/plugins`](https://www.npmjs.com/package/@bpmnkit/plugins) | 22 composable canvas plugins |
|
|
140
|
+
| [`@bpmnkit/operate`](https://www.npmjs.com/package/@bpmnkit/operate) | Monitoring and operations frontend for Camunda 8 |
|
|
141
|
+
| [`@bpmnkit/api`](https://www.npmjs.com/package/@bpmnkit/api) | Camunda 8 REST API TypeScript client |
|
|
142
|
+
| [`@bpmnkit/ascii`](https://www.npmjs.com/package/@bpmnkit/ascii) | Render BPMN diagrams as Unicode ASCII art |
|
|
143
|
+
| [`@bpmnkit/ui`](https://www.npmjs.com/package/@bpmnkit/ui) | Shared design tokens and UI components |
|
|
144
|
+
| [`@bpmnkit/profiles`](https://www.npmjs.com/package/@bpmnkit/profiles) | Shared auth, profile storage, and client factories for CLI & proxy |
|
|
145
|
+
| [`@bpmnkit/connector-gen`](https://www.npmjs.com/package/@bpmnkit/connector-gen) | Generate connector templates from OpenAPI specs |
|
|
146
|
+
| [`@bpmnkit/cli`](https://www.npmjs.com/package/@bpmnkit/cli) | Camunda 8 command-line interface (casen) |
|
|
147
|
+
| [`@bpmnkit/proxy`](https://www.npmjs.com/package/@bpmnkit/proxy) | Local AI bridge and Camunda API proxy server |
|
|
148
|
+
| [`@bpmnkit/cli-sdk`](https://www.npmjs.com/package/@bpmnkit/cli-sdk) | Plugin authoring SDK for the casen CLI |
|
|
149
|
+
| [`@bpmnkit/create-casen-plugin`](https://www.npmjs.com/package/@bpmnkit/create-casen-plugin) | Scaffold a new casen CLI plugin in seconds |
|
|
150
|
+
| [`@bpmnkit/casen-report`](https://www.npmjs.com/package/@bpmnkit/casen-report) | HTML reports from Camunda 8 incident and SLA data |
|
|
151
|
+
| [`@bpmnkit/casen-worker-http`](https://www.npmjs.com/package/@bpmnkit/casen-worker-http) | Example HTTP worker plugin — completes jobs with live JSONPlaceholder API data |
|
|
152
|
+
| [`@bpmnkit/casen-worker-ai`](https://www.npmjs.com/package/@bpmnkit/casen-worker-ai) | AI task worker — classify, summarize, extract, and decide using Claude |
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
[MIT](https://github.com/bpmnkit/monorepo/blob/main/LICENSE) © BPMN Kit — made by [u11g](https://u11g.com)
|
|
157
|
+
|
|
158
|
+
<div align="center">
|
|
159
|
+
<a href="https://bpmnkit.com"><img src="https://bpmnkit.com/favicon.svg" width="32" height="32" alt="BPMN Kit"></a>
|
|
160
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface TaskActionOptions {
|
|
2
|
+
proxyUrl: string;
|
|
3
|
+
profile: string | null;
|
|
4
|
+
taskKey: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function claimTask(options: TaskActionOptions, assignee: string): Promise<void>;
|
|
7
|
+
export declare function unclaimTask(options: TaskActionOptions): Promise<void>;
|
|
8
|
+
export declare function completeTask(options: TaskActionOptions, variables: Record<string, unknown>): Promise<void>;
|
|
9
|
+
export declare function fetchTaskForm(proxyUrl: string, profile: string | null, taskKey: string): Promise<unknown>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=actions.d.ts.map
|
package/dist/actions.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
function buildHeaders(profile) {
|
|
2
|
+
const headers = {
|
|
3
|
+
"content-type": "application/json",
|
|
4
|
+
accept: "application/json",
|
|
5
|
+
};
|
|
6
|
+
if (profile)
|
|
7
|
+
headers["x-profile"] = profile;
|
|
8
|
+
return headers;
|
|
9
|
+
}
|
|
10
|
+
async function proxyPost(proxyUrl, path, profile, body) {
|
|
11
|
+
const res = await fetch(`${proxyUrl}${path}`, {
|
|
12
|
+
method: "POST",
|
|
13
|
+
headers: buildHeaders(profile),
|
|
14
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
15
|
+
});
|
|
16
|
+
if (!res.ok) {
|
|
17
|
+
const text = await res.text().catch(() => "");
|
|
18
|
+
throw new Error(`HTTP ${res.status}${text ? `: ${text}` : ""}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function proxyDelete(proxyUrl, path, profile) {
|
|
22
|
+
const res = await fetch(`${proxyUrl}${path}`, {
|
|
23
|
+
method: "DELETE",
|
|
24
|
+
headers: buildHeaders(profile),
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
const text = await res.text().catch(() => "");
|
|
28
|
+
throw new Error(`HTTP ${res.status}${text ? `: ${text}` : ""}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function claimTask(options, assignee) {
|
|
32
|
+
return proxyPost(options.proxyUrl, `/api/user-tasks/${options.taskKey}/assignment`, options.profile, { assignee });
|
|
33
|
+
}
|
|
34
|
+
export async function unclaimTask(options) {
|
|
35
|
+
return proxyDelete(options.proxyUrl, `/api/user-tasks/${options.taskKey}/assignment`, options.profile);
|
|
36
|
+
}
|
|
37
|
+
export async function completeTask(options, variables) {
|
|
38
|
+
return proxyPost(options.proxyUrl, `/api/user-tasks/${options.taskKey}/completion`, options.profile, { variables });
|
|
39
|
+
}
|
|
40
|
+
export async function fetchTaskForm(proxyUrl, profile, taskKey) {
|
|
41
|
+
const headers = { accept: "application/json" };
|
|
42
|
+
if (profile)
|
|
43
|
+
headers["x-profile"] = profile;
|
|
44
|
+
const res = await fetch(`${proxyUrl}/api/user-tasks/${taskKey}/form`, { headers });
|
|
45
|
+
if (!res.ok)
|
|
46
|
+
throw new Error(`HTTP ${res.status}`);
|
|
47
|
+
return res.json();
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=actions.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Theme } from "@bpmnkit/ui";
|
|
2
|
+
export interface UserTask {
|
|
3
|
+
userTaskKey: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
assignee?: string;
|
|
6
|
+
candidateGroups?: string[];
|
|
7
|
+
dueDate?: string;
|
|
8
|
+
priority?: number;
|
|
9
|
+
processInstanceKey?: string;
|
|
10
|
+
processDefinitionKey?: string;
|
|
11
|
+
formKey?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface UserTaskWidgetOptions {
|
|
14
|
+
/** The container element to render the widget into. */
|
|
15
|
+
container: HTMLElement;
|
|
16
|
+
/** The user task to display. */
|
|
17
|
+
task: UserTask;
|
|
18
|
+
/** Base URL of the proxy server. */
|
|
19
|
+
proxyUrl?: string;
|
|
20
|
+
/** Active profile name for x-profile header. */
|
|
21
|
+
profile?: string | null;
|
|
22
|
+
/** Visual theme. Defaults to "neon". */
|
|
23
|
+
theme?: Theme;
|
|
24
|
+
/** Called when the user completes the task. */
|
|
25
|
+
onComplete(variables: Record<string, unknown>): void;
|
|
26
|
+
/** Called when the user claims the task. */
|
|
27
|
+
onClaim(): void;
|
|
28
|
+
/** Called when the user unclaims the task. */
|
|
29
|
+
onUnclaim(): void;
|
|
30
|
+
/** Called when the user rejects/returns the task. */
|
|
31
|
+
onReject?(reason: string): void;
|
|
32
|
+
}
|
|
33
|
+
export interface UserTaskWidgetApi {
|
|
34
|
+
/** Update the displayed task. */
|
|
35
|
+
setTask(task: UserTask): void;
|
|
36
|
+
/** Remove the widget from the DOM and clean up. */
|
|
37
|
+
destroy(): void;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
package/dist/widget.d.ts
ADDED
package/dist/widget.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { FormViewer } from "@bpmnkit/plugins/form-viewer";
|
|
2
|
+
import { applyTheme, injectUiStyles } from "@bpmnkit/ui";
|
|
3
|
+
import { claimTask, completeTask, fetchTaskForm, unclaimTask } from "./actions.js";
|
|
4
|
+
function el(tag, className, text) {
|
|
5
|
+
const e = document.createElement(tag);
|
|
6
|
+
e.className = className;
|
|
7
|
+
if (text !== undefined)
|
|
8
|
+
e.textContent = text;
|
|
9
|
+
return e;
|
|
10
|
+
}
|
|
11
|
+
const WIDGET_CSS = `
|
|
12
|
+
.ut-root { font-family: var(--bpmnkit-font, system-ui, sans-serif); color: var(--bpmnkit-fg, #1a1a2e); }
|
|
13
|
+
.ut-header { padding: 16px; border-bottom: 1px solid var(--bpmnkit-border, #d0d0e8); }
|
|
14
|
+
.ut-name { font-size: 18px; font-weight: 600; margin-bottom: 8px; }
|
|
15
|
+
.ut-meta { display: flex; gap: 12px; font-size: 13px; color: var(--bpmnkit-fg-muted, #6666a0); }
|
|
16
|
+
.ut-meta-item { display: flex; align-items: center; gap: 4px; }
|
|
17
|
+
.ut-overdue { color: var(--bpmnkit-danger, #dc2626); }
|
|
18
|
+
.ut-form { padding: 16px; flex: 1; overflow-y: auto; }
|
|
19
|
+
.ut-form-placeholder { padding: 32px; text-align: center; color: var(--bpmnkit-fg-muted, #6666a0); font-size: 14px; }
|
|
20
|
+
.ut-actions { display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--bpmnkit-border, #d0d0e8); }
|
|
21
|
+
.ut-btn { padding: 6px 14px; border-radius: 6px; font-size: 13px; cursor: pointer; border: 1px solid transparent; transition: opacity 0.15s; }
|
|
22
|
+
.ut-btn:hover { opacity: 0.85; }
|
|
23
|
+
.ut-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
24
|
+
.ut-btn-primary { background: var(--bpmnkit-accent, #1a56db); color: var(--bpmnkit-accent-fg, #fff); }
|
|
25
|
+
.ut-btn-secondary { background: transparent; border-color: var(--bpmnkit-border, #d0d0e8); color: var(--bpmnkit-fg, #1a1a2e); }
|
|
26
|
+
.ut-btn-danger { background: var(--bpmnkit-danger, #dc2626); color: #fff; }
|
|
27
|
+
.ut-error { padding: 8px 12px; background: color-mix(in srgb, var(--bpmnkit-danger, #dc2626) 15%, transparent); color: var(--bpmnkit-danger, #dc2626); border-radius: 4px; font-size: 13px; margin: 0 16px 8px; }
|
|
28
|
+
`;
|
|
29
|
+
function injectWidgetStyles() {
|
|
30
|
+
const id = "bpmnkit-user-tasks-css";
|
|
31
|
+
if (document.getElementById(id))
|
|
32
|
+
return;
|
|
33
|
+
const style = document.createElement("style");
|
|
34
|
+
style.id = id;
|
|
35
|
+
style.textContent = WIDGET_CSS;
|
|
36
|
+
document.head.appendChild(style);
|
|
37
|
+
}
|
|
38
|
+
export function createUserTaskWidget(options) {
|
|
39
|
+
injectUiStyles();
|
|
40
|
+
injectWidgetStyles();
|
|
41
|
+
const { container, proxyUrl = "http://localhost:3033", profile = null, theme = "neon", onComplete, onClaim, onUnclaim, onReject, } = options;
|
|
42
|
+
// Create root element
|
|
43
|
+
const root = el("div", "ut-root");
|
|
44
|
+
applyTheme(root, theme);
|
|
45
|
+
root.style.display = "flex";
|
|
46
|
+
root.style.flexDirection = "column";
|
|
47
|
+
root.style.height = "100%";
|
|
48
|
+
container.appendChild(root);
|
|
49
|
+
// State
|
|
50
|
+
let currentTask = options.task;
|
|
51
|
+
let formViewer = null;
|
|
52
|
+
let formVariables = {};
|
|
53
|
+
// Error area
|
|
54
|
+
const errorEl = el("div", "ut-error");
|
|
55
|
+
errorEl.style.display = "none";
|
|
56
|
+
// Header
|
|
57
|
+
const header = el("div", "ut-header");
|
|
58
|
+
const nameEl = el("div", "ut-name");
|
|
59
|
+
const metaEl = el("div", "ut-meta");
|
|
60
|
+
header.appendChild(nameEl);
|
|
61
|
+
header.appendChild(metaEl);
|
|
62
|
+
root.appendChild(header);
|
|
63
|
+
root.appendChild(errorEl);
|
|
64
|
+
// Form area
|
|
65
|
+
const formArea = el("div", "ut-form");
|
|
66
|
+
const formContainer = el("div", "");
|
|
67
|
+
formArea.appendChild(formContainer);
|
|
68
|
+
root.appendChild(formArea);
|
|
69
|
+
// Actions
|
|
70
|
+
const actionsEl = el("div", "ut-actions");
|
|
71
|
+
const claimBtn = el("button", "ut-btn ut-btn-secondary", "Claim");
|
|
72
|
+
claimBtn.type = "button";
|
|
73
|
+
const unclaimBtn = el("button", "ut-btn ut-btn-secondary", "Unclaim");
|
|
74
|
+
unclaimBtn.type = "button";
|
|
75
|
+
const completeBtn = el("button", "ut-btn ut-btn-primary", "Complete");
|
|
76
|
+
completeBtn.type = "button";
|
|
77
|
+
const rejectBtn = el("button", "ut-btn ut-btn-danger", "Reject");
|
|
78
|
+
rejectBtn.type = "button";
|
|
79
|
+
if (!onReject)
|
|
80
|
+
rejectBtn.style.display = "none";
|
|
81
|
+
actionsEl.appendChild(claimBtn);
|
|
82
|
+
actionsEl.appendChild(unclaimBtn);
|
|
83
|
+
actionsEl.appendChild(completeBtn);
|
|
84
|
+
if (onReject)
|
|
85
|
+
actionsEl.appendChild(rejectBtn);
|
|
86
|
+
root.appendChild(actionsEl);
|
|
87
|
+
function showError(msg) {
|
|
88
|
+
errorEl.textContent = msg;
|
|
89
|
+
errorEl.style.display = "block";
|
|
90
|
+
}
|
|
91
|
+
function clearError() {
|
|
92
|
+
errorEl.style.display = "none";
|
|
93
|
+
}
|
|
94
|
+
function renderTask(task) {
|
|
95
|
+
nameEl.textContent = task.name ?? `Task ${task.userTaskKey}`;
|
|
96
|
+
metaEl.innerHTML = "";
|
|
97
|
+
if (task.assignee) {
|
|
98
|
+
const assigneeItem = el("span", "ut-meta-item");
|
|
99
|
+
assigneeItem.textContent = `Assigned to ${task.assignee}`;
|
|
100
|
+
metaEl.appendChild(assigneeItem);
|
|
101
|
+
}
|
|
102
|
+
if (task.dueDate) {
|
|
103
|
+
const due = new Date(task.dueDate);
|
|
104
|
+
const overdue = due < new Date();
|
|
105
|
+
const dueItem = el("span", overdue ? "ut-meta-item ut-overdue" : "ut-meta-item");
|
|
106
|
+
dueItem.textContent = `Due: ${due.toLocaleDateString()}${overdue ? " (overdue)" : ""}`;
|
|
107
|
+
metaEl.appendChild(dueItem);
|
|
108
|
+
}
|
|
109
|
+
if (task.priority !== undefined) {
|
|
110
|
+
const priorityItem = el("span", "ut-meta-item");
|
|
111
|
+
priorityItem.textContent = `Priority: ${task.priority}`;
|
|
112
|
+
metaEl.appendChild(priorityItem);
|
|
113
|
+
}
|
|
114
|
+
// Update button states
|
|
115
|
+
claimBtn.disabled = !!task.assignee;
|
|
116
|
+
unclaimBtn.disabled = !task.assignee;
|
|
117
|
+
// Load form
|
|
118
|
+
void loadForm(task);
|
|
119
|
+
}
|
|
120
|
+
async function loadForm(task) {
|
|
121
|
+
// Clean up existing viewer
|
|
122
|
+
if (formViewer) {
|
|
123
|
+
formViewer.destroy();
|
|
124
|
+
formViewer = null;
|
|
125
|
+
}
|
|
126
|
+
formContainer.innerHTML = "";
|
|
127
|
+
try {
|
|
128
|
+
const formData = await fetchTaskForm(proxyUrl, profile, task.userTaskKey);
|
|
129
|
+
// Camunda v2 returns { schema: Record<string,unknown>, ... }
|
|
130
|
+
// Some versions send schema as a JSON string instead of an object
|
|
131
|
+
let formDef = formData;
|
|
132
|
+
if (formData && typeof formData === "object" && "schema" in formData) {
|
|
133
|
+
const raw = formData.schema;
|
|
134
|
+
if (raw && typeof raw === "object") {
|
|
135
|
+
formDef = raw;
|
|
136
|
+
}
|
|
137
|
+
else if (typeof raw === "string") {
|
|
138
|
+
try {
|
|
139
|
+
formDef = JSON.parse(raw);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
formDef = null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (formDef && typeof formDef === "object" && "components" in formDef) {
|
|
147
|
+
const fvTheme = theme === "light" ? "light" : "dark";
|
|
148
|
+
formViewer = new FormViewer({ container: formContainer, theme: fvTheme });
|
|
149
|
+
formViewer.load(formDef);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
const placeholder = el("div", "ut-form-placeholder", "No form associated with this task.");
|
|
153
|
+
formContainer.appendChild(placeholder);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
const placeholder = el("div", "ut-form-placeholder", "No form schema found or form is embedded in the process.");
|
|
158
|
+
formContainer.appendChild(placeholder);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Event handlers
|
|
162
|
+
claimBtn.addEventListener("click", () => {
|
|
163
|
+
claimBtn.disabled = true;
|
|
164
|
+
claimBtn.textContent = "Claiming...";
|
|
165
|
+
claimTask({ proxyUrl, profile, taskKey: currentTask.userTaskKey }, "studio-user")
|
|
166
|
+
.then(() => {
|
|
167
|
+
clearError();
|
|
168
|
+
onClaim();
|
|
169
|
+
})
|
|
170
|
+
.catch((err) => {
|
|
171
|
+
showError(`Claim failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
172
|
+
})
|
|
173
|
+
.finally(() => {
|
|
174
|
+
claimBtn.textContent = "Claim";
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
unclaimBtn.addEventListener("click", () => {
|
|
178
|
+
unclaimBtn.disabled = true;
|
|
179
|
+
unclaimBtn.textContent = "Unclaiming...";
|
|
180
|
+
unclaimTask({ proxyUrl, profile, taskKey: currentTask.userTaskKey })
|
|
181
|
+
.then(() => {
|
|
182
|
+
clearError();
|
|
183
|
+
onUnclaim();
|
|
184
|
+
})
|
|
185
|
+
.catch((err) => {
|
|
186
|
+
showError(`Unclaim failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
187
|
+
})
|
|
188
|
+
.finally(() => {
|
|
189
|
+
unclaimBtn.textContent = "Unclaim";
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
completeBtn.addEventListener("click", () => {
|
|
193
|
+
completeBtn.disabled = true;
|
|
194
|
+
completeBtn.textContent = "Completing...";
|
|
195
|
+
completeTask({ proxyUrl, profile, taskKey: currentTask.userTaskKey }, formVariables)
|
|
196
|
+
.then(() => {
|
|
197
|
+
clearError();
|
|
198
|
+
onComplete(formVariables);
|
|
199
|
+
})
|
|
200
|
+
.catch((err) => {
|
|
201
|
+
showError(`Complete failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
202
|
+
completeBtn.disabled = false;
|
|
203
|
+
})
|
|
204
|
+
.finally(() => {
|
|
205
|
+
completeBtn.textContent = "Complete";
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
rejectBtn.addEventListener("click", () => {
|
|
209
|
+
const reason = window.prompt("Reason for rejection (optional):", "") ?? "";
|
|
210
|
+
if (onReject)
|
|
211
|
+
onReject(reason);
|
|
212
|
+
});
|
|
213
|
+
// Initial render
|
|
214
|
+
renderTask(currentTask);
|
|
215
|
+
return {
|
|
216
|
+
setTask(task) {
|
|
217
|
+
currentTask = task;
|
|
218
|
+
formVariables = {};
|
|
219
|
+
renderTask(task);
|
|
220
|
+
},
|
|
221
|
+
destroy() {
|
|
222
|
+
formViewer?.destroy();
|
|
223
|
+
formViewer = null;
|
|
224
|
+
root.remove();
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=widget.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bpmnkit/user-tasks",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Embeddable user task widget for Camunda 8 — form rendering, claim/complete actions, zero dependencies",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["LICENSE", "README.md", "dist/**/*.js", "dist/**/*.d.ts"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"check": "biome check ."
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@bpmnkit/api": "workspace:*",
|
|
22
|
+
"@bpmnkit/core": "workspace:*",
|
|
23
|
+
"@bpmnkit/plugins": "workspace:*",
|
|
24
|
+
"@bpmnkit/ui": "workspace:*"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"bpmn",
|
|
28
|
+
"camunda",
|
|
29
|
+
"user-tasks",
|
|
30
|
+
"workflow",
|
|
31
|
+
"form",
|
|
32
|
+
"task-management",
|
|
33
|
+
"typescript"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/bpmnkit/monorepo"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://bpmnkit.com",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/bpmnkit/monorepo/issues"
|
|
46
|
+
}
|
|
47
|
+
}
|