diff --git a/src/backend/db/migrations/1_initial-migrations.sql b/src/backend/db/migrations/1_initial-migrations.sql index 18f68ee4..870fcdfa 100644 --- a/src/backend/db/migrations/1_initial-migrations.sql +++ b/src/backend/db/migrations/1_initial-migrations.sql @@ -45,7 +45,8 @@ CREATE TABLE "student" ( first_name TEXT NOT NULL, last_name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, - assigned_case_manager_id UUID REFERENCES "user" (user_id) ON DELETE SET NULL + assigned_case_manager_id UUID REFERENCES "user" (user_id) ON DELETE SET NULL, + grade SMALLINT ); CREATE TABLE "file" ( @@ -70,6 +71,7 @@ CREATE TABLE "goal" ( goal_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- TODO: add index to allow reordering iep_id UUID REFERENCES "iep" (iep_id), description TEXT NOT NULL, + category TEXT NOT NULL CHECK (category IN ('writing', 'reading', 'math', 'other')), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); @@ -77,5 +79,32 @@ CREATE TABLE "subgoal" ( subgoal_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- TODO: add index to allow reordering goal_id UUID REFERENCES "goal" (goal_id), description TEXT NOT NULL, + instructions TEXT NOT NULL DEFAULT '', + target_max_attempts INTEGER, --How many "questions" to administer in a single sitting created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + + -- Different collection types may be added later: + -- collection_type TEXT NOT NULL CHECK (collection_type IN ('attempt', 'behavioral')), +); + +CREATE TABLE "task" ( + task_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + subgoal_id UUID REFERENCES "subgoal" (subgoal_id), + assignee_id UUID REFERENCES "user" (user_id), + due_date TIMESTAMPTZ NOT NULL ); + +CREATE TABLE "trial_data" ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + subgoal_id UUID REFERENCES "subgoal" (subgoal_id), + created_by_user_id UUID REFERENCES "user" (user_id), + -- TODO: Possibly add optional reference to "task" + success_with_prompt INTEGER NOT NULL, + success_without_prompt INTEGER NOT NULL, + notes TEXT,-- Optional depending on type of task + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Potential schema for different collection types: + -- type TEXT NOT NULL CHECK (type IN ('attempt', 'behavioral')) -- enum - type of subgoal + -- data jsonb -- actual data, e.g. attempt_counts etc \ 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 8c3fc296..1ae16023 100644 --- a/src/backend/db/zapatos/schema.d.ts +++ b/src/backend/db/zapatos/schema.d.ts @@ -604,6 +604,12 @@ declare module 'zapatos/schema' { */ description: string; /** + * **goal.category** + * - `text` in database + * - `NOT NULL`, no default + */ + category: string; + /** * **goal.created_at** * - `timestamptz` in database * - `NOT NULL`, default: `now()` @@ -630,6 +636,12 @@ declare module 'zapatos/schema' { */ description: string; /** + * **goal.category** + * - `text` in database + * - `NOT NULL`, no default + */ + category: string; + /** * **goal.created_at** * - `timestamptz` in database * - `NOT NULL`, default: `now()` @@ -656,6 +668,12 @@ declare module 'zapatos/schema' { */ description?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; /** + * **goal.category** + * - `text` in database + * - `NOT NULL`, no default + */ + category?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** * **goal.created_at** * - `timestamptz` in database * - `NOT NULL`, default: `now()` @@ -682,6 +700,12 @@ declare module 'zapatos/schema' { */ description: string | db.Parameter | db.SQLFragment; /** + * **goal.category** + * - `text` in database + * - `NOT NULL`, no default + */ + category: string | db.Parameter | db.SQLFragment; + /** * **goal.created_at** * - `timestamptz` in database * - `NOT NULL`, default: `now()` @@ -708,6 +732,12 @@ declare module 'zapatos/schema' { */ description?: string | db.Parameter | db.SQLFragment | db.SQLFragment | db.SQLFragment>; /** + * **goal.category** + * - `text` in database + * - `NOT NULL`, no default + */ + category?: string | db.Parameter | db.SQLFragment | db.SQLFragment | db.SQLFragment>; + /** * **goal.created_at** * - `timestamptz` in database * - `NOT NULL`, default: `now()` @@ -1330,6 +1360,12 @@ declare module 'zapatos/schema' { * - Nullable, no default */ assigned_case_manager_id: string | null; + /** + * **student.grade** + * - `int2` in database + * - Nullable, no default + */ + grade: number | null; } export interface JSONSelectable { /** @@ -1362,6 +1398,12 @@ declare module 'zapatos/schema' { * - Nullable, no default */ assigned_case_manager_id: string | null; + /** + * **student.grade** + * - `int2` in database + * - Nullable, no default + */ + grade: number | null; } export interface Whereable { /** @@ -1394,6 +1436,12 @@ declare module 'zapatos/schema' { * - Nullable, no default */ assigned_case_manager_id?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** + * **student.grade** + * - `int2` in database + * - Nullable, no default + */ + grade?: number | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; } export interface Insertable { /** @@ -1426,6 +1474,12 @@ declare module 'zapatos/schema' { * - Nullable, no default */ assigned_case_manager_id?: string | db.Parameter | null | db.DefaultType | db.SQLFragment; + /** + * **student.grade** + * - `int2` in database + * - Nullable, no default + */ + grade?: number | db.Parameter | null | db.DefaultType | db.SQLFragment; } export interface Updatable { /** @@ -1458,6 +1512,12 @@ declare module 'zapatos/schema' { * - Nullable, no default */ assigned_case_manager_id?: string | db.Parameter | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; + /** + * **student.grade** + * - `int2` in database + * - Nullable, no default + */ + grade?: number | db.Parameter | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; } export type UniqueIndex = 'student_email_key' | 'student_pkey'; export type Column = keyof Selectable; @@ -1492,6 +1552,18 @@ declare module 'zapatos/schema' { */ description: string; /** + * **subgoal.instructions** + * - `text` in database + * - `NOT NULL`, default: `''::text` + */ + instructions: string; + /** + * **subgoal.target_max_attempts** + * - `int4` in database + * - Nullable, no default + */ + target_max_attempts: number | null; + /** * **subgoal.created_at** * - `timestamptz` in database * - `NOT NULL`, default: `now()` @@ -1518,6 +1590,18 @@ declare module 'zapatos/schema' { */ description: string; /** + * **subgoal.instructions** + * - `text` in database + * - `NOT NULL`, default: `''::text` + */ + instructions: string; + /** + * **subgoal.target_max_attempts** + * - `int4` in database + * - Nullable, no default + */ + target_max_attempts: number | null; + /** * **subgoal.created_at** * - `timestamptz` in database * - `NOT NULL`, default: `now()` @@ -1544,6 +1628,18 @@ declare module 'zapatos/schema' { */ description?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; /** + * **subgoal.instructions** + * - `text` in database + * - `NOT NULL`, default: `''::text` + */ + instructions?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** + * **subgoal.target_max_attempts** + * - `int4` in database + * - Nullable, no default + */ + target_max_attempts?: number | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** * **subgoal.created_at** * - `timestamptz` in database * - `NOT NULL`, default: `now()` @@ -1570,6 +1666,18 @@ declare module 'zapatos/schema' { */ description: string | db.Parameter | db.SQLFragment; /** + * **subgoal.instructions** + * - `text` in database + * - `NOT NULL`, default: `''::text` + */ + instructions?: string | db.Parameter | db.DefaultType | db.SQLFragment; + /** + * **subgoal.target_max_attempts** + * - `int4` in database + * - Nullable, no default + */ + target_max_attempts?: number | db.Parameter | null | db.DefaultType | db.SQLFragment; + /** * **subgoal.created_at** * - `timestamptz` in database * - `NOT NULL`, default: `now()` @@ -1596,6 +1704,18 @@ declare module 'zapatos/schema' { */ description?: string | db.Parameter | db.SQLFragment | db.SQLFragment | db.SQLFragment>; /** + * **subgoal.instructions** + * - `text` in database + * - `NOT NULL`, default: `''::text` + */ + instructions?: string | db.Parameter | db.DefaultType | db.SQLFragment | db.SQLFragment | db.DefaultType | db.SQLFragment>; + /** + * **subgoal.target_max_attempts** + * - `int4` in database + * - Nullable, no default + */ + target_max_attempts?: number | db.Parameter | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; + /** * **subgoal.created_at** * - `timestamptz` in database * - `NOT NULL`, default: `now()` @@ -1609,6 +1729,382 @@ declare module 'zapatos/schema' { export type SQL = SQLExpression | SQLExpression[]; } + /** + * **task** + * - Table in database + */ + export namespace task { + export type Table = 'task'; + export interface Selectable { + /** + * **task.task_id** + * - `uuid` in database + * - `NOT NULL`, default: `uuid_generate_v4()` + */ + task_id: string; + /** + * **task.subgoal_id** + * - `uuid` in database + * - Nullable, no default + */ + subgoal_id: string | null; + /** + * **task.assignee_id** + * - `uuid` in database + * - Nullable, no default + */ + assignee_id: string | null; + /** + * **task.due_date** + * - `timestamptz` in database + * - `NOT NULL`, no default + */ + due_date: Date; + } + export interface JSONSelectable { + /** + * **task.task_id** + * - `uuid` in database + * - `NOT NULL`, default: `uuid_generate_v4()` + */ + task_id: string; + /** + * **task.subgoal_id** + * - `uuid` in database + * - Nullable, no default + */ + subgoal_id: string | null; + /** + * **task.assignee_id** + * - `uuid` in database + * - Nullable, no default + */ + assignee_id: string | null; + /** + * **task.due_date** + * - `timestamptz` in database + * - `NOT NULL`, no default + */ + due_date: db.TimestampTzString; + } + export interface Whereable { + /** + * **task.task_id** + * - `uuid` in database + * - `NOT NULL`, default: `uuid_generate_v4()` + */ + task_id?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** + * **task.subgoal_id** + * - `uuid` in database + * - Nullable, no default + */ + subgoal_id?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** + * **task.assignee_id** + * - `uuid` in database + * - Nullable, no default + */ + assignee_id?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** + * **task.due_date** + * - `timestamptz` in database + * - `NOT NULL`, no default + */ + due_date?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + } + export interface Insertable { + /** + * **task.task_id** + * - `uuid` in database + * - `NOT NULL`, default: `uuid_generate_v4()` + */ + task_id?: string | db.Parameter | db.DefaultType | db.SQLFragment; + /** + * **task.subgoal_id** + * - `uuid` in database + * - Nullable, no default + */ + subgoal_id?: string | db.Parameter | null | db.DefaultType | db.SQLFragment; + /** + * **task.assignee_id** + * - `uuid` in database + * - Nullable, no default + */ + assignee_id?: string | db.Parameter | null | db.DefaultType | db.SQLFragment; + /** + * **task.due_date** + * - `timestamptz` in database + * - `NOT NULL`, no default + */ + due_date: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | db.SQLFragment; + } + export interface Updatable { + /** + * **task.task_id** + * - `uuid` in database + * - `NOT NULL`, default: `uuid_generate_v4()` + */ + task_id?: string | db.Parameter | db.DefaultType | db.SQLFragment | db.SQLFragment | db.DefaultType | db.SQLFragment>; + /** + * **task.subgoal_id** + * - `uuid` in database + * - Nullable, no default + */ + subgoal_id?: string | db.Parameter | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; + /** + * **task.assignee_id** + * - `uuid` in database + * - Nullable, no default + */ + assignee_id?: string | db.Parameter | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; + /** + * **task.due_date** + * - `timestamptz` in database + * - `NOT NULL`, no default + */ + due_date?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | db.SQLFragment | db.SQLFragment | db.SQLFragment>; + } + export type UniqueIndex = 'task_pkey'; + export type Column = keyof Selectable; + export type OnlyCols = Pick; + export type SQLExpression = Table | db.ColumnNames | db.ColumnValues | Whereable | Column | db.ParentColumn | db.GenericSQLExpression; + export type SQL = SQLExpression | SQLExpression[]; + } + + /** + * **trial_data** + * - Table in database + */ + export namespace trial_data { + export type Table = 'trial_data'; + export interface Selectable { + /** + * **trial_data.id** + * - `uuid` in database + * - `NOT NULL`, default: `uuid_generate_v4()` + */ + id: string; + /** + * **trial_data.subgoal_id** + * - `uuid` in database + * - Nullable, no default + */ + subgoal_id: string | null; + /** + * **trial_data.created_by_user_id** + * - `uuid` in database + * - Nullable, no default + */ + created_by_user_id: string | null; + /** + * **trial_data.success_with_prompt** + * - `int4` in database + * - `NOT NULL`, no default + */ + success_with_prompt: number; + /** + * **trial_data.success_without_prompt** + * - `int4` in database + * - `NOT NULL`, no default + */ + success_without_prompt: number; + /** + * **trial_data.notes** + * - `text` in database + * - Nullable, no default + */ + notes: string | null; + /** + * **trial_data.created_at** + * - `timestamptz` in database + * - `NOT NULL`, default: `now()` + */ + created_at: Date; + } + export interface JSONSelectable { + /** + * **trial_data.id** + * - `uuid` in database + * - `NOT NULL`, default: `uuid_generate_v4()` + */ + id: string; + /** + * **trial_data.subgoal_id** + * - `uuid` in database + * - Nullable, no default + */ + subgoal_id: string | null; + /** + * **trial_data.created_by_user_id** + * - `uuid` in database + * - Nullable, no default + */ + created_by_user_id: string | null; + /** + * **trial_data.success_with_prompt** + * - `int4` in database + * - `NOT NULL`, no default + */ + success_with_prompt: number; + /** + * **trial_data.success_without_prompt** + * - `int4` in database + * - `NOT NULL`, no default + */ + success_without_prompt: number; + /** + * **trial_data.notes** + * - `text` in database + * - Nullable, no default + */ + notes: string | null; + /** + * **trial_data.created_at** + * - `timestamptz` in database + * - `NOT NULL`, default: `now()` + */ + created_at: db.TimestampTzString; + } + export interface Whereable { + /** + * **trial_data.id** + * - `uuid` in database + * - `NOT NULL`, default: `uuid_generate_v4()` + */ + id?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** + * **trial_data.subgoal_id** + * - `uuid` in database + * - Nullable, no default + */ + subgoal_id?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** + * **trial_data.created_by_user_id** + * - `uuid` in database + * - Nullable, no default + */ + created_by_user_id?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** + * **trial_data.success_with_prompt** + * - `int4` in database + * - `NOT NULL`, no default + */ + success_with_prompt?: number | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** + * **trial_data.success_without_prompt** + * - `int4` in database + * - `NOT NULL`, no default + */ + success_without_prompt?: number | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** + * **trial_data.notes** + * - `text` in database + * - Nullable, no default + */ + notes?: string | db.Parameter | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + /** + * **trial_data.created_at** + * - `timestamptz` in database + * - `NOT NULL`, default: `now()` + */ + created_at?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | db.SQLFragment | db.ParentColumn | db.SQLFragment | db.SQLFragment | db.ParentColumn>; + } + export interface Insertable { + /** + * **trial_data.id** + * - `uuid` in database + * - `NOT NULL`, default: `uuid_generate_v4()` + */ + id?: string | db.Parameter | db.DefaultType | db.SQLFragment; + /** + * **trial_data.subgoal_id** + * - `uuid` in database + * - Nullable, no default + */ + subgoal_id?: string | db.Parameter | null | db.DefaultType | db.SQLFragment; + /** + * **trial_data.created_by_user_id** + * - `uuid` in database + * - Nullable, no default + */ + created_by_user_id?: string | db.Parameter | null | db.DefaultType | db.SQLFragment; + /** + * **trial_data.success_with_prompt** + * - `int4` in database + * - `NOT NULL`, no default + */ + success_with_prompt: number | db.Parameter | db.SQLFragment; + /** + * **trial_data.success_without_prompt** + * - `int4` in database + * - `NOT NULL`, no default + */ + success_without_prompt: number | db.Parameter | db.SQLFragment; + /** + * **trial_data.notes** + * - `text` in database + * - Nullable, no default + */ + notes?: string | db.Parameter | null | db.DefaultType | db.SQLFragment; + /** + * **trial_data.created_at** + * - `timestamptz` in database + * - `NOT NULL`, default: `now()` + */ + created_at?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | db.DefaultType | db.SQLFragment; + } + export interface Updatable { + /** + * **trial_data.id** + * - `uuid` in database + * - `NOT NULL`, default: `uuid_generate_v4()` + */ + id?: string | db.Parameter | db.DefaultType | db.SQLFragment | db.SQLFragment | db.DefaultType | db.SQLFragment>; + /** + * **trial_data.subgoal_id** + * - `uuid` in database + * - Nullable, no default + */ + subgoal_id?: string | db.Parameter | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; + /** + * **trial_data.created_by_user_id** + * - `uuid` in database + * - Nullable, no default + */ + created_by_user_id?: string | db.Parameter | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; + /** + * **trial_data.success_with_prompt** + * - `int4` in database + * - `NOT NULL`, no default + */ + success_with_prompt?: number | db.Parameter | db.SQLFragment | db.SQLFragment | db.SQLFragment>; + /** + * **trial_data.success_without_prompt** + * - `int4` in database + * - `NOT NULL`, no default + */ + success_without_prompt?: number | db.Parameter | db.SQLFragment | db.SQLFragment | db.SQLFragment>; + /** + * **trial_data.notes** + * - `text` in database + * - Nullable, no default + */ + notes?: string | db.Parameter | null | db.DefaultType | db.SQLFragment | db.SQLFragment | null | db.DefaultType | db.SQLFragment>; + /** + * **trial_data.created_at** + * - `timestamptz` in database + * - `NOT NULL`, default: `now()` + */ + created_at?: (db.TimestampTzString | Date) | db.Parameter<(db.TimestampTzString | Date)> | db.DefaultType | db.SQLFragment | db.SQLFragment | db.DefaultType | db.SQLFragment>; + } + export type UniqueIndex = 'trial_data_pkey'; + export type Column = keyof Selectable; + export type OnlyCols = Pick; + export type SQLExpression = Table | db.ColumnNames | db.ColumnValues | Whereable | Column | db.ParentColumn | db.GenericSQLExpression; + export type SQL = SQLExpression | SQLExpression[]; + } + /** * **user** * - Table in database @@ -1845,20 +2341,20 @@ declare module 'zapatos/schema' { /* --- aggregate types --- */ export namespace public { - export type Table = account.Table | file.Table | goal.Table | iep.Table | migrations.Table | paras_assigned_to_case_manager.Table | session.Table | student.Table | subgoal.Table | user.Table; - export type Selectable = account.Selectable | file.Selectable | goal.Selectable | iep.Selectable | migrations.Selectable | paras_assigned_to_case_manager.Selectable | session.Selectable | student.Selectable | subgoal.Selectable | user.Selectable; - export type JSONSelectable = account.JSONSelectable | file.JSONSelectable | goal.JSONSelectable | iep.JSONSelectable | migrations.JSONSelectable | paras_assigned_to_case_manager.JSONSelectable | session.JSONSelectable | student.JSONSelectable | subgoal.JSONSelectable | user.JSONSelectable; - export type Whereable = account.Whereable | file.Whereable | goal.Whereable | iep.Whereable | migrations.Whereable | paras_assigned_to_case_manager.Whereable | session.Whereable | student.Whereable | subgoal.Whereable | user.Whereable; - export type Insertable = account.Insertable | file.Insertable | goal.Insertable | iep.Insertable | migrations.Insertable | paras_assigned_to_case_manager.Insertable | session.Insertable | student.Insertable | subgoal.Insertable | user.Insertable; - export type Updatable = account.Updatable | file.Updatable | goal.Updatable | iep.Updatable | migrations.Updatable | paras_assigned_to_case_manager.Updatable | session.Updatable | student.Updatable | subgoal.Updatable | user.Updatable; - export type UniqueIndex = account.UniqueIndex | file.UniqueIndex | goal.UniqueIndex | iep.UniqueIndex | migrations.UniqueIndex | paras_assigned_to_case_manager.UniqueIndex | session.UniqueIndex | student.UniqueIndex | subgoal.UniqueIndex | user.UniqueIndex; - export type Column = account.Column | file.Column | goal.Column | iep.Column | migrations.Column | paras_assigned_to_case_manager.Column | session.Column | student.Column | subgoal.Column | user.Column; + export type Table = account.Table | file.Table | goal.Table | iep.Table | migrations.Table | paras_assigned_to_case_manager.Table | session.Table | student.Table | subgoal.Table | task.Table | trial_data.Table | user.Table; + export type Selectable = account.Selectable | file.Selectable | goal.Selectable | iep.Selectable | migrations.Selectable | paras_assigned_to_case_manager.Selectable | session.Selectable | student.Selectable | subgoal.Selectable | task.Selectable | trial_data.Selectable | user.Selectable; + export type JSONSelectable = account.JSONSelectable | file.JSONSelectable | goal.JSONSelectable | iep.JSONSelectable | migrations.JSONSelectable | paras_assigned_to_case_manager.JSONSelectable | session.JSONSelectable | student.JSONSelectable | subgoal.JSONSelectable | task.JSONSelectable | trial_data.JSONSelectable | user.JSONSelectable; + export type Whereable = account.Whereable | file.Whereable | goal.Whereable | iep.Whereable | migrations.Whereable | paras_assigned_to_case_manager.Whereable | session.Whereable | student.Whereable | subgoal.Whereable | task.Whereable | trial_data.Whereable | user.Whereable; + export type Insertable = account.Insertable | file.Insertable | goal.Insertable | iep.Insertable | migrations.Insertable | paras_assigned_to_case_manager.Insertable | session.Insertable | student.Insertable | subgoal.Insertable | task.Insertable | trial_data.Insertable | user.Insertable; + export type Updatable = account.Updatable | file.Updatable | goal.Updatable | iep.Updatable | migrations.Updatable | paras_assigned_to_case_manager.Updatable | session.Updatable | student.Updatable | subgoal.Updatable | task.Updatable | trial_data.Updatable | user.Updatable; + export type UniqueIndex = account.UniqueIndex | file.UniqueIndex | goal.UniqueIndex | iep.UniqueIndex | migrations.UniqueIndex | paras_assigned_to_case_manager.UniqueIndex | session.UniqueIndex | student.UniqueIndex | subgoal.UniqueIndex | task.UniqueIndex | trial_data.UniqueIndex | user.UniqueIndex; + export type Column = account.Column | file.Column | goal.Column | iep.Column | migrations.Column | paras_assigned_to_case_manager.Column | session.Column | student.Column | subgoal.Column | task.Column | trial_data.Column | user.Column; - export type AllBaseTables = [account.Table, file.Table, goal.Table, iep.Table, migrations.Table, paras_assigned_to_case_manager.Table, session.Table, student.Table, subgoal.Table, user.Table]; + export type AllBaseTables = [account.Table, file.Table, goal.Table, iep.Table, migrations.Table, paras_assigned_to_case_manager.Table, session.Table, student.Table, subgoal.Table, task.Table, trial_data.Table, user.Table]; export type AllForeignTables = []; export type AllViews = []; export type AllMaterializedViews = []; - export type AllTablesAndViews = [account.Table, file.Table, goal.Table, iep.Table, migrations.Table, paras_assigned_to_case_manager.Table, session.Table, student.Table, subgoal.Table, user.Table]; + export type AllTablesAndViews = [account.Table, file.Table, goal.Table, iep.Table, migrations.Table, paras_assigned_to_case_manager.Table, session.Table, student.Table, subgoal.Table, task.Table, trial_data.Table, user.Table]; } @@ -1895,6 +2391,8 @@ declare module 'zapatos/schema' { "session": session.Selectable; "student": student.Selectable; "subgoal": subgoal.Selectable; + "task": task.Selectable; + "trial_data": trial_data.Selectable; "user": user.Selectable; }[T]; @@ -1908,6 +2406,8 @@ declare module 'zapatos/schema' { "session": session.JSONSelectable; "student": student.JSONSelectable; "subgoal": subgoal.JSONSelectable; + "task": task.JSONSelectable; + "trial_data": trial_data.JSONSelectable; "user": user.JSONSelectable; }[T]; @@ -1921,6 +2421,8 @@ declare module 'zapatos/schema' { "session": session.Whereable; "student": student.Whereable; "subgoal": subgoal.Whereable; + "task": task.Whereable; + "trial_data": trial_data.Whereable; "user": user.Whereable; }[T]; @@ -1934,6 +2436,8 @@ declare module 'zapatos/schema' { "session": session.Insertable; "student": student.Insertable; "subgoal": subgoal.Insertable; + "task": task.Insertable; + "trial_data": trial_data.Insertable; "user": user.Insertable; }[T]; @@ -1947,6 +2451,8 @@ declare module 'zapatos/schema' { "session": session.Updatable; "student": student.Updatable; "subgoal": subgoal.Updatable; + "task": task.Updatable; + "trial_data": trial_data.Updatable; "user": user.Updatable; }[T]; @@ -1960,6 +2466,8 @@ declare module 'zapatos/schema' { "session": session.UniqueIndex; "student": student.UniqueIndex; "subgoal": subgoal.UniqueIndex; + "task": task.UniqueIndex; + "trial_data": trial_data.UniqueIndex; "user": user.UniqueIndex; }[T]; @@ -1973,6 +2481,8 @@ declare module 'zapatos/schema' { "session": session.Column; "student": student.Column; "subgoal": subgoal.Column; + "task": task.Column; + "trial_data": trial_data.Column; "user": user.Column; }[T]; @@ -1986,6 +2496,8 @@ declare module 'zapatos/schema' { "session": session.SQL; "student": student.SQL; "subgoal": subgoal.SQL; + "task": task.SQL; + "trial_data": trial_data.SQL; "user": user.SQL; }[T]; diff --git a/src/backend/routers/iep.test.ts b/src/backend/routers/iep.test.ts index 3c0f7e3f..0a4c50d4 100644 --- a/src/backend/routers/iep.test.ts +++ b/src/backend/routers/iep.test.ts @@ -1,8 +1,9 @@ import test from "ava"; import { getTestServer } from "@/backend/tests"; -test("basic flow - add/get goals and subgoals", async (t) => { - const { trpc, seed } = await getTestServer(t, { +// TODO: Write more tests +test("basic flow - add/get goals, subgoals, tasks", async (t) => { + const { trpc, db, seed } = await getTestServer(t, { authenticateAs: "case_manager", }); @@ -15,14 +16,24 @@ test("basic flow - add/get goals and subgoals", async (t) => { const goal1 = await trpc.iep.addGoal.mutate({ iep_id: iep.iep_id, description: "goal 1", + category: "writing", }); await trpc.iep.addSubgoal.mutate({ goal_id: goal1!.goal_id, description: "subgoal 1", + instructions: "instructions here", + target_max_attempts: 5, }); - await trpc.iep.addSubgoal.mutate({ + const subgoal2 = await trpc.iep.addSubgoal.mutate({ goal_id: goal1!.goal_id, description: "subgoal 2", + instructions: "", + target_max_attempts: null, + }); + await trpc.iep.addTask.mutate({ + subgoal_id: subgoal2!.subgoal_id, + assignee_id: seed.para.user_id, + due_date: new Date("2023-12-31"), }); const gotGoals = await trpc.iep.getGoals.query({ iep_id: iep.iep_id }); @@ -32,4 +43,14 @@ test("basic flow - add/get goals and subgoals", async (t) => { goal_id: goal1!.goal_id, }); t.is(gotSubgoals.length, 2); + + // TODO: Don't query db directly and use an API method instead. Possibly create a getTasks method later + t.truthy( + await db + .selectFrom("task") + .where("subgoal_id", "=", subgoal2!.subgoal_id) + .where("assignee_id", "=", seed.para.user_id) + .selectAll() + .executeTakeFirstOrThrow() + ); }); diff --git a/src/backend/routers/iep.ts b/src/backend/routers/iep.ts index b0df86a8..d53220b4 100644 --- a/src/backend/routers/iep.ts +++ b/src/backend/routers/iep.ts @@ -8,16 +8,18 @@ export const iep = router({ z.object({ iep_id: z.string(), description: z.string(), + category: z.string(), }) ) .mutation(async (req) => { - const { iep_id, description } = req.input; + const { iep_id, description, category } = req.input; const result = await req.ctx.db .insertInto("goal") .values({ iep_id, description, + category, }) .returningAll() .executeTakeFirst(); @@ -30,16 +32,79 @@ export const iep = router({ z.object({ goal_id: z.string(), description: z.string(), + instructions: z.string(), + target_max_attempts: z.number().nullable(), }) ) .mutation(async (req) => { - const { goal_id, description } = req.input; + const { goal_id, description, instructions, target_max_attempts } = + req.input; const result = await req.ctx.db .insertInto("subgoal") .values({ goal_id, description, + instructions, + target_max_attempts, + }) + .returningAll() + .executeTakeFirst(); + + return result; + }), + + addTask: authenticatedProcedure + .input( + z.object({ + subgoal_id: z.string(), + assignee_id: z.string(), + due_date: z.date(), + }) + ) + .mutation(async (req) => { + const { subgoal_id, assignee_id, due_date } = req.input; + + const result = await req.ctx.db + .insertInto("task") + .values({ + subgoal_id, + assignee_id, + due_date, + }) + .returningAll() + .executeTakeFirst(); + + return result; + }), + + addTrialData: authenticatedProcedure + .input( + z.object({ + subgoal_id: z.string(), + created_by_user_id: z.string(), + success_with_prompt: z.number(), + success_without_prompt: z.number(), + notes: z.string(), + }) + ) + .mutation(async (req) => { + const { + subgoal_id, + created_by_user_id, + success_with_prompt, + success_without_prompt, + notes, + } = req.input; + + const result = req.ctx.db + .insertInto("trial_data") + .values({ + subgoal_id, + created_by_user_id, + success_with_prompt, + success_without_prompt, + notes, }) .returningAll() .executeTakeFirst(); @@ -82,4 +147,41 @@ export const iep = router({ return result; }), + + getSubgoalsByAssignee: authenticatedProcedure + .input( + z.object({ + assignee_id: z.string(), + }) + ) + .query(async (req) => { + const { assignee_id } = req.input; + + const result = await req.ctx.db + .selectFrom("subgoal") + .innerJoin("task", "subgoal.subgoal_id", "task.subgoal_id") + .where("task.assignee_id", "=", assignee_id) + .selectAll() + .execute(); + + return result; + }), + + getTrialData: authenticatedProcedure + .input( + z.object({ + subgoal_id: z.string(), + }) + ) + .query(async (req) => { + const { subgoal_id } = req.input; + + const result = await req.ctx.db + .selectFrom("trial_data") + .where("subgoal_id", "=", subgoal_id) + .selectAll() + .execute(); + + return result; + }), }); diff --git a/src/backend/routers/para.test.ts b/src/backend/routers/para.test.ts index 9db73b68..b1b35c5f 100644 --- a/src/backend/routers/para.test.ts +++ b/src/backend/routers/para.test.ts @@ -108,3 +108,81 @@ test("createPara - invalid email", async (t) => { }) ); }); + +test("getMyTasks", async (t) => { + const { trpc, db, seed } = await getTestServer(t, { + authenticateAs: "case_manager", + }); + + const FIRST_NAME = "Foo"; + const LAST_NAME = "Bar"; + const DESCRIPTION = "Subgoal description"; + const CATEGORY = "writing"; + const DUE_DATE = new Date(); + const INSTRUCTIONS = "subgoal instructions foobar"; + const TARGET_MAX_ATTEMPTS = 5; + + const { student_id } = await db + .insertInto("student") + .values({ + first_name: FIRST_NAME, + last_name: LAST_NAME, + email: "jdoe@email.com", + assigned_case_manager_id: seed.case_manager.user_id, + }) + .returningAll() + .executeTakeFirstOrThrow(); + + const { iep_id } = await db + .insertInto("iep") + .values({ + student_id: student_id, + case_manager_id: seed.case_manager.user_id, + start_date: new Date("2023-01-01"), + end_date: new Date("2023-12-31"), + }) + .returningAll() + .executeTakeFirstOrThrow(); + + const { goal_id } = await db + .insertInto("goal") + .values({ + iep_id: iep_id, + description: "Goal Description", + category: CATEGORY, + }) + .returningAll() + .executeTakeFirstOrThrow(); + + const { subgoal_id } = await db + .insertInto("subgoal") + .values({ + goal_id: goal_id, + description: DESCRIPTION, + instructions: INSTRUCTIONS, + target_max_attempts: TARGET_MAX_ATTEMPTS, + }) + .returningAll() + .executeTakeFirstOrThrow(); + + const { task_id } = await db + .insertInto("task") + .values({ + subgoal_id: subgoal_id, + assignee_id: seed.case_manager.user_id, + due_date: DUE_DATE, + }) + .returningAll() + .executeTakeFirstOrThrow(); + + const task = await trpc.para.getMyTasks.query(); + t.is(task.length, 1); + t.is(task[0].task_id, task_id); + t.is(task[0].first_name, FIRST_NAME); + t.is(task[0].last_name, LAST_NAME); + t.is(task[0].description, DESCRIPTION); + t.is(task[0].category, CATEGORY); + t.deepEqual(task[0].due_date, DUE_DATE); + t.is(task[0].instructions, INSTRUCTIONS); + t.is(task[0].target_max_attempts, TARGET_MAX_ATTEMPTS); +}); diff --git a/src/backend/routers/para.ts b/src/backend/routers/para.ts index 4211a395..208e9034 100644 --- a/src/backend/routers/para.ts +++ b/src/backend/routers/para.ts @@ -74,4 +74,28 @@ export const para = router({ return paraData; }), + + getMyTasks: authenticatedProcedure.query(async (req) => { + const { userId } = req.ctx.auth; + + const result = await req.ctx.db + .selectFrom("subgoal") + .innerJoin("task", "subgoal.subgoal_id", "task.subgoal_id") + .innerJoin("goal", "subgoal.goal_id", "goal.goal_id") + .innerJoin("iep", "goal.iep_id", "iep.iep_id") + .innerJoin("student", "iep.student_id", "student.student_id") + .where("task.assignee_id", "=", userId) + .select([ + "task.task_id", + "student.first_name", + "student.last_name", + "subgoal.description", + "goal.category", + "task.due_date", + "subgoal.instructions", + "subgoal.target_max_attempts", + ]) + .execute(); + return result; + }), }); diff --git a/src/components/Goal.tsx b/src/components/Goal.tsx index 8570952a..5a82aa11 100644 --- a/src/components/Goal.tsx +++ b/src/components/Goal.tsx @@ -2,16 +2,12 @@ import React from "react"; import Subgoals from "./Subgoal"; import { trpc } from "@/client/lib/trpc"; import styles from "@/styles/Goal.module.css"; +import { Goal } from "@/types/global"; interface GoalProps { goal: Goal; } -interface Goal { - goal_id: string; - description: string | null; -} - const Goals = ({ goal }: GoalProps) => { const utils = trpc.useContext(); const { data: subgoals, isLoading } = trpc.iep.getSubgoals.useQuery({ @@ -29,6 +25,8 @@ const Goals = ({ goal }: GoalProps) => { subgoal.mutate({ goal_id: goal.goal_id, description: data.get("description") as string, + instructions: data.get("instructions") as string, + target_max_attempts: Number(data.get("target_max_attempts")) || null, }); }; @@ -38,6 +36,12 @@ const Goals = ({ goal }: GoalProps) => { return (
+

Goal

+
Goal ID: {goal.goal_id}
+

Description: {goal.description}

+

Created at: {goal.created_at.toDateString()}

+

Category: {goal.category}

+
    {subgoals?.map((subgoal) => ( @@ -55,6 +59,13 @@ const Goals = ({ goal }: GoalProps) => { placeholder="Subgoal description" required /> + + + diff --git a/src/components/Subgoal.tsx b/src/components/Subgoal.tsx index 46d1adf4..60a58b1f 100644 --- a/src/components/Subgoal.tsx +++ b/src/components/Subgoal.tsx @@ -1,20 +1,41 @@ +import { trpc } from "@/client/lib/trpc"; import React from "react"; +import { Subgoal } from "@/types/global"; interface SubgoalProps { subgoal: Subgoal; } -interface Subgoal { - subgoal_id: string; - description: string | null; -} - const Subgoals = ({ subgoal }: SubgoalProps) => { + const task = trpc.iep.addTask.useMutation(); + // TODO: add form to assign to my paras + // const assignToPara = (event: React.FormEvent) => { + // event.preventDefault(); + // const data = new FormData(event.currentTarget); + + // task.mutate({ + // subgoal_id: subgoal.subgoal_id, + // assignee_id: data.get("assignee_id") as string, + // due_date: new Date(data.get("due_date")) as Date + // }); + // } + const assignToPara = () => { + task.mutate({ + subgoal_id: subgoal.subgoal_id, + assignee_id: "51c24050-103e-4183-b9ad-6ce6bea0e062", + due_date: new Date(), + }); + }; + return (

    Subgoal

    Subgoal ID: {subgoal.subgoal_id}
    -

    {subgoal.description}

    +

    Description: {subgoal.description}

    +

    Created at: {subgoal.created_at.toDateString()}

    +

    Instructions: {subgoal.instructions || "null"}

    +

    Target max attempts: {subgoal.target_max_attempts || "null"}

    +
    ); }; diff --git a/src/components/para_trials/taskCard.tsx b/src/components/para_trials/taskCard.tsx index f4bf4cba..b891212d 100644 --- a/src/components/para_trials/taskCard.tsx +++ b/src/components/para_trials/taskCard.tsx @@ -1,29 +1,25 @@ // import Image from "next/image"; -import React from "react"; +import React, { useState } from "react"; import styles from "./styles/TaskCard.module.css"; import Link from "next/link"; import ProgressBar from "./progressBar"; +import { ParaTaskCard } from "@/types/global"; interface TaskCardProps { - task: { - student_name: string; - profile_img: string; - goal_id: number; - goal_description: string; - goal_type: string; - due_date: string; - completion_rate: number; - }; + task: ParaTaskCard; } const TaskCard = ({ task }: TaskCardProps) => { + // TODO: calculate completion rate depending on trials + const [completionRate] = useState(0); + const getDateStyle = () => { //New or done should be green - if (task.completion_rate === 0 || task.completion_rate === 100) { + if (completionRate === 0 || completionRate === 100) { return styles.dateFloaterGreen; } //Not sure if this should be due soon, or past due? - else if (task.due_date < "") { + else if (task.due_date) { return styles.dateFloaterRed; } else { return styles.dateFloater; @@ -33,42 +29,39 @@ const TaskCard = ({ task }: TaskCardProps) => { return (
    - {task.completion_rate === 0 + {completionRate === 0 ? "NEW" - : task.completion_rate === 100 + : completionRate === 100 ? "DONE" - : `DUE: ${task.due_date}`} + : `DUE: ${task.due_date.toString()}`}
    {/* Student's profile picture. */}
    -
    {task.student_name}
    +
    + {task.first_name} {task.last_name} +

    - {task.goal_type} - {task.goal_description} + {task.category} - {task.description}

    - {/* Will be a progress bar */}
    - {task.completion_rate}% complete - + {completionRate}% complete +
    Collect data diff --git a/src/pages/iep/[iep_id].tsx b/src/pages/iep/[iep_id].tsx index 7367b205..8b56a20f 100644 --- a/src/pages/iep/[iep_id].tsx +++ b/src/pages/iep/[iep_id].tsx @@ -30,6 +30,7 @@ const Iep = () => { goalMutation.mutate({ iep_id: iep_id as string, description: data.get("description") as string, + category: data.get("category") as string, }); }; @@ -43,13 +44,9 @@ const Iep = () => {

    Goals

      - {goals?.map((goal, idx) => ( + {goals?.map((goal) => (
    • -

      Goal {idx + 1}

      -
      Goal ID: {goal.goal_id}
      -

      {goal.description}

      -
    • ))}
    @@ -62,6 +59,12 @@ const Iep = () => { placeholder="Goal description" required /> + diff --git a/src/pages/paraDashboard.tsx b/src/pages/paraDashboard.tsx index fcecfa6d..f387013b 100644 --- a/src/pages/paraDashboard.tsx +++ b/src/pages/paraDashboard.tsx @@ -1,52 +1,26 @@ //this will be the mobile-first web view of the para data collection app import React from "react"; import TaskCard from "@/components/para_trials/taskCard"; +import { trpc } from "@/client/lib/trpc"; // import styles from "@/styles/Paraflow.module.css"; -const tempGoals = [ - { - student_name: "John Doe", - profile_img: "img", - goal_description: - "Annie will write 5 sentences with 80% accuracy in 4 out of 5 trials.", - goal_type: "Writing goal", - due_date: "01-08-2023", - completion_rate: 80, - goal_id: 1, - }, - { - student_name: "New Name", - profile_img: "img", - goal_description: - "Annie will write 5 sentences with 80% accuracy in 4 out of 5 trials.", - goal_type: "Writing goal", - due_date: "10-15-2023", - completion_rate: 0, - goal_id: 2, - }, - { - student_name: "Kate Doe", - profile_img: "img", - goal_description: - "Annie will write 5 sentences with 80% accuracy in 4 out of 5 trials.", - goal_type: "Writing goal", - due_date: "05-20-2023", - completion_rate: 100, - goal_id: 3, - }, -]; - function paraDashboard() { + const { data: tasks, isLoading } = trpc.para.getMyTasks.useQuery(); + + if (isLoading) { + return
    Loading...
    ; + } + return ( -
    - {tempGoals.map((tempgoal, i) => { +
      + {tasks?.map((task) => { return ( -
      - -
      +
    • + +
    • ); })} -
    +
); } diff --git a/src/types/global.ts b/src/types/global.ts new file mode 100644 index 00000000..0bf1c477 --- /dev/null +++ b/src/types/global.ts @@ -0,0 +1,15 @@ +import { SelectableForTable } from "zapatos/schema"; + +export interface ParaTaskCard { + task_id: string; + first_name: string; + last_name: string; + description: string; + category: string; + due_date: Date; + instructions: string | null; + target_max_attempts: number | null; +} + +export type Goal = SelectableForTable<"goal">; +export type Subgoal = SelectableForTable<"subgoal">;