@crimson-education/sdk 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import type { CrimsonClient } from "./client";
2
- import type { Task, PaginationParams, PaginatedResult, ActionItemResource, AddResourceInput, UploadUrlResponse, DownloadUrlResponse, UpdateTaskResourceInput, User } from "./types";
2
+ import type { Task, PaginationParams, PaginatedResult, ActionItemResource, AddResourceInput, UploadUrlResponse, DownloadUrlResponse, UpdateTaskResourceInput, User, ActionItemActivityBrief } from "./types";
3
3
  export type TaskOrderBy = "priority" | "dueDate" | "missionTitle" | "createdAt" | "description";
4
4
  export interface TaskFilters extends PaginationParams {
5
5
  status?: string[];
@@ -143,4 +143,8 @@ export declare class TasksApi {
143
143
  url: string;
144
144
  key: string;
145
145
  }>;
146
+ /**
147
+ * Get activities (e.g. strict due date history) for tasks
148
+ */
149
+ getActivities(actionItemIds: string[], type?: string): Promise<Record<string, ActionItemActivityBrief[]>>;
146
150
  }
@@ -322,5 +322,14 @@ class TasksApi {
322
322
  return { url, key };
323
323
  });
324
324
  }
325
+ /**
326
+ * Get activities (e.g. strict due date history) for tasks
327
+ */
328
+ getActivities(actionItemIds, type = "duedate") {
329
+ return this.client.fetch("/roadmap/action-items/activities", {
330
+ method: "POST",
331
+ body: JSON.stringify({ actionItemIds, type }),
332
+ });
333
+ }
325
334
  }
326
335
  exports.TasksApi = TasksApi;
