@burdenoff/microfe-buildmyiq 2026.1031.1 → 2026.1031.2
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/dist/buildmyiq/hooks/invalidateAfterWrite.d.ts +3 -0
- package/dist/buildmyiq/hooks/invalidateAfterWrite.d.ts.map +1 -0
- package/dist/buildmyiq/hooks/invalidateAfterWrite.js +11 -0
- package/dist/buildmyiq/hooks/invalidateAfterWrite.js.map +1 -0
- package/dist/buildmyiq/hooks/useBuiComments.d.ts.map +1 -1
- package/dist/buildmyiq/hooks/useBuiComments.js +24 -23
- package/dist/buildmyiq/hooks/useBuiComments.js.map +1 -1
- package/dist/buildmyiq/hooks/useBuiProctoring.d.ts.map +1 -1
- package/dist/buildmyiq/hooks/useBuiProctoring.js +67 -66
- package/dist/buildmyiq/hooks/useBuiProctoring.js.map +1 -1
- package/dist/buildmyiq/hooks/useBuiTags.d.ts.map +1 -1
- package/dist/buildmyiq/hooks/useBuiTags.js +52 -51
- package/dist/buildmyiq/hooks/useBuiTags.js.map +1 -1
- package/dist/buildmyiq/hooks/useBuildmyiqApi.d.ts.map +1 -1
- package/dist/buildmyiq/hooks/useBuildmyiqApi.js +487 -486
- package/dist/buildmyiq/hooks/useBuildmyiqApi.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invalidateAfterWrite.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/invalidateAfterWrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEnE,eAAO,MAAM,oBAAoB,GAC/B,aAAa,WAAW,EACxB,UAAU,QAAQ,KACjB,OAAO,CAAC,IAAI,CAMd,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/buildmyiq/hooks/invalidateAfterWrite.ts
|
|
2
|
+
var e = async (e, t) => {
|
|
3
|
+
await e.cancelQueries({
|
|
4
|
+
queryKey: t,
|
|
5
|
+
predicate: (e) => e.state.fetchStatus === "fetching" && e.state.data === void 0
|
|
6
|
+
}), await e.invalidateQueries({ queryKey: t });
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { e as invalidateAfterWrite };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=invalidateAfterWrite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invalidateAfterWrite.js","names":[],"sources":["../../../src/buildmyiq/hooks/invalidateAfterWrite.ts"],"sourcesContent":["/**\n * Refresh the React Query cache after a BuildMyIQ write.\n *\n * `queryClient.invalidateQueries` alone is not enough when a query matching the\n * key is still on its FIRST load. React Query only cancels and restarts an\n * in-flight fetch when the query already holds data; a query with no data yet\n * has the refetch joined onto the request that is already on the wire. That\n * request was sent before the write committed, so it resolves with the\n * pre-write result and no second read follows. In practice: open a course's\n * Curriculum tab and add a module before the empty module list has loaded, and\n * the tab keeps showing \"No modules yet\" even though the module exists.\n *\n * So first cancel any matching query that is still loading without data (React\n * Query reverts it to its pre-fetch state, so nothing errors), then invalidate,\n * which issues a fresh read for every active query under the key.\n */\nimport type { QueryClient, QueryKey } from '@tanstack/react-query';\n\nexport const invalidateAfterWrite = async (\n queryClient: QueryClient,\n queryKey: QueryKey\n): Promise<void> => {\n await queryClient.cancelQueries({\n queryKey,\n predicate: (query) => query.state.fetchStatus === 'fetching' && query.state.data === undefined,\n });\n await queryClient.invalidateQueries({ queryKey });\n};\n"],"mappings":";AAkBA,IAAa,IAAuB,OAClC,GACA,MACkB;CAKlB,AAJA,MAAM,EAAY,cAAc;EAC9B;EACA,YAAY,MAAU,EAAM,MAAM,gBAAgB,cAAc,EAAM,MAAM,SAAS,KAAA;CACvF,CAAC,GACD,MAAM,EAAY,kBAAkB,EAAE,YAAS,CAAC;AAClD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuiComments.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiComments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"useBuiComments.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiComments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,uEAAuE;AACvE,eAAO,MAAM,cAAc,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM,GAAG,SAAS;;;UAwC9E,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM;QAKN,MAAM;0BAmBvE,CAAC"}
|
|
@@ -1,45 +1,46 @@
|
|
|
1
1
|
import { useBuildmyiq as e } from "../context/BuildmyiqContext.js";
|
|
2
2
|
import "../context/index.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { invalidateAfterWrite as t } from "./invalidateAfterWrite.js";
|
|
4
|
+
import { wspaceFetch as n } from "./useBuiFiles.js";
|
|
5
|
+
import { useMutation as r, useQuery as i, useQueryClient as a } from "@tanstack/react-query";
|
|
5
6
|
//#region src/buildmyiq/hooks/useBuiComments.ts
|
|
6
|
-
var
|
|
7
|
+
var o = (e, t) => [
|
|
7
8
|
"buildmyiq",
|
|
8
9
|
"comments",
|
|
9
10
|
e,
|
|
10
11
|
t
|
|
11
|
-
],
|
|
12
|
-
let { apiGatewayUrl:
|
|
13
|
-
return
|
|
14
|
-
queryKey:
|
|
15
|
-
enabled: !!
|
|
12
|
+
], s = (t, r) => {
|
|
13
|
+
let { apiGatewayUrl: a } = e();
|
|
14
|
+
return i({
|
|
15
|
+
queryKey: o(t, r ?? ""),
|
|
16
|
+
enabled: !!r,
|
|
16
17
|
queryFn: async () => {
|
|
17
|
-
let e = await
|
|
18
|
-
entityType:
|
|
19
|
-
entityId:
|
|
18
|
+
let e = await n("query BuiGetEntityComments($input: GetCommentsInput!) {\n getEntityComments(input: $input) {\n total\n items {\n id\n authorId\n authorType\n content { text markdown }\n createdAt\n edited\n parentCommentId\n replyCount\n }\n }\n }", { input: {
|
|
19
|
+
entityType: t,
|
|
20
|
+
entityId: r,
|
|
20
21
|
take: 100,
|
|
21
22
|
parentCommentId: null
|
|
22
|
-
} },
|
|
23
|
-
return
|
|
24
|
-
items:
|
|
23
|
+
} }, a), i = e.getEntityComments.items.filter((e) => !e.parentCommentId);
|
|
24
|
+
return i.sort((e, t) => new Date(e.createdAt).getTime() - new Date(t.createdAt).getTime()), {
|
|
25
|
+
items: i,
|
|
25
26
|
total: e.getEntityComments.total
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
});
|
|
29
|
-
},
|
|
30
|
-
let { apiGatewayUrl:
|
|
31
|
-
return
|
|
32
|
-
mutationFn: async (e) => (await
|
|
33
|
-
entityType:
|
|
34
|
-
entityId:
|
|
30
|
+
}, c = (i, s) => {
|
|
31
|
+
let { apiGatewayUrl: c } = e(), l = a();
|
|
32
|
+
return r({
|
|
33
|
+
mutationFn: async (e) => (await n("mutation BuiCreateEntityComment($input: CreateEntityCommentInput!) {\n createEntityComment(input: $input) { id }\n }", { input: {
|
|
34
|
+
entityType: i,
|
|
35
|
+
entityId: s,
|
|
35
36
|
content: { text: e }
|
|
36
|
-
} },
|
|
37
|
+
} }, c)).createEntityComment,
|
|
37
38
|
onSuccess: () => {
|
|
38
|
-
|
|
39
|
+
t(l, o(i, s));
|
|
39
40
|
}
|
|
40
41
|
});
|
|
41
42
|
};
|
|
42
43
|
//#endregion
|
|
43
|
-
export {
|
|
44
|
+
export { s as useBuiComments, c as useEcoCreateComment };
|
|
44
45
|
|
|
45
46
|
//# sourceMappingURL=useBuiComments.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuiComments.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiComments.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-conversations-svc EntityComment REUSE hooks (Integration B).\n * @module buildmyiq/hooks/useBuiComments\n *\n * Adds a discussion surface to Buildmyiq entities WITHOUT any buildmyiq-svc change by\n * reusing the generic EntityComment API composed into the WORKSPACE supergraph.\n * Comments attach to any entity by `(entityType, entityId)`:\n * - `getEntityComments(input): EntityCommentConnection!`\n * - `createEntityComment(input): EntityComment!`\n *\n * Transport reuses `wspaceFetch` from `useBuiFiles` (workspace gateway +\n * workspace token). Lives in a separate file; `useBuildmyiqApi.ts` is\n * untouched.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { useBuildmyiq } from '../context';\nimport { wspaceFetch } from './useBuiFiles';\n\nexport interface BuiComment {\n id: string;\n authorId: string;\n authorType: string;\n content: {\n text: string | null;\n markdown: string | null;\n };\n createdAt: string;\n edited: boolean;\n parentCommentId: string | null;\n replyCount: number;\n}\n\nconst commentsKey = (entityType: string, entityId: string) =>\n ['buildmyiq', 'comments', entityType, entityId] as const;\n\n/** Top-level (root) comments for an Buildmyiq entity, oldest first. */\nexport const useBuiComments = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: commentsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{\n getEntityComments: { items: BuiComment[]; total: number };\n }>(\n `query BuiGetEntityComments($input: GetCommentsInput!) {\n getEntityComments(input: $input) {\n total\n items {\n id\n authorId\n authorType\n content { text markdown }\n createdAt\n edited\n parentCommentId\n replyCount\n }\n }\n }`,\n {\n input: {\n entityType,\n entityId,\n take: 100,\n parentCommentId: null,\n },\n },\n apiGatewayUrl\n );\n // Render root comments oldest-first (server order is not guaranteed).\n const roots = data.getEntityComments.items.filter((c) => !c.parentCommentId);\n roots.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n return { items: roots, total: data.getEntityComments.total };\n },\n });\n};\n\nexport const useEcoCreateComment = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (text: string) => {\n const data = await wspaceFetch<{ createEntityComment: { id: string } }>(\n `mutation BuiCreateEntityComment($input: CreateEntityCommentInput!) {\n createEntityComment(input: $input) { id }\n }`,\n {\n input: {\n entityType,\n entityId,\n content: { text },\n },\n },\n apiGatewayUrl\n );\n return data.createEntityComment;\n },\n onSuccess: () => {\n qc
|
|
1
|
+
{"version":3,"file":"useBuiComments.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiComments.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-conversations-svc EntityComment REUSE hooks (Integration B).\n * @module buildmyiq/hooks/useBuiComments\n *\n * Adds a discussion surface to Buildmyiq entities WITHOUT any buildmyiq-svc change by\n * reusing the generic EntityComment API composed into the WORKSPACE supergraph.\n * Comments attach to any entity by `(entityType, entityId)`:\n * - `getEntityComments(input): EntityCommentConnection!`\n * - `createEntityComment(input): EntityComment!`\n *\n * Transport reuses `wspaceFetch` from `useBuiFiles` (workspace gateway +\n * workspace token). Lives in a separate file; `useBuildmyiqApi.ts` is\n * untouched.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { invalidateAfterWrite } from './invalidateAfterWrite';\n\nimport { useBuildmyiq } from '../context';\nimport { wspaceFetch } from './useBuiFiles';\n\nexport interface BuiComment {\n id: string;\n authorId: string;\n authorType: string;\n content: {\n text: string | null;\n markdown: string | null;\n };\n createdAt: string;\n edited: boolean;\n parentCommentId: string | null;\n replyCount: number;\n}\n\nconst commentsKey = (entityType: string, entityId: string) =>\n ['buildmyiq', 'comments', entityType, entityId] as const;\n\n/** Top-level (root) comments for an Buildmyiq entity, oldest first. */\nexport const useBuiComments = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: commentsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{\n getEntityComments: { items: BuiComment[]; total: number };\n }>(\n `query BuiGetEntityComments($input: GetCommentsInput!) {\n getEntityComments(input: $input) {\n total\n items {\n id\n authorId\n authorType\n content { text markdown }\n createdAt\n edited\n parentCommentId\n replyCount\n }\n }\n }`,\n {\n input: {\n entityType,\n entityId,\n take: 100,\n parentCommentId: null,\n },\n },\n apiGatewayUrl\n );\n // Render root comments oldest-first (server order is not guaranteed).\n const roots = data.getEntityComments.items.filter((c) => !c.parentCommentId);\n roots.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n return { items: roots, total: data.getEntityComments.total };\n },\n });\n};\n\nexport const useEcoCreateComment = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (text: string) => {\n const data = await wspaceFetch<{ createEntityComment: { id: string } }>(\n `mutation BuiCreateEntityComment($input: CreateEntityCommentInput!) {\n createEntityComment(input: $input) { id }\n }`,\n {\n input: {\n entityType,\n entityId,\n content: { text },\n },\n },\n apiGatewayUrl\n );\n return data.createEntityComment;\n },\n onSuccess: () => {\n invalidateAfterWrite(qc, commentsKey(entityType, entityId));\n },\n });\n};\n"],"mappings":";;;;;;AAmCA,IAAM,KAAe,GAAoB,MACvC;CAAC;CAAa;CAAY;CAAY;AAAQ,GAGnC,KAAkB,GAAoB,MAAiC;CAClF,IAAM,EAAE,qBAAkB,EAAa;CACvC,OAAO,EAAS;EACd,UAAU,EAAY,GAAY,KAAY,EAAE;EAChD,SAAS,EAAQ;EACjB,SAAS,YAAY;GACnB,IAAM,IAAO,MAAM,EAGjB,6YAeA,EACE,OAAO;IACL;IACA;IACA,MAAM;IACN,iBAAiB;GACnB,EACF,GACA,CACF,GAEM,IAAQ,EAAK,kBAAkB,MAAM,QAAQ,MAAM,CAAC,EAAE,eAAe;GAE3E,OADA,EAAM,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,GAC/E;IAAE,OAAO;IAAO,OAAO,EAAK,kBAAkB;GAAM;EAC7D;CACF,CAAC;AACH,GAEa,KAAuB,GAAoB,MAAqB;CAC3E,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAcV,MAbY,EACjB,wIAGA,EACE,OAAO;GACL;GACA;GACA,SAAS,EAAE,QAAK;EAClB,EACF,GACA,CACF,GACY;EAEd,iBAAiB;GACf,EAAqB,GAAI,EAAY,GAAY,CAAQ,CAAC;EAC5D;CACF,CAAC;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuiProctoring.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiProctoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"useBuiProctoring.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiProctoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,OAAO,KAAK,EACV,aAAa,EACb,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,+BAA+B,EAC/B,gCAAgC,EACjC,MAAM,UAAU,CAAC;AAkClB;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,cAAc,MAAM,GAAG,SAAS,EAAE,iBAAc,gGAwBzF,CAAC;AAEF,eAAO,MAAM,4BAA4B,uGAkBxC,CAAC;AAEF,eAAO,MAAM,8BAA8B;QAIR,MAAM;WAAS,gCAAgC;WAejF,CAAC;AAEF,6FAA6F;AAC7F,eAAO,MAAM,+BAA+B,uGAkB3C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B;QAIP,MAAM;aAAW,wBAAwB,EAAE;WAc7E,CAAC;AAEF,yFAAyF;AACzF,eAAO,MAAM,8BAA8B,GAAI,WAAW,MAAM,GAAG,SAAS,EAAE,iBAAc,4JA2B3F,CAAC;AAEF,2FAA2F;AAC3F,eAAO,MAAM,wBAAwB,GACnC,MAAM;IAAE,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC7E,iBAAc,wGAuBf,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GAAI,WAAW,MAAM,GAAG,SAAS,EAAE,iBAAc,qGAuBzF,CAAC;AAEF,eAAO,MAAM,6BAA6B;QAIP,MAAM;WAAS,+BAA+B;WAYhF,CAAC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useBuildmyiq as e } from "../context/BuildmyiqContext.js";
|
|
2
2
|
import "../context/index.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { invalidateAfterWrite as t } from "./invalidateAfterWrite.js";
|
|
4
|
+
import { wspaceFetch as n } from "./useBuiFiles.js";
|
|
5
|
+
import { useInfiniteQuery as r, useMutation as i, useQuery as a, useQueryClient as o } from "@tanstack/react-query";
|
|
5
6
|
//#region src/buildmyiq/hooks/useBuiProctoring.ts
|
|
6
|
-
var
|
|
7
|
+
var s = "\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt violationCount\n aiMonitoringConsentAt signalCount reviewedById reviewedAt reviewNotes expiresAt createdAt updatedAt\n identityFile { id }\n", c = {
|
|
7
8
|
mine: (e) => [
|
|
8
9
|
"buildmyiq",
|
|
9
10
|
"proctoring",
|
|
@@ -27,121 +28,121 @@ var o = "\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt
|
|
|
27
28
|
"signals",
|
|
28
29
|
e
|
|
29
30
|
]
|
|
30
|
-
},
|
|
31
|
-
let { apiGatewayUrl:
|
|
32
|
-
return
|
|
33
|
-
queryKey:
|
|
31
|
+
}, l = 4 * 6e4, u = "\n id sessionId type startedAt durationMs confidence faceCount peakLevelDb createdAt\n", d = 15e3, f = (t, r = !0) => {
|
|
32
|
+
let { apiGatewayUrl: i } = e();
|
|
33
|
+
return a({
|
|
34
|
+
queryKey: t ? c.mine(t) : [
|
|
34
35
|
"buildmyiq",
|
|
35
36
|
"proctoring",
|
|
36
37
|
"mine",
|
|
37
38
|
"nil"
|
|
38
39
|
],
|
|
39
|
-
enabled: !!
|
|
40
|
-
queryFn: async () => (await
|
|
41
|
-
buiMyProctoringSession(assessmentId: $assessmentId) { ${
|
|
42
|
-
}`, { assessmentId:
|
|
40
|
+
enabled: !!t && r,
|
|
41
|
+
queryFn: async () => (await n(`query BuiMyProctoringSession($assessmentId: ID!) {
|
|
42
|
+
buiMyProctoringSession(assessmentId: $assessmentId) { ${s} }
|
|
43
|
+
}`, { assessmentId: t }, i)).buiMyProctoringSession,
|
|
43
44
|
refetchInterval: (e) => {
|
|
44
45
|
let t = e.state.data?.status;
|
|
45
|
-
return t === "UNDER_REVIEW" || t === "IDENTITY_SUBMITTED" ?
|
|
46
|
+
return t === "UNDER_REVIEW" || t === "IDENTITY_SUBMITTED" ? d : !1;
|
|
46
47
|
}
|
|
47
48
|
});
|
|
48
|
-
},
|
|
49
|
-
let { apiGatewayUrl:
|
|
50
|
-
return
|
|
51
|
-
mutationFn: async (e) => (await
|
|
52
|
-
buiStartProctoringSession(assessmentId: $assessmentId) { ${
|
|
53
|
-
}`, { assessmentId: e },
|
|
49
|
+
}, p = () => {
|
|
50
|
+
let { apiGatewayUrl: t } = e(), r = o();
|
|
51
|
+
return i({
|
|
52
|
+
mutationFn: async (e) => (await n(`mutation BuiStartProctoringSession($assessmentId: ID!) {
|
|
53
|
+
buiStartProctoringSession(assessmentId: $assessmentId) { ${s} }
|
|
54
|
+
}`, { assessmentId: e }, t)).buiStartProctoringSession,
|
|
54
55
|
onSuccess: (e) => {
|
|
55
|
-
|
|
56
|
+
r.setQueryData(c.mine(e.assessmentId), e);
|
|
56
57
|
}
|
|
57
58
|
});
|
|
58
|
-
},
|
|
59
|
-
let { apiGatewayUrl:
|
|
60
|
-
return
|
|
61
|
-
mutationFn: async (e) => (await
|
|
62
|
-
buiSubmitProctoringIdentity(id: $id, input: $input) { ${
|
|
63
|
-
}`, e,
|
|
59
|
+
}, m = () => {
|
|
60
|
+
let { apiGatewayUrl: r } = e(), a = o();
|
|
61
|
+
return i({
|
|
62
|
+
mutationFn: async (e) => (await n(`mutation BuiSubmitProctoringIdentity($id: ID!, $input: BuiSubmitProctoringIdentityInput!) {
|
|
63
|
+
buiSubmitProctoringIdentity(id: $id, input: $input) { ${s} }
|
|
64
|
+
}`, e, r)).buiSubmitProctoringIdentity,
|
|
64
65
|
onSuccess: (e) => {
|
|
65
|
-
|
|
66
|
+
a.setQueryData(c.mine(e.assessmentId), e), t(a, c.queue);
|
|
66
67
|
}
|
|
67
68
|
});
|
|
68
|
-
},
|
|
69
|
-
let { apiGatewayUrl:
|
|
70
|
-
return
|
|
71
|
-
mutationFn: async (e) => (await
|
|
72
|
-
buiRecordProctoringSignals(id: $id, signals: $signals) { ${
|
|
73
|
-
}`, e,
|
|
69
|
+
}, h = () => {
|
|
70
|
+
let { apiGatewayUrl: t } = e(), r = o();
|
|
71
|
+
return i({
|
|
72
|
+
mutationFn: async (e) => (await n(`mutation BuiRecordProctoringSignals($id: ID!, $signals: [BuiProctoringSignalInput!]!) {
|
|
73
|
+
buiRecordProctoringSignals(id: $id, signals: $signals) { ${s} }
|
|
74
|
+
}`, e, t)).buiRecordProctoringSignals,
|
|
74
75
|
onSuccess: (e) => {
|
|
75
|
-
|
|
76
|
+
r.setQueryData(c.mine(e.assessmentId), e);
|
|
76
77
|
}
|
|
77
78
|
});
|
|
78
|
-
},
|
|
79
|
+
}, g = (t, i = !0) => {
|
|
79
80
|
let { apiGatewayUrl: a } = e();
|
|
80
|
-
return
|
|
81
|
-
queryKey:
|
|
81
|
+
return r({
|
|
82
|
+
queryKey: t ? c.signals(t) : [
|
|
82
83
|
"buildmyiq",
|
|
83
84
|
"proctoring",
|
|
84
85
|
"signals",
|
|
85
86
|
"nil"
|
|
86
87
|
],
|
|
87
|
-
enabled: !!
|
|
88
|
+
enabled: !!t && i,
|
|
88
89
|
initialPageParam: void 0,
|
|
89
|
-
queryFn: async ({ pageParam: e }) => (await
|
|
90
|
+
queryFn: async ({ pageParam: e }) => (await n(`query BuiProctoringSessionSignals($sessionId: ID!, $limit: Int, $after: ID) {
|
|
90
91
|
buiProctoringSessionSignals(sessionId: $sessionId, limit: $limit, after: $after) {
|
|
91
|
-
items { ${
|
|
92
|
+
items { ${u} }
|
|
92
93
|
pageInfo { cursor hasMore }
|
|
93
94
|
}
|
|
94
95
|
}`, e ? {
|
|
95
|
-
sessionId:
|
|
96
|
+
sessionId: t,
|
|
96
97
|
limit: 100,
|
|
97
98
|
after: e
|
|
98
99
|
} : {
|
|
99
|
-
sessionId:
|
|
100
|
+
sessionId: t,
|
|
100
101
|
limit: 100
|
|
101
102
|
}, a)).buiProctoringSessionSignals,
|
|
102
103
|
getNextPageParam: (e) => e.pageInfo.hasMore ? e.pageInfo.cursor ?? void 0 : void 0
|
|
103
104
|
});
|
|
104
|
-
},
|
|
105
|
-
let { apiGatewayUrl:
|
|
106
|
-
return
|
|
105
|
+
}, _ = (t, r = !0) => {
|
|
106
|
+
let { apiGatewayUrl: i } = e(), { status: o, assessmentId: l, limit: u = 50 } = t;
|
|
107
|
+
return a({
|
|
107
108
|
queryKey: [
|
|
108
|
-
...
|
|
109
|
-
|
|
109
|
+
...c.queue,
|
|
110
|
+
o ?? "ALL",
|
|
110
111
|
l ?? "ALL",
|
|
111
112
|
u
|
|
112
113
|
],
|
|
113
114
|
enabled: r,
|
|
114
|
-
queryFn: async () => (await
|
|
115
|
+
queryFn: async () => (await n(`query BuiListProctoringSessions($assessmentId: ID, $status: BuiProctoringStatus, $pagination: PaginationInput) {
|
|
115
116
|
buiListProctoringSessions(assessmentId: $assessmentId, status: $status, pagination: $pagination) {
|
|
116
|
-
items { ${
|
|
117
|
+
items { ${s} }
|
|
117
118
|
pageInfo { cursor hasMore }
|
|
118
119
|
}
|
|
119
120
|
}`, {
|
|
120
121
|
assessmentId: l,
|
|
121
|
-
status:
|
|
122
|
+
status: o,
|
|
122
123
|
pagination: { limit: u }
|
|
123
|
-
},
|
|
124
|
+
}, i)).buiListProctoringSessions
|
|
124
125
|
});
|
|
125
|
-
},
|
|
126
|
-
let { apiGatewayUrl:
|
|
127
|
-
return
|
|
128
|
-
queryKey:
|
|
129
|
-
enabled: !!
|
|
130
|
-
staleTime:
|
|
131
|
-
refetchInterval:
|
|
126
|
+
}, v = (t, r = !0) => {
|
|
127
|
+
let { apiGatewayUrl: i } = e();
|
|
128
|
+
return a({
|
|
129
|
+
queryKey: c.identityFile(t ?? "nil"),
|
|
130
|
+
enabled: !!t && r,
|
|
131
|
+
staleTime: l,
|
|
132
|
+
refetchInterval: l,
|
|
132
133
|
retry: !1,
|
|
133
|
-
queryFn: async () => (await
|
|
134
|
+
queryFn: async () => (await n("query BuiProctoringIdentityFile($sessionId: ID!) {\n buiProctoringIdentityFile(sessionId: $sessionId) {\n fileId name mimeType size downloadUrl expiresAt\n }\n }", { sessionId: t }, i)).buiProctoringIdentityFile
|
|
134
135
|
});
|
|
135
|
-
},
|
|
136
|
-
let { apiGatewayUrl:
|
|
137
|
-
return
|
|
138
|
-
mutationFn: async (e) => (await
|
|
139
|
-
buiReviewProctoringSession(id: $id, input: $input) { ${
|
|
140
|
-
}`, e,
|
|
141
|
-
onSuccess: () =>
|
|
136
|
+
}, y = () => {
|
|
137
|
+
let { apiGatewayUrl: r } = e(), a = o();
|
|
138
|
+
return i({
|
|
139
|
+
mutationFn: async (e) => (await n(`mutation BuiReviewProctoringSession($id: ID!, $input: BuiReviewProctoringSessionInput!) {
|
|
140
|
+
buiReviewProctoringSession(id: $id, input: $input) { ${s} }
|
|
141
|
+
}`, e, r)).buiReviewProctoringSession,
|
|
142
|
+
onSuccess: () => t(a, c.queue)
|
|
142
143
|
});
|
|
143
144
|
};
|
|
144
145
|
//#endregion
|
|
145
|
-
export {
|
|
146
|
+
export { f as useBuiMyProctoringSession, v as useBuiProctoringIdentityFile, g as useBuiProctoringSessionSignals, _ as useBuiProctoringSessions, h as useBuiRecordProctoringSignals, y as useBuiReviewProctoringSession, p as useBuiStartProctoringSession, m as useBuiSubmitProctoringIdentity };
|
|
146
147
|
|
|
147
148
|
//# sourceMappingURL=useBuiProctoring.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuiProctoring.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiProctoring.ts"],"sourcesContent":["/**\n * Buildmyiq — high-stakes exam proctoring hooks (BOFF-4431).\n * @module buildmyiq/hooks/useBuiProctoring\n *\n * Wired to the `wspace-buildmyiq-svc` proctoring contract:\n * - learner (self-scoped): `buiMyProctoringSession`, `buiStartProctoringSession`,\n * `buiSubmitProctoringIdentity`, `buiReportProctoringViolation`\n * - reviewer (workspace-scoped): `buiListProctoringSessions`,\n * `buiReviewProctoringSession`, `buiProctoringIdentityFile`\n * - BOFF-9690 signals: `buiRecordProctoringSignals` (learner, own session)\n * and the reviewer timeline `buiProctoringSessionSignals`\n *\n * The backend is the gate: `buiSubmitAssessment` refuses a `requiresProctoring`\n * assessment unless the learner's latest session is APPROVED and unexpired.\n * These hooks only drive the UI through that lifecycle.\n */\n\nimport { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { useBuildmyiq } from '../context';\nimport { wspaceFetch } from './useBuiFiles';\nimport type {\n BuiConnection,\n BuiProctoringIdentityFile,\n BuiProctoringSession,\n BuiProctoringSignal,\n BuiProctoringSignalInput,\n BuiProctoringStatus,\n BuiReviewProctoringSessionInput,\n BuiSubmitProctoringIdentityInput,\n} from '../types';\n\n// `identityFile` selects ONLY `id`: wspace-files-svc's File entity has no\n// __resolveReference, so any other field on a federated File stub fails the\n// whole operation. The learner's photo ID is PRIVATE in the file store\n// (BOFF-9691), so the reviewer page opens it through `buiProctoringIdentityFile`\n// (useBuiProctoringIdentityFile), never through the files graph.\nconst PROCTORING_SESSION_FIELDS = `\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt violationCount\n aiMonitoringConsentAt signalCount reviewedById reviewedAt reviewNotes expiresAt createdAt updatedAt\n identityFile { id }\n`;\n\nconst PROCTORING_KEY = {\n mine: (assessmentId: string) => ['buildmyiq', 'proctoring', 'mine', assessmentId] as const,\n queue: ['buildmyiq', 'proctoring', 'queue'] as const,\n identityFile: (sessionId: string) =>\n ['buildmyiq', 'proctoring', 'identity-file', sessionId] as const,\n signals: (sessionId: string) => ['buildmyiq', 'proctoring', 'signals', sessionId] as const,\n};\n\n/**\n * The backend presigns the identity URL for about five minutes; re-fetch well\n * before that so an open review page never holds a dead link.\n */\nconst IDENTITY_URL_REFRESH_MS = 4 * 60_000;\n\nconst PROCTORING_SIGNAL_FIELDS = `\n id sessionId type startedAt durationMs confidence faceCount peakLevelDb createdAt\n`;\n\n/** How often the learner's waiting screen re-checks for a reviewer decision. */\nconst AWAITING_REVIEW_POLL_MS = 15_000;\n\n/**\n * The calling learner's most recent proctoring session for an assessment\n * (null when they have never started one). Polls while a reviewer decision\n * is pending so the exam unlocks without a manual refresh.\n */\nexport const useBuiMyProctoringSession = (assessmentId: string | undefined, enabled = true) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: assessmentId\n ? PROCTORING_KEY.mine(assessmentId)\n : ['buildmyiq', 'proctoring', 'mine', 'nil'],\n enabled: Boolean(assessmentId) && enabled,\n queryFn: async () => {\n const data = await wspaceFetch<{ buiMyProctoringSession: BuiProctoringSession | null }>(\n `query BuiMyProctoringSession($assessmentId: ID!) {\n buiMyProctoringSession(assessmentId: $assessmentId) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n { assessmentId },\n apiGatewayUrl\n );\n return data.buiMyProctoringSession;\n },\n refetchInterval: (query) => {\n const status = query.state.data?.status;\n return status === 'UNDER_REVIEW' || status === 'IDENTITY_SUBMITTED'\n ? AWAITING_REVIEW_POLL_MS\n : false;\n },\n });\n};\n\nexport const useBuiStartProctoringSession = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (assessmentId: string) => {\n const data = await wspaceFetch<{ buiStartProctoringSession: BuiProctoringSession }>(\n `mutation BuiStartProctoringSession($assessmentId: ID!) {\n buiStartProctoringSession(assessmentId: $assessmentId) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n { assessmentId },\n apiGatewayUrl\n );\n return data.buiStartProctoringSession;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n },\n });\n};\n\nexport const useBuiSubmitProctoringIdentity = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiSubmitProctoringIdentityInput }) => {\n const data = await wspaceFetch<{ buiSubmitProctoringIdentity: BuiProctoringSession }>(\n `mutation BuiSubmitProctoringIdentity($id: ID!, $input: BuiSubmitProctoringIdentityInput!) {\n buiSubmitProctoringIdentity(id: $id, input: $input) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n args,\n apiGatewayUrl\n );\n return data.buiSubmitProctoringIdentity;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n void qc.invalidateQueries({ queryKey: PROCTORING_KEY.queue });\n },\n });\n};\n\n/** Records one lockdown violation (tab switch, window blur, fullscreen exit, copy/paste). */\nexport const useBuiReportProctoringViolation = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await wspaceFetch<{ buiReportProctoringViolation: BuiProctoringSession }>(\n `mutation BuiReportProctoringViolation($id: ID!) {\n buiReportProctoringViolation(id: $id) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return data.buiReportProctoringViolation;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n },\n });\n};\n\n/**\n * BOFF-9690: records a batch (max 50) of signal episodes on the learner's own\n * approved session — lockdown events and, when the assessment opted in and\n * the learner consented, in-browser AI signals.\n */\nexport const useBuiRecordProctoringSignals = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; signals: BuiProctoringSignalInput[] }) => {\n const data = await wspaceFetch<{ buiRecordProctoringSignals: BuiProctoringSession }>(\n `mutation BuiRecordProctoringSignals($id: ID!, $signals: [BuiProctoringSignalInput!]!) {\n buiRecordProctoringSignals(id: $id, signals: $signals) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n args,\n apiGatewayUrl\n );\n return data.buiRecordProctoringSignals;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n },\n });\n};\n\n/** BOFF-9690: reviewer timeline of one session's signals, oldest first, 100 per page. */\nexport const useBuiProctoringSessionSignals = (sessionId: string | undefined, enabled = true) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useInfiniteQuery({\n queryKey: sessionId\n ? PROCTORING_KEY.signals(sessionId)\n : ['buildmyiq', 'proctoring', 'signals', 'nil'],\n enabled: Boolean(sessionId) && enabled,\n initialPageParam: undefined as string | undefined,\n queryFn: async ({ pageParam }) => {\n const data = await wspaceFetch<{\n buiProctoringSessionSignals: BuiConnection<BuiProctoringSignal>;\n }>(\n `query BuiProctoringSessionSignals($sessionId: ID!, $limit: Int, $after: ID) {\n buiProctoringSessionSignals(sessionId: $sessionId, limit: $limit, after: $after) {\n items { ${PROCTORING_SIGNAL_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n // Explicit limit/after: the composed PaginationInput carries only `limit`.\n pageParam ? { sessionId, limit: 100, after: pageParam } : { sessionId, limit: 100 },\n apiGatewayUrl\n );\n return data.buiProctoringSessionSignals;\n },\n getNextPageParam: (last) =>\n last.pageInfo.hasMore ? (last.pageInfo.cursor ?? undefined) : undefined,\n });\n};\n\n/** Reviewer queue: proctoring sessions in the workspace, optionally filtered by status. */\nexport const useBuiProctoringSessions = (\n args: { status?: BuiProctoringStatus; assessmentId?: string; limit?: number },\n enabled = true\n) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const { status, assessmentId, limit = 50 } = args;\n return useQuery({\n queryKey: [...PROCTORING_KEY.queue, status ?? 'ALL', assessmentId ?? 'ALL', limit],\n enabled,\n queryFn: async () => {\n const data = await wspaceFetch<{\n buiListProctoringSessions: BuiConnection<BuiProctoringSession>;\n }>(\n `query BuiListProctoringSessions($assessmentId: ID, $status: BuiProctoringStatus, $pagination: PaginationInput) {\n buiListProctoringSessions(assessmentId: $assessmentId, status: $status, pagination: $pagination) {\n items { ${PROCTORING_SESSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { assessmentId, status, pagination: { limit } },\n apiGatewayUrl\n );\n return data.buiListProctoringSessions;\n },\n });\n};\n\n/**\n * Reviewer-only: the learner's identity photo/ID for one session, with a\n * short-lived download URL (BOFF-9691). Null when the session has none.\n */\nexport const useBuiProctoringIdentityFile = (sessionId: string | undefined, enabled = true) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: PROCTORING_KEY.identityFile(sessionId ?? 'nil'),\n enabled: Boolean(sessionId) && enabled,\n staleTime: IDENTITY_URL_REFRESH_MS,\n refetchInterval: IDENTITY_URL_REFRESH_MS,\n retry: false,\n queryFn: async () => {\n const data = await wspaceFetch<{\n buiProctoringIdentityFile: BuiProctoringIdentityFile | null;\n }>(\n `query BuiProctoringIdentityFile($sessionId: ID!) {\n buiProctoringIdentityFile(sessionId: $sessionId) {\n fileId name mimeType size downloadUrl expiresAt\n }\n }`,\n { sessionId },\n apiGatewayUrl\n );\n return data.buiProctoringIdentityFile;\n },\n });\n};\n\nexport const useBuiReviewProctoringSession = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiReviewProctoringSessionInput }) => {\n const data = await wspaceFetch<{ buiReviewProctoringSession: BuiProctoringSession }>(\n `mutation BuiReviewProctoringSession($id: ID!, $input: BuiReviewProctoringSessionInput!) {\n buiReviewProctoringSession(id: $id, input: $input) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n args,\n apiGatewayUrl\n );\n return data.buiReviewProctoringSession;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: PROCTORING_KEY.queue }),\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useBuiProctoring.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiProctoring.ts"],"sourcesContent":["/**\n * Buildmyiq — high-stakes exam proctoring hooks (BOFF-4431).\n * @module buildmyiq/hooks/useBuiProctoring\n *\n * Wired to the `wspace-buildmyiq-svc` proctoring contract:\n * - learner (self-scoped): `buiMyProctoringSession`, `buiStartProctoringSession`,\n * `buiSubmitProctoringIdentity`, `buiReportProctoringViolation`\n * - reviewer (workspace-scoped): `buiListProctoringSessions`,\n * `buiReviewProctoringSession`, `buiProctoringIdentityFile`\n * - BOFF-9690 signals: `buiRecordProctoringSignals` (learner, own session)\n * and the reviewer timeline `buiProctoringSessionSignals`\n *\n * The backend is the gate: `buiSubmitAssessment` refuses a `requiresProctoring`\n * assessment unless the learner's latest session is APPROVED and unexpired.\n * These hooks only drive the UI through that lifecycle.\n */\n\nimport { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { invalidateAfterWrite } from './invalidateAfterWrite';\n\nimport { useBuildmyiq } from '../context';\nimport { wspaceFetch } from './useBuiFiles';\nimport type {\n BuiConnection,\n BuiProctoringIdentityFile,\n BuiProctoringSession,\n BuiProctoringSignal,\n BuiProctoringSignalInput,\n BuiProctoringStatus,\n BuiReviewProctoringSessionInput,\n BuiSubmitProctoringIdentityInput,\n} from '../types';\n\n// `identityFile` selects ONLY `id`: wspace-files-svc's File entity has no\n// __resolveReference, so any other field on a federated File stub fails the\n// whole operation. The learner's photo ID is PRIVATE in the file store\n// (BOFF-9691), so the reviewer page opens it through `buiProctoringIdentityFile`\n// (useBuiProctoringIdentityFile), never through the files graph.\nconst PROCTORING_SESSION_FIELDS = `\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt violationCount\n aiMonitoringConsentAt signalCount reviewedById reviewedAt reviewNotes expiresAt createdAt updatedAt\n identityFile { id }\n`;\n\nconst PROCTORING_KEY = {\n mine: (assessmentId: string) => ['buildmyiq', 'proctoring', 'mine', assessmentId] as const,\n queue: ['buildmyiq', 'proctoring', 'queue'] as const,\n identityFile: (sessionId: string) =>\n ['buildmyiq', 'proctoring', 'identity-file', sessionId] as const,\n signals: (sessionId: string) => ['buildmyiq', 'proctoring', 'signals', sessionId] as const,\n};\n\n/**\n * The backend presigns the identity URL for about five minutes; re-fetch well\n * before that so an open review page never holds a dead link.\n */\nconst IDENTITY_URL_REFRESH_MS = 4 * 60_000;\n\nconst PROCTORING_SIGNAL_FIELDS = `\n id sessionId type startedAt durationMs confidence faceCount peakLevelDb createdAt\n`;\n\n/** How often the learner's waiting screen re-checks for a reviewer decision. */\nconst AWAITING_REVIEW_POLL_MS = 15_000;\n\n/**\n * The calling learner's most recent proctoring session for an assessment\n * (null when they have never started one). Polls while a reviewer decision\n * is pending so the exam unlocks without a manual refresh.\n */\nexport const useBuiMyProctoringSession = (assessmentId: string | undefined, enabled = true) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: assessmentId\n ? PROCTORING_KEY.mine(assessmentId)\n : ['buildmyiq', 'proctoring', 'mine', 'nil'],\n enabled: Boolean(assessmentId) && enabled,\n queryFn: async () => {\n const data = await wspaceFetch<{ buiMyProctoringSession: BuiProctoringSession | null }>(\n `query BuiMyProctoringSession($assessmentId: ID!) {\n buiMyProctoringSession(assessmentId: $assessmentId) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n { assessmentId },\n apiGatewayUrl\n );\n return data.buiMyProctoringSession;\n },\n refetchInterval: (query) => {\n const status = query.state.data?.status;\n return status === 'UNDER_REVIEW' || status === 'IDENTITY_SUBMITTED'\n ? AWAITING_REVIEW_POLL_MS\n : false;\n },\n });\n};\n\nexport const useBuiStartProctoringSession = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (assessmentId: string) => {\n const data = await wspaceFetch<{ buiStartProctoringSession: BuiProctoringSession }>(\n `mutation BuiStartProctoringSession($assessmentId: ID!) {\n buiStartProctoringSession(assessmentId: $assessmentId) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n { assessmentId },\n apiGatewayUrl\n );\n return data.buiStartProctoringSession;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n },\n });\n};\n\nexport const useBuiSubmitProctoringIdentity = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiSubmitProctoringIdentityInput }) => {\n const data = await wspaceFetch<{ buiSubmitProctoringIdentity: BuiProctoringSession }>(\n `mutation BuiSubmitProctoringIdentity($id: ID!, $input: BuiSubmitProctoringIdentityInput!) {\n buiSubmitProctoringIdentity(id: $id, input: $input) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n args,\n apiGatewayUrl\n );\n return data.buiSubmitProctoringIdentity;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n void invalidateAfterWrite(qc, PROCTORING_KEY.queue);\n },\n });\n};\n\n/** Records one lockdown violation (tab switch, window blur, fullscreen exit, copy/paste). */\nexport const useBuiReportProctoringViolation = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await wspaceFetch<{ buiReportProctoringViolation: BuiProctoringSession }>(\n `mutation BuiReportProctoringViolation($id: ID!) {\n buiReportProctoringViolation(id: $id) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return data.buiReportProctoringViolation;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n },\n });\n};\n\n/**\n * BOFF-9690: records a batch (max 50) of signal episodes on the learner's own\n * approved session — lockdown events and, when the assessment opted in and\n * the learner consented, in-browser AI signals.\n */\nexport const useBuiRecordProctoringSignals = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; signals: BuiProctoringSignalInput[] }) => {\n const data = await wspaceFetch<{ buiRecordProctoringSignals: BuiProctoringSession }>(\n `mutation BuiRecordProctoringSignals($id: ID!, $signals: [BuiProctoringSignalInput!]!) {\n buiRecordProctoringSignals(id: $id, signals: $signals) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n args,\n apiGatewayUrl\n );\n return data.buiRecordProctoringSignals;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n },\n });\n};\n\n/** BOFF-9690: reviewer timeline of one session's signals, oldest first, 100 per page. */\nexport const useBuiProctoringSessionSignals = (sessionId: string | undefined, enabled = true) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useInfiniteQuery({\n queryKey: sessionId\n ? PROCTORING_KEY.signals(sessionId)\n : ['buildmyiq', 'proctoring', 'signals', 'nil'],\n enabled: Boolean(sessionId) && enabled,\n initialPageParam: undefined as string | undefined,\n queryFn: async ({ pageParam }) => {\n const data = await wspaceFetch<{\n buiProctoringSessionSignals: BuiConnection<BuiProctoringSignal>;\n }>(\n `query BuiProctoringSessionSignals($sessionId: ID!, $limit: Int, $after: ID) {\n buiProctoringSessionSignals(sessionId: $sessionId, limit: $limit, after: $after) {\n items { ${PROCTORING_SIGNAL_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n // Explicit limit/after: the composed PaginationInput carries only `limit`.\n pageParam ? { sessionId, limit: 100, after: pageParam } : { sessionId, limit: 100 },\n apiGatewayUrl\n );\n return data.buiProctoringSessionSignals;\n },\n getNextPageParam: (last) =>\n last.pageInfo.hasMore ? (last.pageInfo.cursor ?? undefined) : undefined,\n });\n};\n\n/** Reviewer queue: proctoring sessions in the workspace, optionally filtered by status. */\nexport const useBuiProctoringSessions = (\n args: { status?: BuiProctoringStatus; assessmentId?: string; limit?: number },\n enabled = true\n) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const { status, assessmentId, limit = 50 } = args;\n return useQuery({\n queryKey: [...PROCTORING_KEY.queue, status ?? 'ALL', assessmentId ?? 'ALL', limit],\n enabled,\n queryFn: async () => {\n const data = await wspaceFetch<{\n buiListProctoringSessions: BuiConnection<BuiProctoringSession>;\n }>(\n `query BuiListProctoringSessions($assessmentId: ID, $status: BuiProctoringStatus, $pagination: PaginationInput) {\n buiListProctoringSessions(assessmentId: $assessmentId, status: $status, pagination: $pagination) {\n items { ${PROCTORING_SESSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { assessmentId, status, pagination: { limit } },\n apiGatewayUrl\n );\n return data.buiListProctoringSessions;\n },\n });\n};\n\n/**\n * Reviewer-only: the learner's identity photo/ID for one session, with a\n * short-lived download URL (BOFF-9691). Null when the session has none.\n */\nexport const useBuiProctoringIdentityFile = (sessionId: string | undefined, enabled = true) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: PROCTORING_KEY.identityFile(sessionId ?? 'nil'),\n enabled: Boolean(sessionId) && enabled,\n staleTime: IDENTITY_URL_REFRESH_MS,\n refetchInterval: IDENTITY_URL_REFRESH_MS,\n retry: false,\n queryFn: async () => {\n const data = await wspaceFetch<{\n buiProctoringIdentityFile: BuiProctoringIdentityFile | null;\n }>(\n `query BuiProctoringIdentityFile($sessionId: ID!) {\n buiProctoringIdentityFile(sessionId: $sessionId) {\n fileId name mimeType size downloadUrl expiresAt\n }\n }`,\n { sessionId },\n apiGatewayUrl\n );\n return data.buiProctoringIdentityFile;\n },\n });\n};\n\nexport const useBuiReviewProctoringSession = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiReviewProctoringSessionInput }) => {\n const data = await wspaceFetch<{ buiReviewProctoringSession: BuiProctoringSession }>(\n `mutation BuiReviewProctoringSession($id: ID!, $input: BuiReviewProctoringSessionInput!) {\n buiReviewProctoringSession(id: $id, input: $input) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n args,\n apiGatewayUrl\n );\n return data.buiReviewProctoringSession;\n },\n onSuccess: () => invalidateAfterWrite(qc, PROCTORING_KEY.queue),\n });\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuiTags.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiTags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"useBuiTags.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiTags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAiBD,uDAAuD;AACvD,eAAO,MAAM,gBAAgB,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM,GAAG,SAAS,uFAmBhF,CAAC;AAEF,iEAAiE;AACjE,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,6EAiB5C,CAAC;AAEF,4CAA4C;AAC5C,eAAO,MAAM,eAAe,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM;QAKZ,MAAM;0BAW7D,CAAC;AAEF,oDAAoD;AACpD,eAAO,MAAM,yBAAyB,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM,uFAgB7E,CAAC;AA+BF,2EAA2E;AAC3E,eAAO,MAAM,wBAAwB,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM;QAalB,MAAM;0BAkBhE,CAAC"}
|
|
@@ -1,89 +1,90 @@
|
|
|
1
1
|
import { useBuildmyiq as e } from "../context/BuildmyiqContext.js";
|
|
2
2
|
import "../context/index.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { invalidateAfterWrite as t } from "./invalidateAfterWrite.js";
|
|
4
|
+
import { wspaceFetch as n } from "./useBuiFiles.js";
|
|
5
|
+
import { useMutation as r, useQuery as i, useQueryClient as a } from "@tanstack/react-query";
|
|
5
6
|
//#region src/buildmyiq/hooks/useBuiTags.ts
|
|
6
|
-
var
|
|
7
|
+
var o = "buildmyiq", s = (e, t) => [
|
|
7
8
|
"buildmyiq",
|
|
8
9
|
"entity-tags",
|
|
9
10
|
e,
|
|
10
11
|
t
|
|
11
|
-
],
|
|
12
|
+
], c = "id label color key", l = (e) => e.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "tag", u = (t, r) => {
|
|
12
13
|
let { apiGatewayUrl: a } = e();
|
|
13
|
-
return
|
|
14
|
-
queryKey:
|
|
15
|
-
enabled: !!
|
|
16
|
-
queryFn: async () => (await
|
|
14
|
+
return i({
|
|
15
|
+
queryKey: s(t, r ?? ""),
|
|
16
|
+
enabled: !!r,
|
|
17
|
+
queryFn: async () => (await n(`query BuiGetEntityTags($entityType: String!, $entityId: ID!) {
|
|
17
18
|
getEntityTags(entityType: $entityType, entityId: $entityId) {
|
|
18
19
|
id
|
|
19
|
-
tag { ${
|
|
20
|
+
tag { ${c} }
|
|
20
21
|
}
|
|
21
22
|
}`, {
|
|
22
|
-
entityType:
|
|
23
|
-
entityId:
|
|
23
|
+
entityType: t,
|
|
24
|
+
entityId: r
|
|
24
25
|
}, a)).getEntityTags
|
|
25
26
|
});
|
|
26
|
-
},
|
|
27
|
-
let { apiGatewayUrl:
|
|
28
|
-
return
|
|
27
|
+
}, d = (t) => {
|
|
28
|
+
let { apiGatewayUrl: r } = e(), a = t.trim();
|
|
29
|
+
return i({
|
|
29
30
|
queryKey: [
|
|
30
31
|
"buildmyiq",
|
|
31
32
|
"tag-search",
|
|
32
33
|
a
|
|
33
34
|
],
|
|
34
35
|
enabled: a.length > 0,
|
|
35
|
-
queryFn: async () => (await
|
|
36
|
-
searchTags(query: $query, limit: $limit) { ${
|
|
36
|
+
queryFn: async () => (await n(`query BuiSearchTags($query: String!, $limit: Int) {
|
|
37
|
+
searchTags(query: $query, limit: $limit) { ${c} }
|
|
37
38
|
}`, {
|
|
38
39
|
query: a,
|
|
39
40
|
limit: 8
|
|
40
|
-
},
|
|
41
|
+
}, r)).searchTags
|
|
41
42
|
});
|
|
42
|
-
},
|
|
43
|
-
let { apiGatewayUrl:
|
|
44
|
-
return
|
|
45
|
-
mutationFn: async (e) => (await
|
|
46
|
-
entityType:
|
|
47
|
-
entityId:
|
|
43
|
+
}, f = (i, o) => {
|
|
44
|
+
let { apiGatewayUrl: c } = e(), l = a();
|
|
45
|
+
return r({
|
|
46
|
+
mutationFn: async (e) => (await n("mutation BuiAssignTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }", { input: {
|
|
47
|
+
entityType: i,
|
|
48
|
+
entityId: o,
|
|
48
49
|
tagId: e
|
|
49
|
-
} },
|
|
50
|
-
onSuccess: () =>
|
|
50
|
+
} }, c)).assignTag,
|
|
51
|
+
onSuccess: () => t(l, s(i, o))
|
|
51
52
|
});
|
|
52
|
-
},
|
|
53
|
-
let { apiGatewayUrl:
|
|
54
|
-
return
|
|
55
|
-
mutationFn: async (e) => (await
|
|
56
|
-
onSuccess: () =>
|
|
53
|
+
}, p = (i, o) => {
|
|
54
|
+
let { apiGatewayUrl: c } = e(), l = a();
|
|
55
|
+
return r({
|
|
56
|
+
mutationFn: async (e) => (await n("mutation BuiRemoveTagAssignment($id: ID!) {\n removeTagAssignment(id: $id)\n }", { id: e }, c)).removeTagAssignment,
|
|
57
|
+
onSuccess: () => t(l, s(i, o))
|
|
57
58
|
});
|
|
58
|
-
},
|
|
59
|
-
let
|
|
60
|
-
return
|
|
61
|
-
key:
|
|
59
|
+
}, m = async (e) => {
|
|
60
|
+
let t = await n("query BuiGetVocabulary($key: String!) {\n getVocabularyByKey(key: $key) { id }\n }", { key: o }, e);
|
|
61
|
+
return t.getVocabularyByKey?.id ? t.getVocabularyByKey.id : (await n("mutation BuiCreateVocabulary($input: CreateVocabularyInput!) {\n createVocabulary(input: $input) { id }\n }", { input: {
|
|
62
|
+
key: o,
|
|
62
63
|
name: "Buildmyiq",
|
|
63
64
|
description: "Tags for Buildmyiq sites, species and other records.",
|
|
64
65
|
allowCustomTags: !0
|
|
65
66
|
} }, e)).createVocabulary.id;
|
|
66
|
-
},
|
|
67
|
-
let { apiGatewayUrl:
|
|
68
|
-
return
|
|
67
|
+
}, h = (i, o) => {
|
|
68
|
+
let { apiGatewayUrl: c } = e(), u = a();
|
|
69
|
+
return r({
|
|
69
70
|
mutationFn: async (e) => {
|
|
70
|
-
let
|
|
71
|
-
if (!
|
|
72
|
-
let
|
|
73
|
-
vocabularyId: await
|
|
74
|
-
key: `${
|
|
75
|
-
label:
|
|
76
|
-
} },
|
|
77
|
-
return await
|
|
78
|
-
entityType:
|
|
79
|
-
entityId:
|
|
80
|
-
tagId:
|
|
81
|
-
} },
|
|
71
|
+
let t = e.trim();
|
|
72
|
+
if (!t) throw Error("A tag label is required.");
|
|
73
|
+
let r = await n("mutation BuiCreateTag($input: CreateTagInput!) {\n createTag(input: $input) { id }\n }", { input: {
|
|
74
|
+
vocabularyId: await m(c),
|
|
75
|
+
key: `${l(t)}-${Math.random().toString(36).slice(2, 6)}`,
|
|
76
|
+
label: t
|
|
77
|
+
} }, c);
|
|
78
|
+
return await n("mutation BuiAssignCreatedTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }", { input: {
|
|
79
|
+
entityType: i,
|
|
80
|
+
entityId: o,
|
|
81
|
+
tagId: r.createTag.id
|
|
82
|
+
} }, c), r.createTag;
|
|
82
83
|
},
|
|
83
|
-
onSuccess: () =>
|
|
84
|
+
onSuccess: () => t(u, s(i, o))
|
|
84
85
|
});
|
|
85
86
|
};
|
|
86
87
|
//#endregion
|
|
87
|
-
export {
|
|
88
|
+
export { f as useEcoAssignTag, h as useEcoCreateAndAssignTag, u as useEcoEntityTags, p as useEcoRemoveTagAssignment, d as useEcoTagSearch };
|
|
88
89
|
|
|
89
90
|
//# sourceMappingURL=useBuiTags.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuiTags.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiTags.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-tags-svc UniversalTag REUSE hooks (Integration C).\n * @module buildmyiq/hooks/useBuiTags\n *\n * The buildmyiq svc does NOT expose a tags field, so we drive the platform tag\n * assignment store directly (approach (b) in the reuse audit) — no buildmyiq-svc\n * change. All operations are composed into the WORKSPACE supergraph:\n * - `getEntityTags(entityType, entityId)` → current assignments (display)\n * - `searchTags(query)` → pick an existing tag\n * - `createTag(input)` → create a new tag (needs a vocabulary)\n * - `assignTag(input)` → link tag ↔ entity\n * - `removeTagAssignment(id)` → unlink\n *\n * `createTag` requires a `vocabularyId`; we resolve a stable, workspace-scoped\n * \"buildmyiq\" vocabulary (`getVocabularyByKey`, creating it on first use).\n *\n * Transport reuses `wspaceFetch` from `useBuiFiles`. Lives in a separate file;\n * `useBuildmyiqApi.ts` is untouched.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { useBuildmyiq } from '../context';\nimport { wspaceFetch } from './useBuiFiles';\n\nexport interface BuiTag {\n id: string;\n label: string;\n color: string | null;\n key: string;\n}\n\nexport interface BuiTagAssignment {\n id: string;\n tag: BuiTag;\n}\n\nconst BUI_VOCABULARY_KEY = 'buildmyiq';\n\nconst entityTagsKey = (entityType: string, entityId: string) =>\n ['buildmyiq', 'entity-tags', entityType, entityId] as const;\n\nconst TAG_FIELDS = 'id label color key';\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 48) || 'tag';\n\n/** Current tag assignments for an Buildmyiq entity. */\nexport const useEcoEntityTags = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: entityTagsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{ getEntityTags: BuiTagAssignment[] }>(\n `query BuiGetEntityTags($entityType: String!, $entityId: ID!) {\n getEntityTags(entityType: $entityType, entityId: $entityId) {\n id\n tag { ${TAG_FIELDS} }\n }\n }`,\n { entityType, entityId },\n apiGatewayUrl\n );\n return data.getEntityTags;\n },\n });\n};\n\n/** Search existing tags (enabled only for a non-empty query). */\nexport const useEcoTagSearch = (query: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const trimmed = query.trim();\n return useQuery({\n queryKey: ['buildmyiq', 'tag-search', trimmed],\n enabled: trimmed.length > 0,\n queryFn: async () => {\n const data = await wspaceFetch<{ searchTags: BuiTag[] }>(\n `query BuiSearchTags($query: String!, $limit: Int) {\n searchTags(query: $query, limit: $limit) { ${TAG_FIELDS} }\n }`,\n { query: trimmed, limit: 8 },\n apiGatewayUrl\n );\n return data.searchTags;\n },\n });\n};\n\n/** Assign an existing tag to the entity. */\nexport const useEcoAssignTag = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (tagId: string) => {\n const data = await wspaceFetch<{ assignTag: { id: string } }>(\n `mutation BuiAssignTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }`,\n { input: { entityType, entityId, tagId } },\n apiGatewayUrl\n );\n return data.assignTag;\n },\n onSuccess: () => qc
|
|
1
|
+
{"version":3,"file":"useBuiTags.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiTags.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-tags-svc UniversalTag REUSE hooks (Integration C).\n * @module buildmyiq/hooks/useBuiTags\n *\n * The buildmyiq svc does NOT expose a tags field, so we drive the platform tag\n * assignment store directly (approach (b) in the reuse audit) — no buildmyiq-svc\n * change. All operations are composed into the WORKSPACE supergraph:\n * - `getEntityTags(entityType, entityId)` → current assignments (display)\n * - `searchTags(query)` → pick an existing tag\n * - `createTag(input)` → create a new tag (needs a vocabulary)\n * - `assignTag(input)` → link tag ↔ entity\n * - `removeTagAssignment(id)` → unlink\n *\n * `createTag` requires a `vocabularyId`; we resolve a stable, workspace-scoped\n * \"buildmyiq\" vocabulary (`getVocabularyByKey`, creating it on first use).\n *\n * Transport reuses `wspaceFetch` from `useBuiFiles`. Lives in a separate file;\n * `useBuildmyiqApi.ts` is untouched.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { invalidateAfterWrite } from './invalidateAfterWrite';\n\nimport { useBuildmyiq } from '../context';\nimport { wspaceFetch } from './useBuiFiles';\n\nexport interface BuiTag {\n id: string;\n label: string;\n color: string | null;\n key: string;\n}\n\nexport interface BuiTagAssignment {\n id: string;\n tag: BuiTag;\n}\n\nconst BUI_VOCABULARY_KEY = 'buildmyiq';\n\nconst entityTagsKey = (entityType: string, entityId: string) =>\n ['buildmyiq', 'entity-tags', entityType, entityId] as const;\n\nconst TAG_FIELDS = 'id label color key';\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 48) || 'tag';\n\n/** Current tag assignments for an Buildmyiq entity. */\nexport const useEcoEntityTags = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: entityTagsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{ getEntityTags: BuiTagAssignment[] }>(\n `query BuiGetEntityTags($entityType: String!, $entityId: ID!) {\n getEntityTags(entityType: $entityType, entityId: $entityId) {\n id\n tag { ${TAG_FIELDS} }\n }\n }`,\n { entityType, entityId },\n apiGatewayUrl\n );\n return data.getEntityTags;\n },\n });\n};\n\n/** Search existing tags (enabled only for a non-empty query). */\nexport const useEcoTagSearch = (query: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const trimmed = query.trim();\n return useQuery({\n queryKey: ['buildmyiq', 'tag-search', trimmed],\n enabled: trimmed.length > 0,\n queryFn: async () => {\n const data = await wspaceFetch<{ searchTags: BuiTag[] }>(\n `query BuiSearchTags($query: String!, $limit: Int) {\n searchTags(query: $query, limit: $limit) { ${TAG_FIELDS} }\n }`,\n { query: trimmed, limit: 8 },\n apiGatewayUrl\n );\n return data.searchTags;\n },\n });\n};\n\n/** Assign an existing tag to the entity. */\nexport const useEcoAssignTag = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (tagId: string) => {\n const data = await wspaceFetch<{ assignTag: { id: string } }>(\n `mutation BuiAssignTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }`,\n { input: { entityType, entityId, tagId } },\n apiGatewayUrl\n );\n return data.assignTag;\n },\n onSuccess: () => invalidateAfterWrite(qc, entityTagsKey(entityType, entityId)),\n });\n};\n\n/** Remove a tag assignment by its assignment id. */\nexport const useEcoRemoveTagAssignment = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (assignmentId: string) => {\n const data = await wspaceFetch<{ removeTagAssignment: boolean }>(\n `mutation BuiRemoveTagAssignment($id: ID!) {\n removeTagAssignment(id: $id)\n }`,\n { id: assignmentId },\n apiGatewayUrl\n );\n return data.removeTagAssignment;\n },\n onSuccess: () => invalidateAfterWrite(qc, entityTagsKey(entityType, entityId)),\n });\n};\n\n/** Resolve (creating on first use) the workspace-scoped \"buildmyiq\" vocabulary. */\nconst resolveEcoVocabularyId = async (baseUrl?: string): Promise<string> => {\n const existing = await wspaceFetch<{ getVocabularyByKey: { id: string } | null }>(\n `query BuiGetVocabulary($key: String!) {\n getVocabularyByKey(key: $key) { id }\n }`,\n { key: BUI_VOCABULARY_KEY },\n baseUrl\n );\n if (existing.getVocabularyByKey?.id) {\n return existing.getVocabularyByKey.id;\n }\n const created = await wspaceFetch<{ createVocabulary: { id: string } }>(\n `mutation BuiCreateVocabulary($input: CreateVocabularyInput!) {\n createVocabulary(input: $input) { id }\n }`,\n {\n input: {\n key: BUI_VOCABULARY_KEY,\n name: 'Buildmyiq',\n description: 'Tags for Buildmyiq sites, species and other records.',\n allowCustomTags: true,\n },\n },\n baseUrl\n );\n return created.createVocabulary.id;\n};\n\n/** Create a new tag from a free-text label and assign it to the entity. */\nexport const useEcoCreateAndAssignTag = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (label: string) => {\n const trimmed = label.trim();\n if (!trimmed) {\n throw new Error('A tag label is required.');\n }\n const vocabularyId = await resolveEcoVocabularyId(apiGatewayUrl);\n // A unique key avoids collisions with existing tags in the vocabulary;\n // the (non-unique) label is what users see.\n const key = `${slugify(trimmed)}-${Math.random().toString(36).slice(2, 6)}`;\n const created = await wspaceFetch<{ createTag: { id: string } }>(\n `mutation BuiCreateTag($input: CreateTagInput!) {\n createTag(input: $input) { id }\n }`,\n { input: { vocabularyId, key, label: trimmed } },\n apiGatewayUrl\n );\n await wspaceFetch<{ assignTag: { id: string } }>(\n `mutation BuiAssignCreatedTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }`,\n { input: { entityType, entityId, tagId: created.createTag.id } },\n apiGatewayUrl\n );\n return created.createTag;\n },\n onSuccess: () => invalidateAfterWrite(qc, entityTagsKey(entityType, entityId)),\n });\n};\n"],"mappings":";;;;;;AAsCA,IAAM,IAAqB,aAErB,KAAiB,GAAoB,MACzC;CAAC;CAAa;CAAe;CAAY;AAAQ,GAE7C,IAAa,sBAEb,KAAW,MACf,EACG,YAAY,EACZ,KAAK,EACL,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,EAAE,KAAK,OAGR,KAAoB,GAAoB,MAAiC;CACpF,IAAM,EAAE,qBAAkB,EAAa;CACvC,OAAO,EAAS;EACd,UAAU,EAAc,GAAY,KAAY,EAAE;EAClD,SAAS,EAAQ;EACjB,SAAS,aAWA,MAVY,EACjB;;;oBAGY,EAAW;;YAGvB;GAAE;GAAY;EAAS,GACvB,CACF,GACY;CAEhB,CAAC;AACH,GAGa,KAAmB,MAAkB;CAChD,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAU,EAAM,KAAK;CAC3B,OAAO,EAAS;EACd,UAAU;GAAC;GAAa;GAAc;EAAO;EAC7C,SAAS,EAAQ,SAAS;EAC1B,SAAS,aAQA,MAPY,EACjB;uDAC+C,EAAW;YAE1D;GAAE,OAAO;GAAS,OAAO;EAAE,GAC3B,CACF,GACY;CAEhB,CAAC;AACH,GAGa,KAAmB,GAAoB,MAAqB;CACvE,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAQV,MAPY,EACjB,0GAGA,EAAE,OAAO;GAAE;GAAY;GAAU;EAAM,EAAE,GACzC,CACF,GACY;EAEd,iBAAiB,EAAqB,GAAI,EAAc,GAAY,CAAQ,CAAC;CAC/E,CAAC;AACH,GAGa,KAA6B,GAAoB,MAAqB;CACjF,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAQV,MAPY,EACjB,kGAGA,EAAE,IAAI,EAAa,GACnB,CACF,GACY;EAEd,iBAAiB,EAAqB,GAAI,EAAc,GAAY,CAAQ,CAAC;CAC/E,CAAC;AACH,GAGM,IAAyB,OAAO,MAAsC;CAC1E,IAAM,IAAW,MAAM,EACrB,8FAGA,EAAE,KAAK,EAAmB,GAC1B,CACF;CAkBA,OAjBI,EAAS,oBAAoB,KACxB,EAAS,mBAAmB,MAgB9B,MAde,EACpB,uHAGA,EACE,OAAO;EACL,KAAK;EACL,MAAM;EACN,aAAa;EACb,iBAAiB;CACnB,EACF,GACA,CACF,GACe,iBAAiB;AAClC,GAGa,KAA4B,GAAoB,MAAqB;CAChF,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,MAAkB;GACnC,IAAM,IAAU,EAAM,KAAK;GAC3B,IAAI,CAAC,GACH,MAAU,MAAM,0BAA0B;GAM5C,IAAM,IAAU,MAAM,EACpB,0GAGA,EAAE,OAAO;IAAE,cAAA,MARc,EAAuB,CAAa;IAQpC,KAAA,GALZ,EAAQ,CAAO,EAAE,GAAG,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC;IAKxC,OAAO;GAAQ,EAAE,GAC/C,CACF;GAQA,OAPA,MAAM,EACJ,iHAGA,EAAE,OAAO;IAAE;IAAY;IAAU,OAAO,EAAQ,UAAU;GAAG,EAAE,GAC/D,CACF,GACO,EAAQ;EACjB;EACA,iBAAiB,EAAqB,GAAI,EAAc,GAAY,CAAQ,CAAC;CAC/E,CAAC;AACH"}
|