@haneinfo/frontend-instances 0.2.0 → 0.3.0

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 CHANGED
@@ -1,73 +1,95 @@
1
- # @haneinfo/frontend-instances
2
-
3
- Reusable instance lists, collaboration and import/export pages for React 19 / Next 16.
4
- Backend packages stay free of React and NestJS is not a dependency of this package.
5
-
6
- ## Mounting
7
-
8
- Import `renderInstanceListPage`, `renderInstanceDetailPage` and `renderImportPage` from
9
- `@haneinfo/frontend-instances/server`. Each returns a server-renderable React tree. Supply:
10
-
11
- - `apiFetch<T>(path, init?)`, using the current authenticated session and an App-owned API URL.
12
- - `LinkComponent`, `t(LabelKey)`, `getCurrentUser()`, and `basePath`.
13
- - An App Server Action calling `performInstanceRequest(apiFetch, request)` and returning
14
- `{ data }` or `{ error: instanceError(error) }` after checking the session.
15
- - For imports, an authenticated `uploadAction(FormData)` calling `readImportUpload`, plus
16
- the App's `{ code, label }[]` machine choices.
17
-
18
- The list additionally takes `machineCode` and promised `searchParams: { query?: string }`.
19
- The detail takes `id`. The import takes `machines` and `uploadAction`.
20
- All UI labels are injected. System field labels use `t`; Form labels stay verbatim from the API.
21
- See `LabelKey` and the complete English/zh-TW mounting examples in
22
- `validation/equipment-loan/instance-frontend/app/` in the source repository.
23
-
24
- Add `@source "../../node_modules/@haneinfo/frontend-instances/dist";` to the App's Tailwind
25
- CSS source declarations. Mount navigation in the App's sidebar, not generated plugin files.
26
- Set the App's Server Action body limit to allow multipart uploads (the example uses 16 MB).
27
- The backend HTTP body limit remains App-owned; API rejections are shown in the UI.
28
-
29
- ## Client components and contracts
30
-
31
- `@haneinfo/frontend-instances/client` exports `ListClient`, `DetailClient`, `ImportClient`,
32
- `FilterEditor`, `JobStatus`, `JobReport`, `ImportConfirmation` and `prepareFilter`.
33
- Use the server renderers for normal mounting. `@haneinfo/frontend-instances/contracts` exports
34
- HTTP-only types and `labelKeys`. The server entry also exports the contracts, `dateRange`,
35
- `importMapping`, `normalizeQuery`, `performInstanceRequest`, `readImportUpload` and `instanceError`.
36
-
37
- Lists support PREDICATE/GROUP AND/OR, all eight operators, inclusive date ranges, up to eight
38
- sort keys, selected columns and offset/limit pagination (1–500). IN uses one literal per line.
39
- Datetime-local controls are explicitly UTC. Exports use the last applied query, not unapplied
40
- editor changes. Saved views default to private; sharing controls depend only on `canShare`.
41
-
42
- Details use pinned subject Form labels and the merged timeline; mentions come only from the
43
- instance-specific mentionable API. Following comes from the timeline or mutation response.
44
- Comments are append-only plain text and AI comments have a visible attribution.
45
-
46
- CSV and xlsx headers are read using csv-parse and ExcelJS on the server, with a 10 MB upload
47
- bound. The backend performs the complete preflight and authorization. Duplicate keys are
48
- optional, mapped top-level keys with a five-key maximum. Preflight never confirms an import.
49
- Confirmation requires a separate button click; global errors and zero valid rows disable it.
50
- Re-upload to change a completed preflight's mapping.
51
-
52
- `JobStatus` polls every two seconds, stops on terminal status or failure, and bounds automatic
53
- polling to 90 requests. A manual refresh resumes polling. Job state is owner-authorized by the
54
- backend. Binary result bytes and uploaded job params are removed before returning jobs to clients.
55
-
56
- ## Downloads and safety
57
-
58
- Mount `${basePath}/download?path=...` as an authenticated, no-store streaming GET proxy. Constrain
59
- the path to `/import-export/(imports|exports)/<id>/file`, forward the current session token to
60
- that exact backend-provided `downloadPath`, and forward its disposition/type. Never synthesize
61
- job download paths or put file bytes/tokens in the URL. The App example includes this route.
62
-
63
- Use real Next Server Actions so same-origin CSRF checks apply. All mutations have an immediate
64
- in-memory submission lock and disabled pending buttons. The backend remains authoritative for
65
- authorization and validation; the UI does not recreate RBAC. 401/403/404 display generic localized
66
- messages without raw API details. All user content is rendered as text nodes, including errors,
67
- field values, names, and timeline details. No HTML or Markdown rendering is used.
68
-
69
- No instance creation, submission, approval, rejection or transition controls are included.
70
-
71
- ## Submitter display
72
-
73
- List `$submitterId` cells and the detail submitter field display `submitterName ?? submitterId`. Both are plain React text nodes, including markup-like names. The HTTP contracts include nullable submitterName; older responses without it also fall back to the id. No new translation key, lookup request or App host-page change is required. Upgrade the state-machine and list-view backends with this frontend for names; otherwise the id remains visible.
1
+ # @haneinfo/frontend-instances
2
+
3
+ Reusable instance lists, collaboration and import/export pages for React 19 / Next 16.
4
+ Backend packages stay free of React and NestJS is not a dependency of this package.
5
+
6
+ ## Mounting
7
+
8
+ Import `renderInstanceListPage`, `renderInstanceDetailPage` and `renderImportPage` from
9
+ `@haneinfo/frontend-instances/server`. Each returns a server-renderable React tree. Supply:
10
+
11
+ - `apiFetch<T>(path, init?)`, using the current authenticated session and an App-owned API URL.
12
+ - `LinkComponent`, `t(LabelKey)`, `getCurrentUser()`, and `basePath`.
13
+ - An App Server Action calling `performInstanceRequest(apiFetch, request)` and returning
14
+ `{ data }` or `{ error: instanceError(error) }` after checking the session.
15
+ - For imports, an authenticated `uploadAction(FormData)` calling `readImportUpload`, plus
16
+ the App's `{ code, label }[]` machine choices.
17
+
18
+ The list additionally takes `machineCode` and promised `searchParams: { query?: string }`.
19
+ The detail takes `id`. The import takes `machines` and `uploadAction`.
20
+ All UI labels are injected. System field labels use `t`; Form labels stay verbatim from the API.
21
+ See `LabelKey` and the complete English/zh-TW mounting examples in
22
+ `validation/equipment-loan/instance-frontend/app/` in the source repository.
23
+
24
+ Add `@source "../../node_modules/@haneinfo/frontend-instances/dist";` to the App's Tailwind
25
+ CSS source declarations. Mount navigation in the App's sidebar, not generated plugin files.
26
+ Set the App's Server Action body limit to allow multipart uploads (the example uses 16 MB).
27
+ The backend HTTP body limit remains App-owned; API rejections are shown in the UI.
28
+
29
+ ## Client components and contracts
30
+
31
+ `@haneinfo/frontend-instances/client` exports `ListClient`, `DetailClient`, `ImportClient`,
32
+ `FilterEditor`, `JobStatus`, `JobReport`, `ImportConfirmation` and `prepareFilter`.
33
+ Use the server renderers for normal mounting. `@haneinfo/frontend-instances/contracts` exports
34
+ HTTP-only types and `labelKeys`. The server entry also exports the contracts, `dateRange`,
35
+ `importMapping`, `normalizeQuery`, `performInstanceRequest`, `readImportUpload` and `instanceError`.
36
+
37
+ Lists support PREDICATE/GROUP AND/OR, all eight operators, inclusive date ranges, up to eight
38
+ sort keys, selected columns and offset/limit pagination (1–500). IN uses one literal per line.
39
+ Datetime-local controls are explicitly UTC. Exports use the last applied query, not unapplied
40
+ editor changes. Saved views default to private; sharing controls depend only on `canShare`.
41
+
42
+ Details use pinned subject Form labels and the merged timeline; mentions come only from the
43
+ instance-specific mentionable API. Following comes from the timeline or mutation response.
44
+ Comments are append-only plain text and AI comments have a visible attribution.
45
+
46
+ CSV and xlsx headers are read using csv-parse and ExcelJS on the server, with a 10 MB upload
47
+ bound. The backend performs the complete preflight and authorization. Duplicate keys are
48
+ optional, mapped top-level keys with a five-key maximum. Preflight never confirms an import.
49
+ Confirmation requires a separate button click; global errors and zero valid rows disable it.
50
+ Re-upload to change a completed preflight's mapping.
51
+
52
+ `JobStatus` polls every two seconds, stops on terminal status or failure, and bounds automatic
53
+ polling to 90 requests. A manual refresh resumes polling. Job state is owner-authorized by the
54
+ backend. Binary result bytes and uploaded job params are removed before returning jobs to clients.
55
+
56
+ ## Downloads and safety
57
+
58
+ Mount `${basePath}/download?path=...` as an authenticated, no-store streaming GET proxy. Constrain
59
+ the path to `/import-export/(imports|exports)/<id>/file`, forward the current session token to
60
+ that exact backend-provided `downloadPath`, and forward its disposition/type. Never synthesize
61
+ job download paths or put file bytes/tokens in the URL. The App example includes this route.
62
+
63
+ Use real Next Server Actions so same-origin CSRF checks apply. All mutations have an immediate
64
+ in-memory submission lock and disabled pending buttons. The backend remains authoritative for
65
+ authorization and validation; the UI does not recreate RBAC. 401/403/404 display generic localized
66
+ messages without raw API details. All user content is rendered as text nodes, including errors,
67
+ field values, names, and timeline details. No HTML or Markdown rendering is used.
68
+
69
+ No instance creation, submission, approval, rejection or transition controls are included.
70
+
71
+ ## Submitter display
72
+
73
+ List `$submitterId` cells and the detail submitter field display `submitterName ?? submitterId`. Both are plain React text nodes, including markup-like names. The HTTP contracts include nullable submitterName; older responses without it also fall back to the id. No new translation key, lookup request or App host-page change is required. Upgrade the state-machine and list-view backends with this frontend for names; otherwise the id remains visible.
74
+
75
+
76
+ ## Delegation and access management (draft 202610-e2a9)
77
+
78
+ `renderDelegationsPage` accepts the same injected page dependencies as instance pages. The App
79
+ mounts it at `/dashboard/delegations` and supplies an authenticated Server Action calling
80
+ `performInstanceRequest`. Administrative controls depend on the successful admin list API;
81
+ only DELEGATION_NOT_FOUND is treated as a denial. Other failures render an error.
82
+ The UTC datetime inputs require an explicit end; backend rules remain authoritative.
83
+
84
+ Pass `showAccess: true` to `renderInstanceDetailPage` to load the access list. `InstanceAccessPanel`
85
+ is exported from the client entry for other hosts. It renders USER/ROLE, READ/WRITE, sources,
86
+ grantor and timestamps. Only API canManage enables management, and only GRANTED rows have
87
+ change/remove controls. Mutations refresh both the list and the enclosing activity timeline.
88
+ Pickers use bounded explicit searches and keyboard combobox selection; user search needs two
89
+ trimmed characters. Every mutation uses the supplied Server Action and a synchronous submit lock.
90
+
91
+ Merge the bilingual catalog fragments in validation/equipment-loan/delegation-access-frontend/app/
92
+ into the host's instances catalog. The new keys include the known error code allowlist.
93
+ `instanceError` only maps recognized codes. Unknown/missing codes, even on 401/403/404, use
94
+ requestFailed. Backend message, diagnostic message and lastError text are never rendered.
95
+ Upgrade common and frontend-shell together so the production filter and API adapter retain code.
package/agent-guide.md CHANGED
@@ -1,39 +1,54 @@
1
- # Instance frontend agent guide
2
-
3
- Use this package to mount shared instance lists, read-only details with collaboration, and
4
- human-confirmed bulk imports. Read the README for exact entry points and injected dependencies.
5
- Do not add React or Next dependencies to list-view, collaboration, import-export or background-jobs.
6
-
7
- ## HTTP contracts
8
-
9
- - List metadata: GET `/list-view/machines/:code/fields`; query: POST the same machine's `/query`.
10
- - Views: GET/POST `/list-view/views`; PATCH `/list-view/views/:id/sharing`. Preserve `canShare`.
11
- - Details: GET `/state-machine-instances/:id` uses the pinned subject Form bundle.
12
- - Timeline: GET `/state-machine-instances/:id/timeline?offset=&limit=50`, including `following`.
13
- - People: GET `/state-machine-instances/:id/mentionable?q=`, at most twenty eligible people.
14
- - Collaboration: POST `comments` with `{ body }`, using `@{id}` from the people picker;
15
- POST/DELETE `follow`. There are no comment edit/delete controls.
16
- - Imports: GET `/import-export/machines/:code/fields`; POST `/import-export/imports/preflight`
17
- with file format/base64, header-to-key mapping, and optional duplicateKey (one to five keys).
18
- - Human confirmation: POST `/import-export/imports/:id/confirm` with `{ confirmed: true }` only
19
- from an explicit button event, never from polling, render, an effect, or an AI tool.
20
- - Exports: POST `/import-export/exports` with `{ format, source: { machineCode, query } }`.
21
- - Jobs: GET `/background-jobs/:id`; downloads use the result's exact `downloadPath` through
22
- an App-owned authenticated streaming proxy. The API alone decides job ownership and expiry.
23
-
24
- The minimal types are local HTTP contracts; never import backend runtime packages for types.
25
- Keep the API adapter and authentication in the App, with Server Action CSRF protection.
26
- Keep API error values and all user-controlled content as plain text. Do not inspect role names
27
- to decide sharing or visibility. Use both English and zh-TW translations for new UI text.
28
-
29
- ## Validation
30
-
31
- Run package build/typecheck/tests and workspace build:graph, lint, specs:lint. Component tests
32
- render actual HTML, test authorization and escaping, and verify confirmation and parameters.
33
- Use the equipment-loan validation overlay for full production-build/browser acceptance. Always
34
- restore and finish it, including original manifest/lockfile bytes and dependency installation.
35
- Do not release, version, approve a decision, or commit as part of frontend validation.
36
-
37
- ## Submitter display
38
-
39
- List `$submitterId` cells and the detail submitter field display `submitterName ?? submitterId`. Both are plain React text nodes, including markup-like names. The HTTP contracts include nullable submitterName; older responses without it also fall back to the id. No new translation key, lookup request or App host-page change is required. Upgrade the state-machine and list-view backends with this frontend for names; otherwise the id remains visible.
1
+ # Instance frontend agent guide
2
+
3
+ Use this package to mount shared instance lists, read-only details with collaboration, and
4
+ human-confirmed bulk imports. Read the README for exact entry points and injected dependencies.
5
+ Do not add React or Next dependencies to list-view, collaboration, import-export or background-jobs.
6
+
7
+ ## HTTP contracts
8
+
9
+ - List metadata: GET `/list-view/machines/:code/fields`; query: POST the same machine's `/query`.
10
+ - Views: GET/POST `/list-view/views`; PATCH `/list-view/views/:id/sharing`. Preserve `canShare`.
11
+ - Details: GET `/state-machine-instances/:id` uses the pinned subject Form bundle.
12
+ - Timeline: GET `/state-machine-instances/:id/timeline?offset=&limit=50`, including `following`.
13
+ - People: GET `/state-machine-instances/:id/mentionable?q=`, at most twenty eligible people.
14
+ - Collaboration: POST `comments` with `{ body }`, using `@{id}` from the people picker;
15
+ POST/DELETE `follow`. There are no comment edit/delete controls.
16
+ - Imports: GET `/import-export/machines/:code/fields`; POST `/import-export/imports/preflight`
17
+ with file format/base64, header-to-key mapping, and optional duplicateKey (one to five keys).
18
+ - Human confirmation: POST `/import-export/imports/:id/confirm` with `{ confirmed: true }` only
19
+ from an explicit button event, never from polling, render, an effect, or an AI tool.
20
+ - Exports: POST `/import-export/exports` with `{ format, source: { machineCode, query } }`.
21
+ - Jobs: GET `/background-jobs/:id`; downloads use the result's exact `downloadPath` through
22
+ an App-owned authenticated streaming proxy. The API alone decides job ownership and expiry.
23
+
24
+ The minimal types are local HTTP contracts; never import backend runtime packages for types.
25
+ Keep the API adapter and authentication in the App, with Server Action CSRF protection.
26
+ Keep API error values and all user-controlled content as plain text. Do not inspect role names
27
+ to decide sharing or visibility. Use both English and zh-TW translations for new UI text.
28
+
29
+ ## Validation
30
+
31
+ Run package build/typecheck/tests and workspace build:graph, lint, specs:lint. Component tests
32
+ render actual HTML, test authorization and escaping, and verify confirmation and parameters.
33
+ Use the equipment-loan validation overlay for full production-build/browser acceptance. Always
34
+ restore and finish it, including original manifest/lockfile bytes and dependency installation.
35
+ Do not release, version, approve a decision, or commit as part of frontend validation.
36
+
37
+ ## Submitter display
38
+
39
+ List `$submitterId` cells and the detail submitter field display `submitterName ?? submitterId`. Both are plain React text nodes, including markup-like names. The HTTP contracts include nullable submitterName; older responses without it also fall back to the id. No new translation key, lookup request or App host-page change is required. Upgrade the state-machine and list-view backends with this frontend for names; otherwise the id remains visible.
40
+
41
+
42
+ ## Delegations, access lists and safe errors
43
+
44
+ Implemented against draft 202610-e2a9; approval/versioning remain with the maintainer.
45
+ Use renderDelegationsPage and showAccess on renderInstanceDetailPage as documented in README.
46
+ Lookup routes are GET /identity/users/lookup?q= (two trimmed characters) and
47
+ GET /rbac/roles/lookup?q= (empty allowed), at most twenty projected public labels.
48
+ GET instance access and the read-only list_instance_access MCP result now include canManage.
49
+ Never infer authority from role names or HTTP status. Admin delegation visibility comes from
50
+ GET /delegations; only DELEGATION_NOT_FOUND means this operation is unavailable to the caller.
51
+ Use a same-origin authenticated Server Action for writes. Permanent sources have no controls.
52
+ Translate only exact known codes in src/messages.ts; never show backend prose or job lastError.
53
+ Both current pages and the new management pages follow this policy. Merge the complete English
54
+ and zh-TW fragments with the existing host catalogs before building an overlay.
@@ -0,0 +1,16 @@
1
+ import type { ActionError, InstanceAction, InstanceRequest, Labels } from './contracts.js';
2
+ export declare class RequestFailure extends Error {
3
+ readonly reason: ActionError;
4
+ constructor(reason: ActionError);
5
+ }
6
+ export declare function call<T>(action: InstanceAction, request: InstanceRequest): Promise<T>;
7
+ export declare function useTask(): {
8
+ pending: boolean;
9
+ error: ActionError | undefined;
10
+ run: (work: () => Promise<void>) => Promise<void>;
11
+ };
12
+ export declare function ErrorMessage({ error, labels }: {
13
+ error?: ActionError;
14
+ labels: Labels;
15
+ }): import("react").JSX.Element | null;
16
+ //# sourceMappingURL=action-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-client.d.ts","sourceRoot":"","sources":["../src/action-client.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC3F,qBAAa,cAAe,SAAQ,KAAK;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW;gBAAnB,MAAM,EAAE,WAAW;CAGzC;AACD,wBAAsB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,CAI1F;AACD,wBAAgB,OAAO;;;gBAII,MAAM,OAAO,CAAC,IAAI,CAAC;EAe7C;AACD,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,sCAMtF"}
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useRef, useState } from 'react';
4
+ export class RequestFailure extends Error {
5
+ constructor(reason) {
6
+ super(reason.key);
7
+ this.reason = reason;
8
+ }
9
+ }
10
+ export async function call(action, request) {
11
+ const result = await action(request);
12
+ if (result.error)
13
+ throw new RequestFailure(result.error);
14
+ return result.data;
15
+ }
16
+ export function useTask() {
17
+ const lock = useRef(false);
18
+ const [pending, setPending] = useState(false);
19
+ const [error, setError] = useState();
20
+ async function run(work) {
21
+ if (lock.current)
22
+ return;
23
+ lock.current = true;
24
+ setPending(true);
25
+ setError(undefined);
26
+ try {
27
+ await work();
28
+ }
29
+ catch (error) {
30
+ setError(error instanceof RequestFailure ? error.reason : { key: 'requestFailed' });
31
+ }
32
+ finally {
33
+ lock.current = false;
34
+ setPending(false);
35
+ }
36
+ }
37
+ return { pending, error, run };
38
+ }
39
+ export function ErrorMessage({ error, labels }) {
40
+ return error ? (_jsx("p", { role: "alert", className: "break-words text-destructive", children: labels[error.key] })) : null;
41
+ }
42
+ //# sourceMappingURL=action-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-client.js","sourceRoot":"","sources":["../src/action-client.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AACb,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEzC,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAqB,MAAmB;QACtC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QADC,WAAM,GAAN,MAAM,CAAa;IAExC,CAAC;CACF;AACD,MAAM,CAAC,KAAK,UAAU,IAAI,CAAI,MAAsB,EAAE,OAAwB;IAC5E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,MAAM,CAAC,KAAK;QAAE,MAAM,IAAI,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACzD,OAAO,MAAM,CAAC,IAAS,CAAC;AAC1B,CAAC;AACD,MAAM,UAAU,OAAO;IACrB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,EAAe,CAAC;IAClD,KAAK,UAAU,GAAG,CAAC,IAAyB;QAC1C,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,UAAU,CAAC,IAAI,CAAC,CAAC;QACjB,QAAQ,CAAC,SAAS,CAAC,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,IAAI,EAAE,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,KAAK,YAAY,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,CAAC,CAAC;QACtF,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,UAAU,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAA2C;IACrF,OAAO,KAAK,CAAC,CAAC,CAAC,CACb,YAAG,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,8BAA8B,YACrD,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAChB,CACL,CAAC,CAAC,CAAC,IAAI,CAAC;AACX,CAAC"}
package/dist/client.d.ts CHANGED
@@ -1,4 +1,7 @@
1
+ import type { AccessList } from './contracts.js';
1
2
  import { type Condition, type FormField, type ImportField, type InstanceAction, type InstanceQuery, type Job, type JobResult, type Labels, type ListResult, type QueryField, type SavedView, type Timeline, type UploadAction } from './contracts.js';