@@ -79,7 +79,8 @@ export interface Task {
79
79
  roadmapMissionId: string;
80
80
  userId: string;
81
81
  creatorId?: string;
82
- roadmapId?: string;
82
+ /** null roadmapId is used to clear roadmapId when update */
83
+ roadmapId?: string | null;
83
84
  isComplete?: boolean;
84
85
  dueDate?: string | null;
85
86
  missionId?: string | null;
@@ -521,3 +522,7 @@ export interface StudentProfile {
521
522
  profileImageId?: string;
522
523
  [key: string]: unknown;
523
524
  }
525
+ export interface ActionItemActivityBrief {
526
+ data: any;
527
+ createdAt: string;
528
+ }
@@ -1,8 +1,9 @@
1
1
  export { useAuthState } from "./useAuthState";
2
+ export { useQueryClient } from "@tanstack/react-query";
2
3
  export { useOAuth, useOAuthCallback } from "./useOAuth";
3
4
  export type { UseOAuthResult } from "./useOAuth";
4
5
  export { useMissions, useMissionsInfinite, useCreateMission, useUpdateMission, useDeleteMission, missionKeys, } from "./useMissions";
5
- export { useTasks, useAllUserTasks, useTasksByRoadmapId, useTasksInfinite, useCreateTask, useUpdateTask, useDeleteTask, useBulkDeleteTasks, useBulkUpdateTasks, taskKeys, useGetDownloadUrl, useTaskCreators, } from "./useTasks";
6
+ export { useTasks, useAllUserTasks, useTasksByRoadmapId, useTasksInfinite, useCreateTask, useUpdateTask, useDeleteTask, useBulkDeleteTasks, useBulkUpdateTasks, taskKeys, useGetDownloadUrl, useTaskCreators, useTaskActivities, } from "./useTasks";
6
7
  export { useUsers, useUser, userKeys } from "./useUsers";
7
8
  export { useCurrentUserRoles, useMyStudents, accountKeys } from "./useAccount";
8
9
  export { useRoadmapContext } from "./useRoadmapContext";
@@ -14,9 +14,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.indigoKeys = exports.useTutorStudents = exports.useStudentTutors = exports.useIndigoMe = exports.studentProfileKeys = exports.useStudentPrefilledInfo = exports.useStudentProfile = exports.useDeleteTemplateMission = exports.useCreateTemplateMission = exports.missionLibraryKeys = exports.useCreateFromPredefinedTasks = exports.useAssignBulkTask = exports.useAssignBulkMission = exports.useCopyTemplateMission = exports.useUpdateTemplateMission = exports.useTemplateMissionDetail = exports.useTemplateTasksInfinite = exports.useTemplateTasks = exports.useTemplateMissionsInfinite = exports.useTemplateMissions = exports.useRoadmapContext = exports.accountKeys = exports.useMyStudents = exports.useCurrentUserRoles = exports.userKeys = exports.useUser = exports.useUsers = exports.useTaskCreators = exports.useGetDownloadUrl = exports.taskKeys = exports.useBulkUpdateTasks = exports.useBulkDeleteTasks = exports.useDeleteTask = exports.useUpdateTask = exports.useCreateTask = exports.useTasksInfinite = exports.useTasksByRoadmapId = exports.useAllUserTasks = exports.useTasks = exports.missionKeys = exports.useDeleteMission = exports.useUpdateMission = exports.useCreateMission = exports.useMissionsInfinite = exports.useMissions = exports.useOAuthCallback = exports.useOAuth = exports.useAuthState = void 0;
17
+ exports.indigoKeys = exports.useTutorStudents = exports.useStudentTutors = exports.useIndigoMe = exports.studentProfileKeys = exports.useStudentPrefilledInfo = exports.useStudentProfile = exports.useDeleteTemplateMission = exports.useCreateTemplateMission = exports.missionLibraryKeys = exports.useCreateFromPredefinedTasks = exports.useAssignBulkTask = exports.useAssignBulkMission = exports.useCopyTemplateMission = exports.useUpdateTemplateMission = exports.useTemplateMissionDetail = exports.useTemplateTasksInfinite = exports.useTemplateTasks = exports.useTemplateMissionsInfinite = exports.useTemplateMissions = exports.useRoadmapContext = exports.accountKeys = exports.useMyStudents = exports.useCurrentUserRoles = exports.userKeys = exports.useUser = exports.useUsers = exports.useTaskActivities = exports.useTaskCreators = exports.useGetDownloadUrl = exports.taskKeys = exports.useBulkUpdateTasks = exports.useBulkDeleteTasks = exports.useDeleteTask = exports.useUpdateTask = exports.useCreateTask = exports.useTasksInfinite = exports.useTasksByRoadmapId = exports.useAllUserTasks = exports.useTasks = exports.missionKeys = exports.useDeleteMission = exports.useUpdateMission = exports.useCreateMission = exports.useMissionsInfinite = exports.useMissions = exports.useOAuthCallback = exports.useOAuth = exports.useQueryClient = exports.useAuthState = void 0;
18
18
  var useAuthState_1 = require("./useAuthState");
19
19
  Object.defineProperty(exports, "useAuthState", { enumerable: true, get: function () { return useAuthState_1.useAuthState; } });
20
+ var react_query_1 = require("@tanstack/react-query");
21
+ Object.defineProperty(exports, "useQueryClient", { enumerable: true, get: function () { return react_query_1.useQueryClient; } });
20
22
  var useOAuth_1 = require("./useOAuth");
21
23
  Object.defineProperty(exports, "useOAuth", { enumerable: true, get: function () { return useOAuth_1.useOAuth; } });
22
24
  Object.defineProperty(exports, "useOAuthCallback", { enumerable: true, get: function () { return useOAuth_1.useOAuthCallback; } });
@@ -40,6 +42,7 @@ Object.defineProperty(exports, "useBulkUpdateTasks", { enumerable: true, get: fu
40
42
  Object.defineProperty(exports, "taskKeys", { enumerable: true, get: function () { return useTasks_1.taskKeys; } });
41
43
  Object.defineProperty(exports, "useGetDownloadUrl", { enumerable: true, get: function () { return useTasks_1.useGetDownloadUrl; } });
42
44
  Object.defineProperty(exports, "useTaskCreators", { enumerable: true, get: function () { return useTasks_1.useTaskCreators; } });
45
+ Object.defineProperty(exports, "useTaskActivities", { enumerable: true, get: function () { return useTasks_1.useTaskActivities; } });
43
46
  var useUsers_1 = require("./useUsers");
44
47
  Object.defineProperty(exports, "useUsers", { enumerable: true, get: function () { return useUsers_1.useUsers; } });
45
48
  Object.defineProperty(exports, "useUser", { enumerable: true, get: function () { return useUsers_1.useUser; } });
@@ -1,5 +1,5 @@
1
1
  import { InfiniteData } from "@tanstack/react-query";
2
- import type { Task, PaginatedResult, User } from "../../core/types";
2
+ import type { Task, PaginatedResult, User, ActionItemActivityBrief } from "../../core/types";
3
3
  import type { TaskFilters } from "../../core/tasks";
4
4
  export declare const taskKeys: {
5
5
  all: readonly ["tasks"];
@@ -9,9 +9,15 @@ export declare const taskKeys: {
9
9
  details: () => readonly ["tasks", "detail"];
10
10
  detail: (id: string) => readonly ["tasks", "detail", string];
11
11
  creators: (roadmapId: string) => readonly ["tasks", "creators", string];
12
+ activitiesOfTask: (id: string) => readonly ["task-activities", string];
13
+ activity: (id: string, type?: string) => readonly ["task-activities", string, string];
12
14
  };
13
15
  export declare function useTasks(missionId: string | null, enabled: boolean): import("@tanstack/react-query").UseQueryResult<Task[], Error>;
14
16
  export declare function useTaskCreators(roadmapId: string | null, enabled?: boolean): import("@tanstack/react-query").UseQueryResult<User[], Error>;
17
+ export declare function useTaskActivities(actionItemIds: string[], type?: string, enabled?: boolean): {
18
+ data: Record<string, ActionItemActivityBrief[]>;
19
+ isLoading: boolean;
20
+ };
15
21
  export declare function useAllUserTasks(missionLinkIds: string[], enabled: boolean): import("@tanstack/react-query").UseQueryResult<Task[], Error>;
16
22
  export declare function useTasksByRoadmapId(roadmapId: string | null, userId: string | null, enabled: boolean): import("@tanstack/react-query").UseQueryResult<Task[], Error>;
17
23
  /**
@@ -13,6 +13,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.taskKeys = void 0;
14
14
  exports.useTasks = useTasks;
15
15
  exports.useTaskCreators = useTaskCreators;
16
+ exports.useTaskActivities = useTaskActivities;
16
17
  exports.useAllUserTasks = useAllUserTasks;
17
18
  exports.useTasksByRoadmapId = useTasksByRoadmapId;
18
19
  exports.useTasksInfinite = useTasksInfinite;
@@ -23,6 +24,7 @@ exports.useBulkDeleteTasks = useBulkDeleteTasks;
23
24
  exports.useBulkUpdateTasks = useBulkUpdateTasks;
24
25
  exports.useGetDownloadUrl = useGetDownloadUrl;
25
26
  const react_query_1 = require("@tanstack/react-query");
27
+ const react_1 = require("react");
26
28
  const provider_1 = require("../provider");
27
29
  // Query keys
28
30
  exports.taskKeys = {
@@ -33,6 +35,8 @@ exports.taskKeys = {
33
35
  details: () => [...exports.taskKeys.all, "detail"],
34
36
  detail: (id) => [...exports.taskKeys.details(), id],
35
37
  creators: (roadmapId) => [...exports.taskKeys.all, "creators", roadmapId],
38
+ activitiesOfTask: (id) => ["task-activities", id],
39
+ activity: (id, type = "duedate") => [...exports.taskKeys.activitiesOfTask(id), type],
36
40
  };
37
41
  function useTasks(missionId, enabled) {
38
42
  const client = (0, provider_1.useCrimsonClient)();
@@ -61,6 +65,107 @@ function useTaskCreators(roadmapId, enabled = true) {
61
65
  enabled: !!roadmapId && enabled,
62
66
  });
63
67
  }
68
+ function useTaskActivities(actionItemIds, type = "duedate", enabled = true) {
69
+ const client = (0, provider_1.useCrimsonClient)();
70
+ const queryClient = (0, react_query_1.useQueryClient)();
71
+ // Track which IDs we know are in the cache (or have been fetched)
72
+ const [cachedIds, setCachedIds] = (0, react_1.useState)(() => {
73
+ const s = new Set();
74
+ // We only check for existence, not staleness. Stale data will be refetched by useQueries if needed,
75
+ // but we want to prevent the *initial* fetch of empty data which causes the waterfall/storm.
76
+ // However, checking queryCache synchronously here is good for hydration.
77
+ return s;
78
+ });
79
+ // Sync effect to update cachedIds from props?
80
+ // actually, we should just check the cache on render or effect.
81
+ // But doing it in effect allows the batch fetch to be the primary mechanism.
82
+ (0, react_1.useEffect)(() => {
83
+ if (!enabled)
84
+ return;
85
+ // Identify which IDs are missing from our "known cached" set
86
+ // AND missing from the actual query cache (double check to be safe)
87
+ const missing = actionItemIds.filter((id) => {
88
+ if (cachedIds.has(id))
89
+ return false;
90
+ const data = queryClient.getQueryData(exports.taskKeys.activity(id, type));
91
+ if (data) {
92
+ // If it's already there (maybe from another component), we can mark it as cached
93
+ // We'll update state later to avoid set-during-render warning if we did it synchronously above constantly
94
+ return false;
95
+ }
96
+ return true;
97
+ });
98
+ if (missing.length === 0) {
99
+ // If we found some inconsistencies (e.g. they were in queryClient but not in our local state), align them
100
+ if (actionItemIds.length !== cachedIds.size) {
101
+ // This logic is tricky. Let's simplify:
102
+ // Just fetch what we really need.
103
+ }
104
+ return;
105
+ }
106
+ const fetchActivities = () => __awaiter(this, void 0, void 0, function* () {
107
+ try {
108
+ const result = yield client.tasks.getActivities(missing, type);
109
+ // Populate Cache
110
+ Object.entries(result).forEach(([id, activities]) => {
111
+ queryClient.setQueryData(exports.taskKeys.activity(id, type), activities);
112
+ });
113
+ // Also handling IDs that returned no result (empty array) to prevent infinite re-fetching?
114
+ // The API returns Record<string, ...>. If an ID is missing, we should probably set it to empty.
115
+ missing.forEach(id => {
116
+ if (!result[id]) {
117
+ queryClient.setQueryData(exports.taskKeys.activity(id, type), []);
118
+ }
119
+ });
120
+ // Update local state to enable the queries
121
+ setCachedIds((prev) => {
122
+ const next = new Set(prev);
123
+ missing.forEach((id) => next.add(id));
124
+ return next;
125
+ });
126
+ }
127
+ catch (e) {
128
+ console.error("Failed to batch fetch activities", e);
129
+ }
130
+ });
131
+ fetchActivities();
132
+ }, [actionItemIds, type, enabled, client, queryClient, cachedIds]); // cachedIds dep is important
133
+ // Also, we need to make sure we "discover" items that are already cached
134
+ // without fetching them. Use an effect to sync "initially present" items?
135
+ (0, react_1.useEffect)(() => {
136
+ if (!enabled)
137
+ return;
138
+ const present = actionItemIds.filter(id => !cachedIds.has(id) && queryClient.getQueryData(exports.taskKeys.activity(id, type)));
139
+ if (present.length > 0) {
140
+ setCachedIds(prev => {
141
+ const next = new Set(prev);
142
+ present.forEach(id => next.add(id));
143
+ return next;
144
+ });
145
+ }
146
+ }, [actionItemIds, type, enabled, queryClient, cachedIds]);
147
+ const queries = (0, react_query_1.useQueries)({
148
+ queries: actionItemIds.map((id) => ({
149
+ queryKey: exports.taskKeys.activity(id, type),
150
+ queryFn: () => __awaiter(this, void 0, void 0, function* () {
151
+ const result = yield client.tasks.getActivities([id], type);
152
+ return result[id] || [];
153
+ }),
154
+ // Only enable if we have "acknowledged" that the data is ready/cached.
155
+ // OR if it was already in the cache (optimization).
156
+ enabled: enabled && !!id && cachedIds.has(id),
157
+ staleTime: 5 * 60 * 1000,
158
+ })),
159
+ });
160
+ const isLoading = queries.some((q) => q.isLoading);
161
+ const data = {};
162
+ queries.forEach((q, index) => {
163
+ if (q.data) {
164
+ data[actionItemIds[index]] = q.data;
165
+ }
166
+ });
167
+ return { data, isLoading };
168
+ }
64
169
  function useAllUserTasks(missionLinkIds, enabled) {
65
170
  const client = (0, provider_1.useCrimsonClient)();
66
171
  return (0, react_query_1.useQuery)({
@@ -197,8 +302,13 @@ function useUpdateTask(options) {
197
302
  queryClient.setQueryData(JSON.parse(keyStr), old);
198
303
  });
199
304
  },
200
- onSettled: () => {
305
+ onSettled: (data, error, variables) => {
201
306
  queryClient.invalidateQueries({ queryKey: exports.taskKeys.all });
307
+ if (variables.id) {
308
+ queryClient.invalidateQueries({
309
+ queryKey: exports.taskKeys.activitiesOfTask(variables.id),
310
+ });
311
+ }
202
312
  },
203
313
  });
204
314
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crimson-education/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Crimson SDK for accessing Crimson App APIs",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",