diff --git a/src/backend/db/migrations/5_trial_data_belongs_to_benchmark.sql b/src/backend/db/migrations/5_trial_data_belongs_to_benchmark.sql new file mode 100644 index 00000000..42a31694 --- /dev/null +++ b/src/backend/db/migrations/5_trial_data_belongs_to_benchmark.sql @@ -0,0 +1,41 @@ +-- Step 1: Add new benchmark_id column to trial_data +ALTER TABLE trial_data +ADD COLUMN benchmark_id uuid REFERENCES benchmark(benchmark_id); + +-- Step 1a: Add due_date and trial_count to benchmark + +ALTER TABLE benchmark +ADD COLUMN due_date TIMESTAMPTZ; + +ALTER TABLE benchmark +ADD COLUMN trial_count INTEGER; + +-- Step 2: Copy benchmark_id from tasks for existing records +UPDATE trial_data +SET benchmark_id = task.benchmark_id +FROM task +WHERE trial_data.task_id = task.task_id; + +-- Step 2a: Copy due_date and trial_count from tasks for existing records +-- Taking the first result of tasks that matches the right benchmark_id + +UPDATE benchmark +SET due_date = task.due_date, trial_count = task.trial_count +FROM task +WHERE task.benchmark_id = benchmark.benchmark_id; + +-- Step 3: Make benchmark_id required +ALTER TABLE trial_data +ALTER COLUMN benchmark_id SET NOT NULL; + +-- Step 4: Remove task_id column +ALTER TABLE trial_data +DROP COLUMN task_id; + +-- Step 4a: Remove due_date, and trial_count from task + +ALTER TABLE task +DROP COLUMN due_date; + +ALTER TABLE task +DROP COLUMN trial_count; \ No newline at end of file diff --git a/src/backend/db/zapatos/schema.d.ts b/src/backend/db/zapatos/schema.d.ts index b49ab1f6..9de73d6c 100644 --- a/src/backend/db/zapatos/schema.d.ts +++ b/src/backend/db/zapatos/schema.d.ts @@ -419,6 +419,12 @@ declare module 'zapatos/schema' { */ description: string; /** + * **benchmark.due_date** + * - `timestamptz` in database + * - Nullable, no default + */ + due_date: Date | null; + /** * **benchmark.frequency** * - `text` in database * - `NOT NULL`, default: `''::text` @@ -472,6 +478,12 @@ declare module 'zapatos/schema' { * - `NOT NULL`, no default */ target_level: number; + /** + * **benchmark.trial_count** + * - `int4` in database + * - Nullable, no default + */ + trial_count: number | null; } export interface JSONSelectable { /** @@ -511,6 +523,12 @@ declare module 'zapatos/schema' { */ description: string; /** + * **benchmark.due_date** + * - `timestamptz` in database + * - Nullable, no default + */ + due_date: db.TimestampTzString | null; + /** * **benchmark.frequency** * - `text` in database * - `NOT NULL`, default: `''::text` @@ -564,6 +582,12 @@ declare module 'zapatos/schema' { * - `NOT NULL`, no default */ target_level: number; + /** + * **benchmark.trial_count** + * - `int4` in database + * - Nullable, no default + */ + trial_count: number | null; } export interface Whereable { /** @@ -603,6 +627,12 @@ declare module 'zapatos/schema' { */ description?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; /** + * **benchmark.due_date** + * - `timestamptz` in database + * - Nullable, no default + */ + due_date?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** * **benchmark.frequency** * - `text` in database * - `NOT NULL`, default: `''::text` @@ -656,6 +686,12 @@ declare module 'zapatos/schema' { * - `NOT NULL`, no default */ target_level?: number | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** + * **benchmark.trial_count** + * - `int4` in database + * - Nullable, no default + */ + trial_count?: number | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; } export interface Insertable { /** @@ -695,6 +731,12 @@ declare module 'zapatos/schema' { */ description: string | db.Parameter | db.SQLFragment; /** + * **benchmark.due_date** + * - `timestamptz` in database + * - Nullable, no default + */ + due_date?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | null | db.DefaultType | db.SQLFragment; + /** * **benchmark.frequency** * - `text` in database * - `NOT NULL`, default: `''::text` @@ -748,6 +790,12 @@ declare module 'zapatos/schema' { * - `NOT NULL`, no default */ target_level: number | db.Parameter | db.SQLFragment; + /** + * **benchmark.trial_count** + * - `int4` in database + * - Nullable, no default + */ + trial_count?: number | db.Parameter | null | db.DefaultType | db.SQLFragment; } export interface Updatable { /** @@ -787,6 +835,12 @@ declare module 'zapatos/schema' { */ description?: string | db.Parameter | db.SQLFragment | db.SQLFragment | db.SQLFragment>; /** + * **benchmark.due_date** + * - `timestamptz` in database + * - Nullable, no default + */ + due_date?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; + /** * **benchmark.frequency** * - `text` in database * - `NOT NULL`, default: `''::text` @@ -840,6 +894,12 @@ declare module 'zapatos/schema' { * - `NOT NULL`, no default */ target_level?: number | db.Parameter | db.SQLFragment | db.SQLFragment | db.SQLFragment>; + /** + * **benchmark.trial_count** + * - `int4` in database + * - Nullable, no default + */ + trial_count?: number | db.Parameter | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; } export type UniqueIndex = 'benchmark_pkey'; export type Column = keyof Selectable; @@ -2025,12 +2085,6 @@ declare module 'zapatos/schema' { */ created_at: Date; /** - * **task.due_date** - * - `timestamptz` in database - * - Nullable, no default - */ - due_date: Date | null; - /** * **task.seen** * - `bool` in database * - `NOT NULL`, default: `false` @@ -2042,12 +2096,6 @@ declare module 'zapatos/schema' { * - `NOT NULL`, default: `uuid_generate_v4()` */ task_id: string; - /** - * **task.trial_count** - * - `int4` in database - * - Nullable, no default - */ - trial_count: number | null; } export interface JSONSelectable { /** @@ -2069,12 +2117,6 @@ declare module 'zapatos/schema' { */ created_at: db.TimestampTzString; /** - * **task.due_date** - * - `timestamptz` in database - * - Nullable, no default - */ - due_date: db.TimestampTzString | null; - /** * **task.seen** * - `bool` in database * - `NOT NULL`, default: `false` @@ -2086,12 +2128,6 @@ declare module 'zapatos/schema' { * - `NOT NULL`, default: `uuid_generate_v4()` */ task_id: string; - /** - * **task.trial_count** - * - `int4` in database - * - Nullable, no default - */ - trial_count: number | null; } export interface Whereable { /** @@ -2113,12 +2149,6 @@ declare module 'zapatos/schema' { */ created_at?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; /** - * **task.due_date** - * - `timestamptz` in database - * - Nullable, no default - */ - due_date?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; - /** * **task.seen** * - `bool` in database * - `NOT NULL`, default: `false` @@ -2130,12 +2160,6 @@ declare module 'zapatos/schema' { * - `NOT NULL`, default: `uuid_generate_v4()` */ task_id?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; - /** - * **task.trial_count** - * - `int4` in database - * - Nullable, no default - */ - trial_count?: number | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; } export interface Insertable { /** @@ -2157,12 +2181,6 @@ declare module 'zapatos/schema' { */ created_at?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | db.DefaultType | db.SQLFragment; /** - * **task.due_date** - * - `timestamptz` in database - * - Nullable, no default - */ - due_date?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | null | db.DefaultType | db.SQLFragment; - /** * **task.seen** * - `bool` in database * - `NOT NULL`, default: `false` @@ -2174,12 +2192,6 @@ declare module 'zapatos/schema' { * - `NOT NULL`, default: `uuid_generate_v4()` */ task_id?: string | db.Parameter | db.DefaultType | db.SQLFragment; - /** - * **task.trial_count** - * - `int4` in database - * - Nullable, no default - */ - trial_count?: number | db.Parameter | null | db.DefaultType | db.SQLFragment; } export interface Updatable { /** @@ -2201,12 +2213,6 @@ declare module 'zapatos/schema' { */ created_at?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | db.DefaultType | db.SQLFragment | db.SQLFragment | db.DefaultType | db.SQLFragment>; /** - * **task.due_date** - * - `timestamptz` in database - * - Nullable, no default - */ - due_date?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; - /** * **task.seen** * - `bool` in database * - `NOT NULL`, default: `false` @@ -2218,12 +2224,6 @@ declare module 'zapatos/schema' { * - `NOT NULL`, default: `uuid_generate_v4()` */ task_id?: string | db.Parameter | db.DefaultType | db.SQLFragment | db.SQLFragment | db.DefaultType | db.SQLFragment>; - /** - * **task.trial_count** - * - `int4` in database - * - Nullable, no default - */ - trial_count?: number | db.Parameter | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; } export type UniqueIndex = 'benchmark_assignee_unique' | 'task_pkey'; export type Column = keyof Selectable; @@ -2239,6 +2239,12 @@ declare module 'zapatos/schema' { export namespace trial_data { export type Table = 'trial_data'; export interface Selectable { + /** + * **trial_data.benchmark_id** + * - `uuid` in database + * - `NOT NULL`, no default + */ + benchmark_id: string; /** * **trial_data.created_at** * - `timestamptz` in database @@ -2270,12 +2276,6 @@ declare module 'zapatos/schema' { */ success: number; /** - * **trial_data.task_id** - * - `uuid` in database - * - Nullable, no default - */ - task_id: string | null; - /** * **trial_data.trial_data_id** * - `uuid` in database * - `NOT NULL`, default: `uuid_generate_v4()` @@ -2289,6 +2289,12 @@ declare module 'zapatos/schema' { unsuccess: number; } export interface JSONSelectable { + /** + * **trial_data.benchmark_id** + * - `uuid` in database + * - `NOT NULL`, no default + */ + benchmark_id: string; /** * **trial_data.created_at** * - `timestamptz` in database @@ -2320,12 +2326,6 @@ declare module 'zapatos/schema' { */ success: number; /** - * **trial_data.task_id** - * - `uuid` in database - * - Nullable, no default - */ - task_id: string | null; - /** * **trial_data.trial_data_id** * - `uuid` in database * - `NOT NULL`, default: `uuid_generate_v4()` @@ -2339,6 +2339,12 @@ declare module 'zapatos/schema' { unsuccess: number; } export interface Whereable { + /** + * **trial_data.benchmark_id** + * - `uuid` in database + * - `NOT NULL`, no default + */ + benchmark_id?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; /** * **trial_data.created_at** * - `timestamptz` in database @@ -2370,12 +2376,6 @@ declare module 'zapatos/schema' { */ success?: number | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; /** - * **trial_data.task_id** - * - `uuid` in database - * - Nullable, no default - */ - task_id?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; - /** * **trial_data.trial_data_id** * - `uuid` in database * - `NOT NULL`, default: `uuid_generate_v4()` @@ -2389,6 +2389,12 @@ declare module 'zapatos/schema' { unsuccess?: number | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; } export interface Insertable { + /** + * **trial_data.benchmark_id** + * - `uuid` in database + * - `NOT NULL`, no default + */ + benchmark_id: string | db.Parameter | db.SQLFragment; /** * **trial_data.created_at** * - `timestamptz` in database @@ -2420,12 +2426,6 @@ declare module 'zapatos/schema' { */ success: number | db.Parameter | db.SQLFragment; /** - * **trial_data.task_id** - * - `uuid` in database - * - Nullable, no default - */ - task_id?: string | db.Parameter | null | db.DefaultType | db.SQLFragment; - /** * **trial_data.trial_data_id** * - `uuid` in database * - `NOT NULL`, default: `uuid_generate_v4()` @@ -2439,6 +2439,12 @@ declare module 'zapatos/schema' { unsuccess: number | db.Parameter | db.SQLFragment; } export interface Updatable { + /** + * **trial_data.benchmark_id** + * - `uuid` in database + * - `NOT NULL`, no default + */ + benchmark_id?: string | db.Parameter | db.SQLFragment | db.SQLFragment | db.SQLFragment>; /** * **trial_data.created_at** * - `timestamptz` in database @@ -2470,12 +2476,6 @@ declare module 'zapatos/schema' { */ success?: number | db.Parameter | db.SQLFragment | db.SQLFragment | db.SQLFragment>; /** - * **trial_data.task_id** - * - `uuid` in database - * - Nullable, no default - */ - task_id?: string | db.Parameter | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; - /** * **trial_data.trial_data_id** * - `uuid` in database * - `NOT NULL`, default: `uuid_generate_v4()` diff --git a/src/backend/routers/iep.test.ts b/src/backend/routers/iep.test.ts index f37b97b3..e5e767f9 100644 --- a/src/backend/routers/iep.test.ts +++ b/src/backend/routers/iep.test.ts @@ -72,16 +72,16 @@ test("basic flow - add/get goals, benchmarks, tasks", async (t) => { await trpc.iep.addTask.mutate({ benchmark_id: benchmark1Id, assignee_id: para_id, - due_date: new Date("2023-12-31"), - trial_count: 5, }); const assignTask = await trpc.iep.assignTaskToParas.mutate({ benchmark_id: benchmark2Id, para_ids: [para_id], + due_date: new Date("2023-12-31"), + trial_count: 5, }); t.is(assignTask?.benchmark_id, benchmark2Id); - t.is(assignTask?.assignee_id, para_id); + t.is(assignTask?.assignees?.[0]?.assignee_id, para_id); const gotGoals = await trpc.iep.getGoals.query({ iep_id: iep.iep_id }); t.is(gotGoals.length, 1); @@ -94,7 +94,9 @@ test("basic flow - add/get goals, benchmarks, tasks", async (t) => { const gotBenchmark = await trpc.iep.getBenchmark.query({ benchmark_id: benchmark2Id, }); - t.is(gotBenchmark[0].description, "benchmark 2"); + t.is(gotBenchmark.description, "benchmark 2"); + t.deepEqual(gotBenchmark.due_date, new Date("2023-12-31")); + t.is(gotBenchmark.trial_count, 5); // TODO: Don't query db directly and use an API method instead. Possibly create a getTasks method later t.truthy( @@ -139,22 +141,20 @@ test("addTask - no duplicate benchmark_id + assigned_id combo", async (t) => { metric_name: "words", attempts_per_trial: 10, number_of_trials: 30, + due_date: new Date("2023-12-31"), + trial_count: 5, }); const benchmark1Id = benchmark1!.benchmark_id; await trpc.iep.addTask.mutate({ benchmark_id: benchmark1Id, assignee_id: para_id, - due_date: new Date("2023-12-31"), - trial_count: 5, }); const error = await t.throwsAsync(async () => { await trpc.iep.addTask.mutate({ benchmark_id: benchmark1Id, assignee_id: para_id, - due_date: new Date("2024-03-31"), - trial_count: 1, }); }); @@ -165,7 +165,7 @@ test("addTask - no duplicate benchmark_id + assigned_id combo", async (t) => { }); test("assignTaskToParas - no duplicate benchmark_id + para_id combo", async (t) => { - const { trpc, seed } = await getTestServer(t, { + const { trpc, db, seed } = await getTestServer(t, { authenticateAs: UserType.CaseManager, }); @@ -212,19 +212,20 @@ test("assignTaskToParas - no duplicate benchmark_id + para_id combo", async (t) trial_count: 5, }); - const error = await t.throwsAsync(async () => { - await trpc.iep.assignTaskToParas.mutate({ - benchmark_id: benchmark1Id, - para_ids: [para_1.user_id, para_2.user_id], - due_date: new Date("2024-03-31"), - trial_count: 1, - }); + await trpc.iep.assignTaskToParas.mutate({ + benchmark_id: benchmark1Id, + para_ids: [para_1.user_id, para_2.user_id], + due_date: new Date("2023-12-31"), + trial_count: 5, }); - t.is( - error?.message, - "Task already exists: This benchmark has already been assigned to one or more of these paras" - ); + const result = await db + .selectFrom("task") + .where("benchmark_id", "=", benchmark1Id) + .selectAll() + .execute(); + + t.is(result.length, 2); }); test("add benchmark - check full schema", async (t) => { diff --git a/src/backend/routers/iep.ts b/src/backend/routers/iep.ts index 5db3a2d3..a824ce74 100644 --- a/src/backend/routers/iep.ts +++ b/src/backend/routers/iep.ts @@ -86,6 +86,8 @@ export const iep = router({ metric_name: z.string(), attempts_per_trial: z.number().nullable(), number_of_trials: z.number().nullable(), + due_date: z.date().nullable().optional(), + trial_count: z.number().nullable().optional(), }) ) .mutation(async (req) => { @@ -102,6 +104,8 @@ export const iep = router({ metric_name, attempts_per_trial, number_of_trials, + due_date, + trial_count, } = req.input; const result = await req.ctx.db @@ -119,6 +123,8 @@ export const iep = router({ metric_name, attempts_per_trial, number_of_trials, + due_date, + trial_count, }) .returningAll() .executeTakeFirst(); @@ -126,17 +132,78 @@ export const iep = router({ return result; }), + updateBenchmark: hasPara + .input( + z.object({ + benchmark_id: z.string(), + goal_id: z.string().optional(), + status: z.string().optional(), + description: z.string().optional(), + setup: z.string().optional(), + instructions: z.string().optional(), + materials: z.string().optional(), + frequency: z.string().optional(), + target_level: z.number().min(0).max(100).optional(), + baseline_level: z.number().min(0).max(100).optional(), + metric_name: z.string().optional(), + attempts_per_trial: z.number().nullable().optional(), + number_of_trials: z.number().nullable().optional(), + due_date: z.date().nullable().optional(), + trial_count: z.number().nullable().optional(), + }) + ) + .mutation(async (req) => { + const { + benchmark_id, + goal_id, + status, + description, + setup, + instructions, + materials, + frequency, + target_level, + baseline_level, + metric_name, + attempts_per_trial, + number_of_trials, + due_date, + trial_count, + } = req.input; + + const benchmark = await req.ctx.db + .updateTable("benchmark") + .set({ + goal_id, + status, + description, + setup, + instructions, + materials, + frequency, + target_level, + baseline_level, + metric_name, + attempts_per_trial, + number_of_trials, + due_date, + trial_count, + }) + .where("benchmark.benchmark_id", "=", benchmark_id) + .returningAll() + .executeTakeFirstOrThrow(); + return benchmark; + }), + addTask: hasCaseManager .input( z.object({ benchmark_id: z.string(), assignee_id: z.string(), - due_date: z.date(), - trial_count: z.number(), }) ) .mutation(async (req) => { - const { benchmark_id, assignee_id, due_date, trial_count } = req.input; + const { benchmark_id, assignee_id } = req.input; const existingTask = await req.ctx.db .selectFrom("task") @@ -156,8 +223,6 @@ export const iep = router({ .values({ benchmark_id, assignee_id, - due_date, - trial_count, }) .returningAll() .executeTakeFirst(); @@ -169,87 +234,97 @@ export const iep = router({ z.object({ benchmark_id: z.string().uuid(), para_ids: z.string().uuid().array(), - due_date: z.date().optional(), - trial_count: z.number().optional(), + due_date: z.date().nullable(), + trial_count: z.number().nullable(), }) ) .mutation(async (req) => { const { benchmark_id, para_ids, due_date, trial_count } = req.input; + // fetch all existing task records for this benchmark const existingTasks = await req.ctx.db .selectFrom("task") .where("benchmark_id", "=", benchmark_id) - .where("assignee_id", "in", para_ids) .selectAll() .execute(); - if (existingTasks.length > 0) { - throw new Error( - "Task already exists: This benchmark has already been assigned to one or more of these paras" - ); - } - - const result = await req.ctx.db - .insertInto("task") - .values( - para_ids.map((para_id) => ({ - benchmark_id, - assignee_id: para_id, + // collect list of task records to delete (if assignee_id not in para_ids) + const deleteTaskIds = existingTasks + .filter( + (task) => task.assignee_id && !para_ids.includes(task.assignee_id) + ) + .map((task) => task.task_id); + + // collect a list of new assignee_ids to insert + const newAssigneeIds = para_ids.filter( + (para_id) => !existingTasks.find((task) => task.assignee_id === para_id) + ); + + return await req.ctx.db.transaction().execute(async (trx) => { + // delete tasks whose assignees that are not in para_ids + if (deleteTaskIds.length > 0) { + await trx + .deleteFrom("task") + .where("task_id", "in", deleteTaskIds) + .execute(); + } + // insert new tasks for new assignees + if (newAssigneeIds.length > 0) { + await trx + .insertInto("task") + .values( + newAssigneeIds.map((para_id) => ({ + benchmark_id, + assignee_id: para_id, + })) + ) + .returningAll() + .executeTakeFirst(); + } + // update benchmark + const result = await trx + .updateTable("benchmark") + .set({ due_date, trial_count, - })) - ) - .returningAll() - .executeTakeFirst(); - return result; - }), - //Temporary function to easily assign tasks to self for testing - tempAddTaskToSelf: hasCaseManager - .input( - z.object({ - benchmark_id: z.string(), - due_date: z.date(), - trial_count: z.number(), - }) - ) - .mutation(async (req) => { - const { benchmark_id, due_date, trial_count } = req.input; - const { userId } = req.ctx.auth; - - const shouldAdd = await req.ctx.db - .selectFrom("task") - .selectAll() - .where((eb) => - eb.and([ - eb("benchmark_id", "=", benchmark_id), - eb("assignee_id", "=", userId), + }) + .where("benchmark.benchmark_id", "=", benchmark_id) + .returning((eb) => [ + "benchmark.benchmark_id", + "benchmark.status", + "benchmark.description", + "benchmark.instructions", + "benchmark.materials", + "benchmark.metric_name", + "benchmark.setup", + "benchmark.frequency", + "benchmark.number_of_trials", + "benchmark.attempts_per_trial", + "benchmark.trial_count", + "benchmark.baseline_level", + "benchmark.current_level", + "benchmark.target_level", + "benchmark.created_at", + "benchmark.due_date", + "benchmark.goal_id", + jsonArrayFrom( + eb + .selectFrom("user") + .innerJoin("task", "task.assignee_id", "user.user_id") + .whereRef("task.benchmark_id", "=", "benchmark.benchmark_id") + .orderBy("user.first_name") + .selectAll() + ).as("assignees"), ]) - ) - .executeTakeFirst(); - - // Prevent multiple assignments of the same task - if (shouldAdd !== undefined) { - return null; - } - - const result = await req.ctx.db - .insertInto("task") - .values({ - benchmark_id, - assignee_id: userId, - due_date, - trial_count, - }) - .returningAll() - .executeTakeFirst(); - - return result; + .executeTakeFirstOrThrow(); + return result; + }); }), addTrialData: hasPara .input( z.object({ - task_id: z.string(), + benchmark_id: z.string(), success: z.number(), unsuccess: z.number(), notes: z.string(), @@ -258,12 +333,12 @@ export const iep = router({ .mutation(async (req) => { const { userId } = req.ctx.auth; - const { task_id, success, unsuccess, notes } = req.input; + const { benchmark_id, success, unsuccess, notes } = req.input; const result = await req.ctx.db .insertInto("trial_data") .values({ - task_id, + benchmark_id, created_by_user_id: userId, success, unsuccess, @@ -348,7 +423,33 @@ export const iep = router({ const result = await req.ctx.db .selectFrom("benchmark") .where("goal_id", "=", goal_id) - .selectAll() + .select((eb) => [ + "benchmark.benchmark_id", + "benchmark.status", + "benchmark.description", + "benchmark.instructions", + "benchmark.materials", + "benchmark.metric_name", + "benchmark.setup", + "benchmark.frequency", + "benchmark.number_of_trials", + "benchmark.attempts_per_trial", + "benchmark.trial_count", + "benchmark.baseline_level", + "benchmark.current_level", + "benchmark.target_level", + "benchmark.created_at", + "benchmark.due_date", + "benchmark.goal_id", + jsonArrayFrom( + eb + .selectFrom("user") + .innerJoin("task", "task.assignee_id", "user.user_id") + .whereRef("task.benchmark_id", "=", "benchmark.benchmark_id") + .orderBy("user.first_name") + .selectAll() + ).as("assignees"), + ]) .execute(); return result; @@ -366,8 +467,35 @@ export const iep = router({ const result = await req.ctx.db .selectFrom("benchmark") .where("benchmark.benchmark_id", "=", benchmark_id) - .selectAll() - .execute(); + .select((eb) => [ + "benchmark.benchmark_id", + "benchmark.status", + "benchmark.description", + "benchmark.instructions", + "benchmark.materials", + "benchmark.metric_name", + "benchmark.setup", + "benchmark.frequency", + "benchmark.number_of_trials", + "benchmark.attempts_per_trial", + "benchmark.trial_count", + "benchmark.baseline_level", + "benchmark.current_level", + "benchmark.target_level", + "benchmark.created_at", + "benchmark.due_date", + "benchmark.goal_id", + jsonArrayFrom( + eb + .selectFrom("user") + .innerJoin("task", "task.assignee_id", "user.user_id") + .whereRef("task.benchmark_id", "=", "benchmark.benchmark_id") + .orderBy("user.first_name") + .selectAll() + ).as("assignees"), + ]) + .executeTakeFirstOrThrow(); + return result; }), @@ -390,24 +518,24 @@ export const iep = router({ return result; }), + // Not just hasPara, but check to make sure that the userId has a task with the benchmark_id getBenchmarkAndTrialData: hasPara .input( z.object({ - task_id: z.string(), + benchmark_id: z.string(), }) ) .query(async (req) => { - const { task_id } = req.input; + const { benchmark_id } = req.input; const result = await req.ctx.db .selectFrom("benchmark") - .innerJoin("task", "benchmark.benchmark_id", "task.benchmark_id") .innerJoin("goal", "benchmark.goal_id", "goal.goal_id") .innerJoin("iep", "goal.iep_id", "iep.iep_id") .innerJoin("student", "iep.student_id", "student.student_id") - .where("task.task_id", "=", task_id) + .where("benchmark.benchmark_id", "=", benchmark_id) .select((eb) => [ - "task.task_id", + "benchmark.benchmark_id", "student.first_name", "student.last_name", "goal.category", @@ -416,9 +544,8 @@ export const iep = router({ "benchmark.frequency", "benchmark.number_of_trials", "benchmark.benchmark_id", - "task.due_date", - "task.seen", - "task.trial_count", + "benchmark.due_date", + "benchmark.trial_count", jsonArrayFrom( eb .selectFrom("trial_data") @@ -440,11 +567,10 @@ export const iep = router({ .selectAll("file") ).as("files"), ]) - .whereRef("trial_data.task_id", "=", "task.task_id") .whereRef( - "trial_data.created_by_user_id", + "trial_data.benchmark_id", "=", - "task.assignee_id" + "benchmark.benchmark_id" ) .orderBy("trial_data.created_at") ).as("trials"), @@ -457,19 +583,34 @@ export const iep = router({ markAsSeen: hasPara .input( z.object({ - task_id: z.string(), + benchmark_id: z.string(), }) ) .mutation(async (req) => { - const { task_id } = req.input; + const { benchmark_id } = req.input; + const { userId } = req.ctx.auth; - await req.ctx.db - .updateTable("task") - .set({ - seen: true, - }) - .where("task.task_id", "=", task_id) - .execute(); + const task = await req.ctx.db + .selectFrom("task") + .where("benchmark_id", "=", benchmark_id) + .where("assignee_id", "=", userId) + .selectAll() + .executeTakeFirst(); + + if (task?.seen) { + await req.ctx.db + .updateTable("task") + .set({ + seen: true, + }) + .where((eb) => + eb.and([ + eb("benchmark_id", "=", benchmark_id), + eb("assignee_id", "=", userId), + ]) + ) + .execute(); + } }), attachFileToTrialData: hasCaseManager diff --git a/src/backend/routers/para.test.ts b/src/backend/routers/para.test.ts index 29e51749..0cfa8810 100644 --- a/src/backend/routers/para.test.ts +++ b/src/backend/routers/para.test.ts @@ -228,6 +228,8 @@ test("getMyTasks", async (t) => { target_level: 100, baseline_level: 20, metric_name: "words", + due_date: DUE_DATE, + trial_count: TRIAL_COUNT, attempts_per_trial: ATTEMPTS_PER_TRIAL, number_of_trials: NUMBER_OF_TRIALS, }) @@ -239,8 +241,6 @@ test("getMyTasks", async (t) => { .values({ benchmark_id: benchmark_id, assignee_id: seed.case_manager.user_id, - due_date: DUE_DATE, - trial_count: TRIAL_COUNT, }) .returningAll() .executeTakeFirstOrThrow(); diff --git a/src/backend/routers/para.ts b/src/backend/routers/para.ts index 78661095..9dc6bb61 100644 --- a/src/backend/routers/para.ts +++ b/src/backend/routers/para.ts @@ -76,19 +76,18 @@ export const para = router({ "student.first_name", "student.last_name", "goal.category", + "benchmark.benchmark_id", "benchmark.description", "benchmark.instructions", "benchmark.attempts_per_trial", "benchmark.number_of_trials", - "task.due_date", + "benchmark.due_date", "task.seen", - "task.trial_count", + "benchmark.trial_count", "task.created_at", - eb .selectFrom("trial_data") - .whereRef("trial_data.task_id", "=", "task.task_id") - .where("trial_data.created_by_user_id", "=", userId) + .whereRef("trial_data.benchmark_id", "=", "benchmark.benchmark_id") .where("trial_data.submitted", "=", true) .select(({ fn }) => fn.count("trial_data.trial_data_id").as("completed_trials") diff --git a/src/backend/routers/student.ts b/src/backend/routers/student.ts index 23956056..bcacd991 100644 --- a/src/backend/routers/student.ts +++ b/src/backend/routers/student.ts @@ -29,7 +29,11 @@ export const student = router({ .innerJoin("iep", "iep.iep_id", "goal.iep_id") .innerJoin("student", "student.student_id", "iep.student_id") .where("task.task_id", "=", task_id) - .select(["student.first_name", "student.last_name", "task.due_date"]) + .select([ + "student.first_name", + "student.last_name", + "benchmark.due_date", + ]) .executeTakeFirstOrThrow(); return result; diff --git a/src/client/lib/trpc.ts b/src/client/lib/trpc.ts index 10b65b63..32aea15e 100644 --- a/src/client/lib/trpc.ts +++ b/src/client/lib/trpc.ts @@ -1,4 +1,12 @@ -import { createTRPCReact } from "@trpc/react-query"; +import { + createTRPCReact, + type inferReactQueryProcedureOptions, +} from "@trpc/react-query"; +import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server"; import { AppRouter } from "@/backend/routers/_app"; +export type ReactQueryOptions = inferReactQueryProcedureOptions; +export type RouterInputs = inferRouterInputs; +export type RouterOutputs = inferRouterOutputs; + export const trpc = createTRPCReact(); diff --git a/src/components/benchmarks/BenchmarkAssignees.tsx b/src/components/benchmarks/BenchmarkAssignees.tsx new file mode 100644 index 00000000..47e0579d --- /dev/null +++ b/src/components/benchmarks/BenchmarkAssignees.tsx @@ -0,0 +1,55 @@ +import { Box, Button, Stack } from "@mui/material"; +import { format } from "date-fns"; + +import $button from "@/components/design_system/button/Button.module.css"; +import { Benchmark } from "@/types/global"; + +const BenchmarkAssignees = ({ + benchmark, + onAssign, +}: { + benchmark: Benchmark; + onAssign: () => void; +}) => { + const { assignees, due_date, trial_count } = benchmark; + + return ( + <> + {assignees.length === 0 && ( + + )} + {!!assignees.length && ( + + + {assignees.map((u) => ( + + {u.first_name} {u.last_name} + + ))} + {due_date && Until {format(new Date(due_date), "MMM d")}} + {!due_date && !!trial_count && {trial_count} times} + {!due_date && !trial_count && Until unassigned} + + + + + + )} + + ); +}; + +export default BenchmarkAssignees; diff --git a/src/components/benchmarks/BenchmarkAssignmentModal.tsx b/src/components/benchmarks/BenchmarkAssignmentModal.tsx index 2d52868d..68a177ea 100644 --- a/src/components/benchmarks/BenchmarkAssignmentModal.tsx +++ b/src/components/benchmarks/BenchmarkAssignmentModal.tsx @@ -9,7 +9,7 @@ import { DialogContent, DialogActions, } from "@mui/material"; -import { useState, useRef } from "react"; +import { useState, useRef, useEffect } from "react"; import $benchmark from "./BenchmarkAssignmentModal.module.css"; import $button from "@/components/design_system/button/Button.module.css"; @@ -18,10 +18,11 @@ import { DurationSelectionStep, } from "./Duration-Selection-Step"; import DS_Checkbox from "../design_system/checkbox/Checkbox"; +import { Benchmark } from "@/types/global"; interface BenchmarkAssignmentModalProps { isOpen: boolean; - onClose: () => void; + onClose: (benchmark?: Benchmark) => void; benchmark_id: string; } @@ -50,9 +51,32 @@ export const BenchmarkAssignmentModal = ( const [currentModalSelection, setCurrentModalSelection] = useState("PARA_SELECTION"); const { data: myParas } = trpc.case_manager.getMyParas.useQuery(); - const { data: benchmark } = trpc.iep.getBenchmark.useQuery({ - benchmark_id: props.benchmark_id, - }); + const { data: benchmark, isError: benchmarkFetchError } = + trpc.iep.getBenchmark.useQuery({ + benchmark_id: props.benchmark_id, + }); + + useEffect(() => { + const paraIds = + benchmark?.assignees + .map(({ assignee_id }) => assignee_id) + .filter((ele) => ele !== null) ?? []; + + setSelectedParaIds(paraIds); + if (benchmark?.trial_count !== null) { + setAssignmentDuration({ + type: "minimum_number_of_collections", + minimumNumberOfCollections: benchmark?.trial_count ?? 1, + }); + } else if (benchmark?.due_date !== null) { + setAssignmentDuration({ + type: "until_date", + date: new Date(benchmark.due_date), + }); + } else { + setAssignmentDuration({ type: "forever" }); + } + }, [benchmark]); const [errorMessage, setErrorMessage] = useState(""); @@ -69,8 +93,8 @@ export const BenchmarkAssignmentModal = ( }); }; - const handleClose = () => { - props.onClose(); + const handleClose = (benchmark?: Benchmark) => { + props.onClose(benchmark); setSelectedParaIds([]); setErrorMessage(""); setCurrentModalSelection("PARA_SELECTION"); @@ -95,19 +119,19 @@ export const BenchmarkAssignmentModal = ( } else { // Reached end, save try { - await assignTaskToPara.mutateAsync({ + const benchmark = await assignTaskToPara.mutateAsync({ benchmark_id: props.benchmark_id, para_ids: selectedParaIds, due_date: assignmentDuration.type === "until_date" ? assignmentDuration.date - : undefined, + : null, trial_count: assignmentDuration.type === "minimum_number_of_collections" ? assignmentDuration.minimumNumberOfCollections - : undefined, + : null, }); - handleClose(); + handleClose(benchmark); } catch (err) { // TODO: issue #450 console.log(err); @@ -118,10 +142,15 @@ export const BenchmarkAssignmentModal = ( } }; + // does this matter to prevent error typed values? + if (benchmarkFetchError) { + return
Oops! Error fetching benchmark
; + } + return ( handleClose()} className={$benchmark.assignBenchmarkModal} maxWidth="sm" fullWidth @@ -133,14 +162,9 @@ export const BenchmarkAssignmentModal = (

Benchmark

- {benchmark?.map((thisBenchmark) => ( -

- {thisBenchmark.description} -

- ))} +

+ {benchmark?.description} +

{currentModalSelection === "PARA_SELECTION" && ( diff --git a/src/components/benchmarks/BenchmarkListElement.tsx b/src/components/benchmarks/BenchmarkListElement.tsx index 24d22297..247bbeab 100644 --- a/src/components/benchmarks/BenchmarkListElement.tsx +++ b/src/components/benchmarks/BenchmarkListElement.tsx @@ -1,16 +1,20 @@ -import { Benchmark } from "@/types/global"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Divider from "@mui/material/Divider"; import ContentPasteIcon from "@mui/icons-material/ContentPaste"; import { useState, type ReactNode } from "react"; -import { BenchmarkAssignmentModal } from "./BenchmarkAssignmentModal"; import $button from "@/components/design_system/button/Button.module.css"; import { format } from "date-fns"; import Typography from "@mui/material/Typography"; + +import { BenchmarkAssignmentModal } from "./BenchmarkAssignmentModal"; +import BenchmarkAssignees from "./BenchmarkAssignees"; +import { Benchmark } from "@/types/global"; + interface BenchmarkProps { benchmark: Benchmark; index?: number; + onUpdate: (benchmark: Benchmark) => void; } interface InfoProps { @@ -42,8 +46,20 @@ const Info = ({ description, children }: InfoProps) => { ); }; -const BenchmarkListElement = ({ benchmark, index }: BenchmarkProps) => { +const BenchmarkListElement = ({ + benchmark, + index, + onUpdate, +}: BenchmarkProps) => { const [isAssignmentModalOpen, setIsAssignmentModalOpen] = useState(false); + + const closeModal = (benchmark?: Benchmark) => { + setIsAssignmentModalOpen(false); + if (benchmark) { + onUpdate(benchmark); + } + }; + return ( { display="block" gutterBottom > - #{(index ?? 0) + 1} created on {format(benchmark.created_at, "P")} + #{(index ?? 0) + 1} created on {format(benchmark?.created_at, "P")} @@ -112,6 +128,12 @@ const BenchmarkListElement = ({ benchmark, index }: BenchmarkProps) => { {" "} {benchmark?.number_of_trials || "N/A"} + + setIsAssignmentModalOpen(true)} + /> + { - - - - setIsAssignmentModalOpen(false)} - benchmark_id={benchmark.benchmark_id} - /> + {isAssignmentModalOpen && ( + + )} ); }; diff --git a/src/components/benchmarks/BenchmarksContainer.tsx b/src/components/benchmarks/BenchmarksContainer.tsx index 7f16765c..1f62b896 100644 --- a/src/components/benchmarks/BenchmarksContainer.tsx +++ b/src/components/benchmarks/BenchmarksContainer.tsx @@ -55,8 +55,10 @@ const SelectableTab = ({ export default function BenchmarksContainer({ benchmarks, + onUpdate, }: { benchmarks: Benchmark[]; + onUpdate: (benchmark: Benchmark) => void; }) { const router = useRouter(); const [activeTab, setActiveTab] = useState( @@ -122,6 +124,7 @@ export default function BenchmarksContainer({ key={benchmark.benchmark_id} benchmark={benchmark} index={index} + onUpdate={onUpdate} /> )) ); diff --git a/src/components/taskCard/taskCard.tsx b/src/components/taskCard/taskCard.tsx index 0ac27a0e..bff29645 100644 --- a/src/components/taskCard/taskCard.tsx +++ b/src/components/taskCard/taskCard.tsx @@ -6,6 +6,7 @@ import { useMemo } from "react"; import $taskCard from "./TaskCard.module.css"; interface ParaTaskCard { + // this should be based on TaskData, maybe have some Omit's. task_id: string; first_name: string; last_name: string; @@ -17,6 +18,7 @@ interface ParaTaskCard { seen: boolean; trial_count: number | null; completed_trials: string | number | bigint | null; + benchmark_id: string; } interface TaskCardProps { @@ -74,7 +76,7 @@ const TaskCard = ({ task, isPara }: TaskCardProps) => { {/* Para smaller screen view can click on card instead */} {!isPara ? ( View benchmark @@ -82,7 +84,7 @@ const TaskCard = ({ task, isPara }: TaskCardProps) => { ) : null} = 100 ? $button.inactive : "" }`} diff --git a/src/pages/benchmarks/[benchmark_id]/index.tsx b/src/pages/benchmarks/[benchmark_id]/index.tsx index c4e5aa2a..d29dda44 100644 --- a/src/pages/benchmarks/[benchmark_id]/index.tsx +++ b/src/pages/benchmarks/[benchmark_id]/index.tsx @@ -30,12 +30,12 @@ const BenchmarkPage = () => { const { benchmark_id } = router.query; const utils = trpc.useContext(); const { - data: task, - isLoading: taskIsLoading, + data: benchmark, + isLoading: benchmarkIsLoading, isError, } = trpc.iep.getBenchmarkAndTrialData.useQuery( { - task_id: benchmark_id as string, + benchmark_id: benchmark_id as string, // how does this line make sense? }, { enabled: Boolean(benchmark_id), @@ -69,7 +69,7 @@ const BenchmarkPage = () => { const [unsuccessInputValue, setUnsuccessInputValue] = useState(0); const [currentTrialIdx, setCurrentTrialIdx] = useState(0); - const currentTrial = task?.trials[currentTrialIdx] || null; + const currentTrial = benchmark?.trials[currentTrialIdx] || null; const trialAddedRef = useRef(false); @@ -80,10 +80,10 @@ const BenchmarkPage = () => { // Sets the current trial to most recent whenever a new task is loaded. useEffect(() => { - if (task && task.trials.length > 0) { - setCurrentTrialIdx(task.trials.length - 1); + if (benchmark && benchmark.trials.length > 0) { + setCurrentTrialIdx(benchmark.trials.length - 1); } - }, [task]); + }, [benchmark]); // Sets all input states to saved values useEffect(() => { @@ -98,36 +98,45 @@ const BenchmarkPage = () => { } }, [currentTrial?.notes, currentTrial?.success, currentTrial?.unsuccess]); + // Move this to the backend, this page shouldn't have to deal with tasks, only + // benchmarks, we don't want mutations in useEffect, useEffect should be responding + // to UI changes and not making backend changes. // Marks this benchmark as seen (if it hasn't been seen yet) + // or: we can modify the seenMutation to take the benchmark_id and ask it to look up + // the task based on the current user id being the task's assignee_id useEffect(() => { - if (!seenMutation.isLoading && !taskIsLoading && task && !task.seen) { - seenMutation.mutate({ task_id: task.task_id }); + if (!seenMutation.isLoading && !benchmarkIsLoading && benchmark) { + seenMutation.mutate({ benchmark_id: benchmark.benchmark_id }); } - }, [task, seenMutation, taskIsLoading]); + }, [benchmark, seenMutation, benchmarkIsLoading]); // Creates a new data collection instance (if there are none in progress) useEffect(() => { if ( !trialAddedRef.current && !addTrialMutation.isLoading && - !taskIsLoading && - task && - (task.trials.length === 0 || - task.trials[task.trials.length - 1]?.submitted === true) + !benchmarkIsLoading && + benchmark && + (benchmark.trials.length === 0 || + benchmark.trials[benchmark.trials.length - 1]?.submitted === true) ) { addTrialMutation.mutate({ - task_id: task.task_id, + benchmark_id: benchmark.benchmark_id, success: 0, unsuccess: 0, notes: "", }); trialAddedRef.current = true; } - }, [task, addTrialMutation, taskIsLoading]); + }, [benchmark, addTrialMutation, benchmarkIsLoading, trialAddedRef]); const handleUpdate = (updates: DataUpdate) => { //Can only update if we're on the most recent trial - if (task && currentTrial && currentTrialIdx === task.trials.length - 1) { + if ( + benchmark && + currentTrial && + currentTrialIdx === benchmark.trials.length - 1 + ) { updateTrialMutation.mutate({ trial_data_id: currentTrial.trial_data_id, ...updates, @@ -179,7 +188,7 @@ const BenchmarkPage = () => { }); }; - if (taskIsLoading || !currentTrial) { + if (benchmarkIsLoading || !currentTrial) { return
Loading...
; } @@ -194,7 +203,7 @@ const BenchmarkPage = () => {

- Task: {task.description} + Benchmark: {benchmark.description}

@@ -228,9 +237,10 @@ const BenchmarkPage = () => { onDecrement={() => { setSuccessInputValue(successInputValue - 1); }} - disableInc={currentTrialIdx !== task.trials.length - 1} + disableInc={currentTrialIdx !== benchmark.trials.length - 1} disableDec={ - currentTrialIdx !== task.trials.length - 1 || successInputValue <= 0 + currentTrialIdx !== benchmark.trials.length - 1 || + successInputValue <= 0 } color="green" /> @@ -247,23 +257,23 @@ const BenchmarkPage = () => { onDecrement={() => { setUnsuccessInputValue(unsuccessInputValue - 1); }} - disableInc={currentTrialIdx !== task.trials.length - 1} + disableInc={currentTrialIdx !== benchmark.trials.length - 1} disableDec={ - currentTrialIdx !== task.trials.length - 1 || + currentTrialIdx !== benchmark.trials.length - 1 || unsuccessInputValue <= 0 } color="red" />

{successInputValue + unsuccessInputValue} attempts out of{" "} - {task.number_of_trials ?? "-"} + {benchmark.number_of_trials ?? "-"}

@@ -296,7 +306,9 @@ const BenchmarkPage = () => { Review diff --git a/src/pages/benchmarks/[benchmark_id]/instructions.tsx b/src/pages/benchmarks/[benchmark_id]/instructions.tsx index 4a606cfb..d084a22e 100644 --- a/src/pages/benchmarks/[benchmark_id]/instructions.tsx +++ b/src/pages/benchmarks/[benchmark_id]/instructions.tsx @@ -12,7 +12,7 @@ const InstructionsPage = () => { const { benchmark_id } = router.query; const { data: benchmark, isLoading } = trpc.iep.getBenchmarkAndTrialData.useQuery( - { task_id: benchmark_id as string }, + { benchmark_id: benchmark_id as string }, { enabled: Boolean(benchmark_id) } ); diff --git a/src/pages/benchmarks/[benchmark_id]/review.tsx b/src/pages/benchmarks/[benchmark_id]/review.tsx index 10b236a4..d7cff881 100644 --- a/src/pages/benchmarks/[benchmark_id]/review.tsx +++ b/src/pages/benchmarks/[benchmark_id]/review.tsx @@ -7,14 +7,15 @@ import $button from "@/components/design_system/button/Button.module.css"; const ReviewPage = () => { const router = useRouter(); const { benchmark_id } = router.query; - const { data: task, isLoading } = trpc.iep.getBenchmarkAndTrialData.useQuery( - { - task_id: benchmark_id as string, - }, - { - enabled: Boolean(benchmark_id), - } - ); + const { data: benchmark, isLoading } = + trpc.iep.getBenchmarkAndTrialData.useQuery( + { + benchmark_id: benchmark_id as string, // how does this line make sense? + }, + { + enabled: Boolean(benchmark_id), + } + ); const updateTrialMutation = trpc.iep.updateTrialData.useMutation(); @@ -32,20 +33,20 @@ const ReviewPage = () => { } }; - if (isLoading || !task) { + if (isLoading || !benchmark) { return
Loading...
; } - const currentTrial = task.trials[task.trials.length - 1]; + const currentTrial = benchmark.trials[benchmark.trials.length - 1]; return (

- Trial {task.trials.length} + Trial {benchmark.trials.length}

- {task.first_name} completed {currentTrial.success} successful attempts - and {currentTrial.unsuccess} unsuccessful attempts. + {benchmark.first_name} completed {currentTrial.success} successful + attempts and {currentTrial.unsuccess} unsuccessful attempts.

Observation Notes:

diff --git a/src/pages/benchmarks/index.tsx b/src/pages/benchmarks/index.tsx index 2bd3750d..9b60a129 100644 --- a/src/pages/benchmarks/index.tsx +++ b/src/pages/benchmarks/index.tsx @@ -162,7 +162,7 @@ function Benchmarks() { {/* Temporary CM & Para View */} {isPara && !completed ? ( diff --git a/src/pages/students/[student_id]/goals/[goal_id].tsx b/src/pages/students/[student_id]/goals/[goal_id].tsx index 9b951a23..4614c9a1 100644 --- a/src/pages/students/[student_id]/goals/[goal_id].tsx +++ b/src/pages/students/[student_id]/goals/[goal_id].tsx @@ -4,19 +4,27 @@ import Stack from "@mui/material/Stack"; import Grid from "@mui/material/Grid"; import BenchmarksContainer from "@/components/benchmarks/BenchmarksContainer"; import { GoalHeader } from "@/components/goal-header/goal-header"; +import { Benchmark } from "@/types/global"; const GoalPage = () => { const router = useRouter(); const goal_id = (router.query?.goal_id as string) || ""; const student_id = (router.query?.student_id as string) || ""; + const utils = trpc.useUtils(); + const { data: activeIep } = trpc.student.getActiveStudentIep.useQuery( { student_id: student_id }, { enabled: Boolean(student_id), retry: false } ); - const { data: goals = [] } = trpc.iep.getGoals.useQuery({ - iep_id: activeIep?.iep_id || "", - }); + const { data: goals = [] } = trpc.iep.getGoals.useQuery( + { + iep_id: activeIep?.iep_id || "", + }, + { + enabled: Boolean(activeIep), + } + ); const { data: goal } = trpc.iep.getGoal.useQuery( { goal_id: goal_id }, @@ -29,6 +37,16 @@ const GoalPage = () => { ); const goal_index = goals.findIndex((g) => g.goal_id === goal?.goal_id) + 1; + + function onUpdate(benchmark: Benchmark) { + const newBenchmarks = [...(benchmarks ?? [])]; + const index = newBenchmarks.findIndex( + (b) => b.benchmark_id === benchmark.benchmark_id + ); + newBenchmarks[index] = benchmark; + utils.iep.getBenchmarks.setData({ goal_id: goal_id }, newBenchmarks); + } + return ( {

Benchmarks

- +
); }; diff --git a/src/types/global.ts b/src/types/global.ts index 06cbce7f..5c80848d 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -1,7 +1,9 @@ +import { RouterOutputs } from "@/client/lib/trpc"; import { SelectableForTable } from "zapatos/schema"; export type Goal = SelectableForTable<"goal">; -export type Benchmark = SelectableForTable<"benchmark">; +export type Benchmark = RouterOutputs["iep"]["getBenchmark"]; +export type User = SelectableForTable<"user">; export type ChangeEvent = React.ChangeEvent; export type FormEvent = React.FormEvent; @@ -22,4 +24,5 @@ export interface TaskData { seen: boolean; completed_trials: string | number | bigint | null; created_at: Date; + benchmark_id: string; }