3
+ export { call, ErrorMessage, useTask } from './action-client.js';
4
+ export { InstanceAccessPanel } from './management-client.js';
2
5
  export declare function FilterEditor({ condition, fields, labels, onChange, depth, }: {
3
6
  condition: Condition;
4
7
  fields: QueryField[];
@@ -18,7 +21,7 @@ export declare function ListClient({ fields, initial, initialQuery, views: initi
18
21
  labels: Labels;
19
22
  action: InstanceAction;
20
23
  }): import("react").JSX.Element;
21
- export declare function DetailClient({ id, data, fields, state, submitterId, submitterName, createdAt, initial, initialNumbers, labels: t, action, }: {
24
+ export declare function DetailClient({ id, data, fields, state, submitterId, submitterName, createdAt, initial, initialNumbers, initialAccess, labels: t, action, }: {
22
25
  id: string;
23
26
  data: Record<string, unknown>;
24
27
  fields: FormField[];
@@ -28,6 +31,7 @@ export declare function DetailClient({ id, data, fields, state, submitterId, sub
28
31
  createdAt: string;
29
32
  initial: Timeline;
30
33
  initialNumbers?: string[];
34
+ initialAccess?: AccessList;
31
35
  labels: Labels;
32
36
  action: InstanceAction;
33
37
  }): import("react").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -1,56 +1,18 @@
1
1
  'use client';
2
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { Button, Input, ListPagination } from '@haneinfo/frontend-shell';
4
4
  import { useEffect, useId, useRef, useState } from 'react';
5
+ import { call, ErrorMessage, RequestFailure, useTask } from './action-client.js';
5
6
  import { operators, } from './contracts.js';
7
+ import { InstanceAccessPanel } from './management-client.js';
8
+ import { errorKey } from './messages.js';
9
+ export { call, ErrorMessage, useTask } from './action-client.js';
6
10
  import { canPoll, dateRange, fieldValue, importMapping, listHref, textValue } from './query.js';
11
+ export { InstanceAccessPanel } from './management-client.js';
7
12
  const box = 'rounded-md border bg-card p-4';
8
13
  const selectClass = 'min-w-0 rounded-md border bg-background px-3 py-2 text-foreground';
9
- class RequestFailure extends Error {
10
- constructor(reason) {
11
- super(reason.key);
12
- this.reason = reason;
13
- }
14
- }
15
- async function call(action, request) {
16
- const result = await action(request);
17
- if (result.error)
18
- throw new RequestFailure(result.error);
19
- return result.data;
20
- }
21
- function useTask() {
22
- const lock = useRef(false);
23
- const [pending, setPending] = useState(false);
24
- const [error, setError] = useState();
25
- async function run(work) {
26
- if (lock.current)
27
- return;
28
- lock.current = true;
29
- setPending(true);
30
- setError(undefined);
31
- try {
32
- await work();
33
- }
34
- catch (error) {
35
- setError(error instanceof RequestFailure
36
- ? error.reason
37
- : {
38
- key: error instanceof Error && error.message === 'invalidInput'
39
- ? 'invalidInput'
40
- : 'requestFailed',
41
- });
42
- }
43
- finally {
44
- lock.current = false;
45
- setPending(false);
46
- }
47
- }
48
- return { pending, error, run };
49
- }
50
- function ErrorMessage({ error, labels }) {
51
- return error ? (_jsxs("p", { role: "alert", className: "break-words text-destructive", children: [labels[error.key], error.message && `: ${error.message}`] })) : null;
52
- }
53
- const inaccessible = (error) => error && ['unauthorized', 'forbidden', 'notFound'].includes(error.key);
14
+ const inaccessible = (error) => error &&
15
+ ['unauthorized', 'forbidden', 'notFound', 'FORBIDDEN', 'INSTANCE_NOT_FOUND'].includes(error.key);
54
16
  function labelOf(field, labels) {
55
17
  const system = {
56
18
  $state: 'state',
@@ -264,7 +226,7 @@ function DataFields({ fields, data }) {
264
226
  .filter((f) => (f.id ?? f.key) in data)
265
227
  .map((field) => (_jsxs("div", { className: "min-w-0", children: [_jsx("dt", { className: "text-sm text-muted-foreground", children: field.label }), _jsx("dd", { className: "whitespace-pre-wrap break-words", children: textValue(data[field.id ?? field.key]) })] }, field.id ?? field.key))) }));
266
228
  }
267
- export function DetailClient({ id, data, fields, state, submitterId, submitterName, createdAt, initial, initialNumbers, labels: t, action, }) {
229
+ export function DetailClient({ id, data, fields, state, submitterId, submitterName, createdAt, initial, initialNumbers, initialAccess, labels: t, action, }) {
268
230
  const task = useTask();
269
231
  const [timeline, setTimeline] = useState(initial), [body, setBody] = useState('');
270
232
  const [q, setQ] = useState(''), [people, setPeople] = useState(), [selected, setSelected] = useState('');
@@ -285,7 +247,7 @@ export function DetailClient({ id, data, fields, state, submitterId, submitterNa
285
247
  : next);
286
248
  };
287
249
  const numbers = timeline.items.filter((e) => e.kind === 'NUMBER');
288
- return (_jsxs("div", { className: "flex min-w-0 flex-col gap-4", "aria-busy": task.pending, children: [_jsx(ErrorMessage, { error: task.error, labels: t }), task.pending && _jsx("p", { role: "status", children: t.loading }), _jsxs("section", { className: `${box} flex flex-col gap-4`, children: [_jsxs("dl", { className: "grid gap-3 sm:grid-cols-2", children: [_jsxs("div", { children: [_jsx("dt", { children: t.state }), _jsx("dd", { children: state })] }), _jsxs("div", { children: [_jsx("dt", { children: t.number }), _jsx("dd", { children: initialNumbers?.length
250
+ return (_jsxs("div", { className: "flex min-w-0 flex-col gap-4", "aria-busy": task.pending, children: [_jsx(ErrorMessage, { error: task.error, labels: t }), task.pending && _jsx("p", { role: "status", children: t.loading }), initialAccess && (_jsx(InstanceAccessPanel, { id: id, initial: initialAccess, labels: t, action: action, onChanged: () => loadTimeline(false) })), _jsxs("section", { className: `${box} flex flex-col gap-4`, children: [_jsxs("dl", { className: "grid gap-3 sm:grid-cols-2", children: [_jsxs("div", { children: [_jsx("dt", { children: t.state }), _jsx("dd", { children: state })] }), _jsxs("div", { children: [_jsx("dt", { children: t.number }), _jsx("dd", { children: initialNumbers?.length
289
251
  ? initialNumbers.join(', ')
290
252
  : numbers.length
291
253
  ? numbers.map((e) => textValue(e.details.number)).join(', ')
@@ -368,7 +330,7 @@ export function JobStatus({ initial, labels: t, action, downloadBase, onComplete
368
330
  }, [initial, action, attempt]);
369
331
  if (inaccessible(error))
370
332
  return _jsx(ErrorMessage, { error: error, labels: t });
371
- return (_jsxs("section", { className: `${box} flex flex-col gap-2`, "aria-label": t.job, children: [_jsxs("p", { role: "status", children: [t.job, ": ", t[job.status], " (", Math.max(0, Math.min(100, job.progress)), "%)"] }), _jsx("progress", { "aria-label": t.job, value: Math.max(0, Math.min(100, job.progress)), max: 100, className: "w-full" }), _jsx(ErrorMessage, { error: error, labels: t }), job.lastError && (_jsx("p", { role: "alert", className: "break-words text-destructive", children: job.lastError })), stopped && (_jsxs("div", { children: [_jsx("p", { children: t.pollStopped }), _jsx(Button, { type: "button", variant: "outline", onClick: () => setAttempt(attempt + 1), children: t.retry })] })), job.status === 'SUCCEEDED' && job.result && (_jsx(JobReport, { result: job.result, labels: t, downloadBase: downloadBase }))] }));
333
+ return (_jsxs("section", { className: `${box} flex flex-col gap-2`, "aria-label": t.job, children: [_jsxs("p", { role: "status", children: [t.job, ": ", t[job.status], " (", Math.max(0, Math.min(100, job.progress)), "%)"] }), _jsx("progress", { "aria-label": t.job, value: Math.max(0, Math.min(100, job.progress)), max: 100, className: "w-full" }), _jsx(ErrorMessage, { error: error, labels: t }), job.lastError && (_jsx("p", { role: "alert", className: "break-words text-destructive", children: t.requestFailed })), stopped && (_jsxs("div", { children: [_jsx("p", { children: t.pollStopped }), _jsx(Button, { type: "button", variant: "outline", onClick: () => setAttempt(attempt + 1), children: t.retry })] })), job.status === 'SUCCEEDED' && job.result && (_jsx(JobReport, { result: job.result, labels: t, downloadBase: downloadBase }))] }));
372
334
  }
373
335
  export function JobReport({ result, labels: t, downloadBase, }) {
374
336
  const failures = result.kind === 'preflight'
@@ -376,7 +338,9 @@ export function JobReport({ result, labels: t, downloadBase, }) {
376
338
  : Array.isArray(result.failed)
377
339
  ? result.failed
378
340
  : [];
379
- return (_jsxs("div", { className: "flex min-w-0 flex-col gap-3", children: [_jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsxs("span", { children: [t.total, ": ", result.total ?? result.rowCount ?? 0] }), result.kind === 'preflight' && (_jsxs("span", { children: [t.valid, ": ", result.valid ?? 0] })), result.kind === 'import' && (_jsxs("span", { children: [t.success, ": ", result.imported?.length ?? 0] })), result.kind !== 'export' && (_jsxs("span", { children: [t.failed, ": ", Array.isArray(result.failed) ? result.failed.length : (result.failed ?? 0)] }))] }), (result.errors ?? []).map((error) => (_jsxs("p", { className: "break-words text-destructive", children: [error.code, ": ", error.message] }, `${error.code}-${error.path}-${error.message}`))), !!failures.length && (_jsx("div", { className: "max-h-96 overflow-auto", children: _jsxs("table", { className: "w-full text-left text-sm", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: t.row }), _jsx("th", { children: t.value }), _jsx("th", { children: t.code }), _jsx("th", { children: t.reason })] }) }), _jsx("tbody", { children: failures.map((row) => (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "p-2", children: row.rowNumber }), _jsx("td", { className: "max-w-64 break-words p-2", children: row.data ? textValue(row.data) : '—' }), _jsx("td", { className: "p-2", children: row.errors.map((e) => e.code).join(', ') }), _jsx("td", { className: "max-w-64 break-words p-2", children: row.errors.map((e) => e.message).join('; ') })] }, row.rowNumber))) })] }) })), result.downloadPath && (_jsx("a", { className: "underline", href: `${downloadBase}?${new URLSearchParams({ path: result.downloadPath })}`, children: result.kind === 'export' ? t.download : t.downloadErrors }))] }));
341
+ return (_jsxs("div", { className: "flex min-w-0 flex-col gap-3", children: [_jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsxs("span", { children: [t.total, ": ", result.total ?? result.rowCount ?? 0] }), result.kind === 'preflight' && (_jsxs("span", { children: [t.valid, ": ", result.valid ?? 0] })), result.kind === 'import' && (_jsxs("span", { children: [t.success, ": ", result.imported?.length ?? 0] })), result.kind !== 'export' && (_jsxs("span", { children: [t.failed, ": ", Array.isArray(result.failed) ? result.failed.length : (result.failed ?? 0)] }))] }), (result.errors ?? []).map((error) => (_jsx("p", { className: "break-words text-destructive", children: t[errorKey(error.code)] }, `${error.code}-${error.path}-${error.message}`))), !!failures.length && (_jsx("div", { className: "max-h-96 overflow-auto", children: _jsxs("table", { className: "w-full text-left text-sm", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: t.row }), _jsx("th", { children: t.value }), _jsx("th", { children: t.code }), _jsx("th", { children: t.reason })] }) }), _jsx("tbody", { children: failures.map((row) => (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "p-2", children: row.rowNumber }), _jsx("td", { className: "max-w-64 break-words p-2", children: row.data ? textValue(row.data) : '—' }), _jsx("td", { className: "p-2", children: row.errors
342
+ .map((e) => (errorKey(e.code) === 'requestFailed' ? '—' : e.code))
343
+ .join(', ') }), _jsx("td", { className: "max-w-64 break-words p-2", children: row.errors.map((e) => t[errorKey(e.code)]).join('; ') })] }, row.rowNumber))) })] }) })), result.downloadPath && (_jsx("a", { className: "underline", href: `${downloadBase}?${new URLSearchParams({ path: result.downloadPath })}`, children: result.kind === 'export' ? t.download : t.downloadErrors }))] }));
380
344
  }
381
345
  export function ImportClient({ machines, fields: initialFields, labels: t, action, uploadAction, downloadBase, }) {
382
346
  const task = useTask();