diff --git a/README.md b/README.md index ec6d5f8..cfeca54 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,12 @@ ## Stack -[![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white)](https://go.dev/doc/) -[![Postgres](https://img.shields.io/badge/postgres-%23316192.svg?style=for-the-badge&logo=postgresql&logoColor=white)](https://www.postgresql.org/) +![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) +![Postgres](https://img.shields.io/badge/postgres-%23316192.svg?style=for-the-badge&logo=postgresql&logoColor=white) -[![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/) -[![React Native](https://img.shields.io/badge/react_native-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB)](https://reactnative.dev/) -[![TailwindCSS](https://img.shields.io/badge/tailwindcss-%2338B2AC.svg?style=for-the-badge&logo=tailwind-css&logoColor=white)](https://tailwindcss.com/) +![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) +![React Native](https://img.shields.io/badge/react_native-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB) +![TailwindCSS](https://img.shields.io/badge/tailwindcss-%2338B2AC.svg?style=for-the-badge&logo=tailwind-css&logoColor=white) ## Tools diff --git a/backend/db/migrations/5.files.sql b/backend/db/migrations/5.files.sql index ae14204..82b38cf 100644 --- a/backend/db/migrations/5.files.sql +++ b/backend/db/migrations/5.files.sql @@ -8,8 +8,6 @@ CREATE TABLE IF NOT EXISTS files ( upload_date timestamp, file_size integer NOT NULL, task_id integer, - notes varchar, - label_name varchar, PRIMARY KEY (file_id), FOREIGN KEY (group_id) REFERENCES care_group (group_id), FOREIGN KEY (upload_by) REFERENCES users (user_id), diff --git a/backend/db/migrations/init.sql b/backend/db/migrations/init.sql new file mode 100644 index 0000000..f664cfc --- /dev/null +++ b/backend/db/migrations/init.sql @@ -0,0 +1,16 @@ +DROP TABLE IF EXISTS medication; + +CREATE TABLE IF NOT EXISTS medication ( + medication_id integer NOT NULL UNIQUE, + medication_name varchar NOT NULL, + PRIMARY KEY (medication_id) +); + +INSERT INTO medication (medication_id, medication_name) +VALUES + (1, 'Medication A'), + (2, 'Medication B'), + (3, 'Medication C'), + (4, 'Medication D'), + (5, 'Medication E') +; diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 79849b7..df089af 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -43,19 +43,6 @@ const docTemplate = `{ "name": "group_id", "in": "formData", "required": true - }, - { - "type": "string", - "description": "Notes for the file", - "name": "notes", - "in": "formData", - "required": true - }, - { - "type": "string", - "description": "Label name for the file", - "name": "label_name", - "in": "formData" } ], "responses": { @@ -522,6 +509,58 @@ const docTemplate = `{ } } }, + "/medications": { + "get": { + "description": "get all user medications", + "tags": [ + "medications" + ], + "summary": "Get All Meds", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Medication" + } + } + } + } + }, + "post": { + "description": "add a medication to a users medlist", + "tags": [ + "medications" + ], + "summary": "add a medication", + "parameters": [ + { + "description": "a medication", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Medication" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Medication" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/tasks": { "post": { "description": "Create a new task", @@ -1184,12 +1223,6 @@ const docTemplate = `{ "group_id": { "type": "integer" }, - "label_name": { - "type": "string" - }, - "notes": { - "type": "string" - }, "task_id": { "type": "integer" }, @@ -1229,6 +1262,17 @@ const docTemplate = `{ } } }, + "models.Medication": { + "type": "object", + "properties": { + "medication_id": { + "type": "integer" + }, + "medication_name": { + "type": "string" + } + } + }, "models.Role": { "type": "string", "enum": [ diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index ff44a83..cf5ea38 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -36,19 +36,6 @@ "name": "group_id", "in": "formData", "required": true - }, - { - "type": "string", - "description": "Notes for the file", - "name": "notes", - "in": "formData", - "required": true - }, - { - "type": "string", - "description": "Label name for the file", - "name": "label_name", - "in": "formData" } ], "responses": { @@ -515,6 +502,58 @@ } } }, + "/medications": { + "get": { + "description": "get all user medications", + "tags": [ + "medications" + ], + "summary": "Get All Meds", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Medication" + } + } + } + } + }, + "post": { + "description": "add a medication to a users medlist", + "tags": [ + "medications" + ], + "summary": "add a medication", + "parameters": [ + { + "description": "a medication", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Medication" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Medication" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/tasks": { "post": { "description": "Create a new task", @@ -1177,12 +1216,6 @@ "group_id": { "type": "integer" }, - "label_name": { - "type": "string" - }, - "notes": { - "type": "string" - }, "task_id": { "type": "integer" }, @@ -1222,6 +1255,17 @@ } } }, + "models.Medication": { + "type": "object", + "properties": { + "medication_id": { + "type": "integer" + }, + "medication_name": { + "type": "string" + } + } + }, "models.Role": { "type": "string", "enum": [ diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 7b75574..657f177 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -33,10 +33,6 @@ definitions: type: integer group_id: type: integer - label_name: - type: string - notes: - type: string task_id: type: integer upload_by: @@ -62,6 +58,13 @@ definitions: label_name: type: string type: object + models.Medication: + properties: + medication_id: + type: integer + medication_name: + type: string + type: object models.Role: enum: - PATIENT @@ -234,15 +237,6 @@ paths: name: group_id required: true type: integer - - description: Notes for the file - in: formData - name: notes - required: true - type: string - - description: Label name for the file - in: formData - name: label_name - type: string responses: "200": description: OK @@ -554,6 +548,40 @@ paths: summary: Retrieve a group id given a user id tags: - group + /medications: + get: + description: get all user medications + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Medication' + type: array + summary: Get All Meds + tags: + - medications + post: + description: add a medication to a users medlist + parameters: + - description: a medication + in: body + name: _ + required: true + schema: + $ref: '#/definitions/models.Medication' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Medication' + "400": + description: Bad Request + schema: + type: string + summary: add a medication + tags: + - medications /tasks: post: description: Create a new task diff --git a/backend/main.go b/backend/main.go index a5bff5d..6fb1c0c 100644 --- a/backend/main.go +++ b/backend/main.go @@ -8,6 +8,7 @@ import ( groupRoles "carewallet/schema/group-roles" "carewallet/schema/groups" "carewallet/schema/labels" + "carewallet/schema/medication" "carewallet/schema/task_labels" "carewallet/schema/tasks" "carewallet/schema/user" @@ -50,6 +51,8 @@ func main() { v1 := r.Group("/") { + medication.GetMedicationGroup(v1, &medication.PgModel{Conn: conn}) + files.FileGroup(v1, &files.PgModel{Conn: conn}) user.UserGroup(v1, &user.PgModel{Conn: conn}) diff --git a/backend/models/file.go b/backend/models/file.go index b931270..b0878e4 100644 --- a/backend/models/file.go +++ b/backend/models/file.go @@ -8,6 +8,4 @@ type File struct { UploadDate string `json:"upload_date"` FileSize int64 `json:"file_size"` TaskID int `json:"task_id"` - Notes string `json:"notes"` - LabelName string `json:"label_name"` } diff --git a/backend/models/medication.go b/backend/models/medication.go new file mode 100644 index 0000000..3806606 --- /dev/null +++ b/backend/models/medication.go @@ -0,0 +1,6 @@ +package models + +type Medication struct { + MedicationID int `json:"medication_id"` + MedicationName string `json:"medication_name"` +} diff --git a/backend/schema/files/routes.go b/backend/schema/files/routes.go index 6db4f46..3e426fb 100644 --- a/backend/schema/files/routes.go +++ b/backend/schema/files/routes.go @@ -32,8 +32,6 @@ func FileGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { // @param file_data formData file true "Body with file zip" // @param upload_by formData string true "The userId of the uploader" // @param group_id formData int true "The groupId of the uploader" -// @param notes formData string true "Notes for the file" -// @param label_name formData string false "Label name for the file" // // @success 200 {object} models.File // @failure 400 {object} string @@ -50,8 +48,6 @@ func (pg *PgModel) UploadFile(c *gin.Context) { fileResponse := form.File["file_data"][0] file.UploadBy = form.Value["upload_by"][0] file.GroupID, err = strconv.Atoi(form.Value["group_id"][0]) - file.Notes = form.Value["notes"][0] - file.LabelName = form.Value["label_name"][0] if err != nil { c.JSON(http.StatusBadRequest, "Failed to parse groupid") diff --git a/backend/schema/files/transactions.go b/backend/schema/files/transactions.go index 348ba07..2085a9e 100644 --- a/backend/schema/files/transactions.go +++ b/backend/schema/files/transactions.go @@ -30,8 +30,8 @@ func UploadFile(pool *pgxpool.Pool, file models.File, data *multipart.FileHeader } // Insert file into database - err := pool.QueryRow(context.Background(), "INSERT INTO files (file_name, group_id, upload_by, upload_date, file_size, notes, label_name) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING file_id;", - file.FileName, file.GroupID, file.UploadBy, file.UploadDate, data.Size, file.Notes, file.LabelName).Scan(&file.FileID) + err := pool.QueryRow(context.Background(), "INSERT INTO files (file_name, group_id, upload_by, upload_date, file_size) VALUES ($1, $2, $3, $4, $5) RETURNING file_id;", + file.FileName, file.GroupID, file.UploadBy, file.UploadDate, data.Size).Scan(&file.FileID) if err != nil { fmt.Println(err.Error()) return err diff --git a/backend/schema/medication/medication_test.go b/backend/schema/medication/medication_test.go new file mode 100644 index 0000000..58bfd20 --- /dev/null +++ b/backend/schema/medication/medication_test.go @@ -0,0 +1,81 @@ +package medication + +import ( + "carewallet/configuration" + "carewallet/db" + "carewallet/models" + "fmt" + "os" + "slices" + "testing" + + "encoding/json" + "net/http" + "net/http/httptest" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + _ "github.com/lib/pq" +) + +func TestGetMedication(t *testing.T) { + config, err := configuration.GetConfiguration() + + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to retreive configuration file: %v\n", err) + os.Exit(1) + } + + conn := db.ConnectPosgresDatabase(config) + + defer conn.Close() + + controller := PgModel{Conn: conn} + + router := gin.Default() + + router.Use(cors.Default()) + + v1 := router.Group("/") + { + GetMedicationGroup(v1, &controller) + } + + t.Run("TestGetMedication", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/medications", nil) + router.ServeHTTP(w, req) + + // Check for HTTP Status OK (200) + if http.StatusOK != w.Code { + t.Error("Failed to retrieve medications.") + } + + var responseMedication []models.Medication + err := json.Unmarshal(w.Body.Bytes(), &responseMedication) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + // Define the expected medication data + expectedMedication := []models.Medication{ + {MedicationID: 1, + MedicationName: "Medication A"}, + {MedicationID: 2, + MedicationName: "Medication B"}, + {MedicationID: 3, + MedicationName: "Medication C"}, + {MedicationID: 4, + MedicationName: "Medication D"}, + {MedicationID: 5, + MedicationName: "Medication E"}, + } + + if !slices.Equal(expectedMedication, responseMedication) { + t.Error("Result was not correct") + } + + }) + +} diff --git a/backend/schema/medication/routes.go b/backend/schema/medication/routes.go new file mode 100644 index 0000000..93c7df3 --- /dev/null +++ b/backend/schema/medication/routes.go @@ -0,0 +1,67 @@ +package medication + +import ( + "carewallet/models" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/jackc/pgx/v5/pgxpool" +) + +type PgModel struct { + Conn *pgxpool.Pool +} + +func GetMedicationGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { + + medications := v1.Group("medications") + { + medications.GET("", c.GetMedications) + medications.POST("", c.AddMedications) + } + + return medications +} + +// GetMedications godoc +// +// @summary Get All Meds +// @description get all user medications +// @tags medications +// @success 200 {array} models.Medication +// @router /medications [get] +func (pg *PgModel) GetMedications(c *gin.Context) { + med, err := GetAllMedsFromDB(pg.Conn) + + if err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, med) +} + +// AddMedications godoc +// +// @summary add a medication +// @description add a medication to a users medlist +// @tags medications +// +// @param _ body models.Medication true "a medication" +// +// @success 200 {object} models.Medication +// @failure 400 {object} string +// @router /medications [post] +func (pg *PgModel) AddMedications(c *gin.Context) { + var medbody models.Medication + c.Bind(&medbody) + + med, err := AddMedToDB(pg.Conn, medbody) + + if err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, med) +} diff --git a/backend/schema/medication/transactions.go b/backend/schema/medication/transactions.go new file mode 100644 index 0000000..ab83d07 --- /dev/null +++ b/backend/schema/medication/transactions.go @@ -0,0 +1,48 @@ +package medication + +import ( + "carewallet/models" + "context" + + "github.com/jackc/pgx/v5/pgxpool" +) + +func GetAllMedsFromDB(pool *pgxpool.Pool) ([]models.Medication, error) { + rows, err := pool.Query(context.Background(), "SELECT medication_id, medication_name FROM medication;") + + if err != nil { + print(err, "from transactions err ") + + return nil, err + } + + defer rows.Close() + + var results []models.Medication + + for rows.Next() { + med := models.Medication{} + err := rows.Scan(&med.MedicationID, &med.MedicationName) + + if err != nil { + print(err, "from transactions err2 ") + return nil, err + } + + results = append(results, med) + } + + return results, nil +} + +func AddMedToDB(pool *pgxpool.Pool, med models.Medication) (models.Medication, error) { + err := pool.QueryRow(context.Background(), "INSERT INTO medication (medication_id, medication_name) VALUES ($1, $2) RETURNING medication_id;", + med.MedicationID, med.MedicationName).Scan(&med.MedicationID) + + if err != nil { + print(err.Error()) + return models.Medication{}, err + } + + return med, nil +} diff --git a/client/.eslintrc b/client/.eslintrc index 59197be..f31ec14 100644 --- a/client/.eslintrc +++ b/client/.eslintrc @@ -39,6 +39,8 @@ "message": "Import/export only modules you need" } ], + // Enforces no named deafult export + "import/no-named-as-default": 0, // Enforces no default export "import/no-default-export": "error", // Enforces react not being added to files where it isnt needed diff --git a/client/assets/arrow-left.svg b/client/assets/arrow-left.svg index 54e84eb..99dedf4 100644 --- a/client/assets/arrow-left.svg +++ b/client/assets/arrow-left.svg @@ -1,4 +1,4 @@ - - + + diff --git a/client/assets/financial.svg b/client/assets/financial.svg new file mode 100644 index 0000000..56c26fc --- /dev/null +++ b/client/assets/financial.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/home.svg b/client/assets/home.svg new file mode 100644 index 0000000..5d6c8ef --- /dev/null +++ b/client/assets/home.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/assets/other.svg b/client/assets/other.svg new file mode 100644 index 0000000..d3094c2 --- /dev/null +++ b/client/assets/other.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/client/assets/personal.svg b/client/assets/personal.svg new file mode 100644 index 0000000..b333da5 --- /dev/null +++ b/client/assets/personal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/assets/profile/ellipse.svg b/client/assets/profile/ellipse.svg new file mode 100644 index 0000000..bff7bc4 --- /dev/null +++ b/client/assets/profile/ellipse.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/task-creation/bathing.svg b/client/assets/task-creation/bathing.svg new file mode 100644 index 0000000..9cc5771 --- /dev/null +++ b/client/assets/task-creation/bathing.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/assets/task-creation/financial-bg.svg b/client/assets/task-creation/financial-bg.svg new file mode 100644 index 0000000..589dfb5 --- /dev/null +++ b/client/assets/task-creation/financial-bg.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/task-creation/health-bg.svg b/client/assets/task-creation/health-bg.svg new file mode 100644 index 0000000..9a320e3 --- /dev/null +++ b/client/assets/task-creation/health-bg.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/task-creation/home-bg.svg b/client/assets/task-creation/home-bg.svg new file mode 100644 index 0000000..b90abae --- /dev/null +++ b/client/assets/task-creation/home-bg.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/task-creation/liquid.svg b/client/assets/task-creation/liquid.svg new file mode 100644 index 0000000..a867417 --- /dev/null +++ b/client/assets/task-creation/liquid.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/assets/task-creation/other-bg.svg b/client/assets/task-creation/other-bg.svg new file mode 100644 index 0000000..3ac21b3 --- /dev/null +++ b/client/assets/task-creation/other-bg.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/task-creation/personal-bg.svg b/client/assets/task-creation/personal-bg.svg new file mode 100644 index 0000000..2937eec --- /dev/null +++ b/client/assets/task-creation/personal-bg.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/assets/task-creation/pill.svg b/client/assets/task-creation/pill.svg new file mode 100644 index 0000000..e93de32 --- /dev/null +++ b/client/assets/task-creation/pill.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/client/assets/task-creation/shot.svg b/client/assets/task-creation/shot.svg new file mode 100644 index 0000000..4cbad88 --- /dev/null +++ b/client/assets/task-creation/shot.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/assets/task-creation/toileting.svg b/client/assets/task-creation/toileting.svg new file mode 100644 index 0000000..ed8ef0a --- /dev/null +++ b/client/assets/task-creation/toileting.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/assets/task-type-icon-pill.svg b/client/assets/task-type-icon-pill.svg new file mode 100644 index 0000000..d848544 --- /dev/null +++ b/client/assets/task-type-icon-pill.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/components/nav_buttons/BackButton.tsx b/client/components/nav_buttons/BackButton.tsx index 1ce4968..a34fa00 100644 --- a/client/components/nav_buttons/BackButton.tsx +++ b/client/components/nav_buttons/BackButton.tsx @@ -11,9 +11,9 @@ export function BackButton() { return ( } + icon={({ color }) => } onPress={() => navigation.goBack()} /> ); diff --git a/client/components/profile/CircleCard.tsx b/client/components/profile/CircleCard.tsx index 535d6b8..bda2f13 100644 --- a/client/components/profile/CircleCard.tsx +++ b/client/components/profile/CircleCard.tsx @@ -12,7 +12,7 @@ export function CircleCard({ onTouchEnd, ButtonText, Icon }: CircleCardProps) { {Icon} - + {ButtonText} diff --git a/client/components/profile/Group.tsx b/client/components/profile/Group.tsx index 802e296..fafc9e9 100644 --- a/client/components/profile/Group.tsx +++ b/client/components/profile/Group.tsx @@ -7,7 +7,6 @@ import { View } from 'react-native'; -import { useCareWalletContext } from '../../contexts/CareWalletContext'; import { GroupRole, Role } from '../../types/group'; import { User } from '../../types/user'; @@ -28,7 +27,6 @@ export function Group({ setActiveUser, activeUser }: GroupProps) { - const { user: signedInUser } = useCareWalletContext(); const [canPress, setCanPress] = useState(true); if (rolesAreLoading || usersAreLoading) { @@ -60,7 +58,6 @@ export function Group({ data={users.filter( (user) => user.user_id !== activeUser && - user.user_id !== signedInUser.userID && user.user_id !== (roles.find((role) => role.role === Role.PATIENT)?.user_id ?? '') )} diff --git a/client/components/profile/Header.tsx b/client/components/profile/Header.tsx index baf98ba..f5a36e1 100644 --- a/client/components/profile/Header.tsx +++ b/client/components/profile/Header.tsx @@ -4,23 +4,18 @@ import { Text, View } from 'react-native'; import { IconButton } from 'react-native-paper'; import Edit from '../../assets/profile/edit.svg'; -import { useCareWalletContext } from '../../contexts/CareWalletContext'; import { GroupRole, Role } from '../../types/group'; import { User } from '../../types/user'; -import { BackButton } from '../nav_buttons/BackButton'; -import { NavigationLeftArrow } from '../nav_buttons/NavigateLeftArrow'; interface HeaderProps { user: User | undefined; role: GroupRole | undefined; - onPress?: () => void; } -export function Header({ user, role, onPress }: HeaderProps) { - const { user: signedInUser } = useCareWalletContext(); +export function Header({ user, role }: HeaderProps) { if (!user) return null; - return signedInUser.userID === user.user_id ? ( + return ( @@ -46,37 +41,5 @@ export function Header({ user, role, onPress }: HeaderProps) { - ) : ( - - - {role?.role === Role.PATIENT ? ( - - ) : ( - onPress && - )} - - {role?.role === Role.PATIENT ? ( - - Patient Information - - ) : ( - <> - - - - {user.first_name} {user.last_name} - - - {`${role?.role} CARETAKER`} - - - {user.phone ? user.phone : user.email} - - - - - - )} - ); } diff --git a/client/components/profile/HealthStats.tsx b/client/components/profile/HealthStats.tsx index be3d14f..b931348 100644 --- a/client/components/profile/HealthStats.tsx +++ b/client/components/profile/HealthStats.tsx @@ -1,29 +1,24 @@ import React, { useState } from 'react'; -import { FlatList, Pressable, Text, View } from 'react-native'; +import { FlatList, Pressable, View } from 'react-native'; export function HealthStats() { const [, setCanPress] = useState(true); - const HealthStats: string[] = []; - - return HealthStats.length > 0 ? ( + return ( setCanPress(false)} onScrollEndDrag={() => setCanPress(true)} horizontal showsHorizontalScrollIndicator={false} - data={HealthStats} + data={Array.from({ length: 10 }, (_, i) => i)} renderItem={({ index }) => ( {}}> - + )} /> - ) : ( - - There are no health stats to view. - ); } diff --git a/client/components/profile/UserTaskStatusCard.tsx b/client/components/profile/UserTaskStatusCard.tsx index 5f13c7d..9d1e6ea 100644 --- a/client/components/profile/UserTaskStatusCard.tsx +++ b/client/components/profile/UserTaskStatusCard.tsx @@ -1,11 +1,9 @@ import React from 'react'; import { Text, View } from 'react-native'; -import { useCareWalletContext } from '../../contexts/CareWalletContext'; import { useTaskByAssigned } from '../../services/task'; export function UserTaskStatusCard({ userID }: { userID: string }) { - const { user: signedInUser } = useCareWalletContext(); const { taskByUser, taskByUserIsLoading } = useTaskByAssigned(userID); if (taskByUserIsLoading) { @@ -15,20 +13,18 @@ export function UserTaskStatusCard({ userID }: { userID: string }) { return ( - - {userID === signedInUser.userID ? 'YOUR TASKS' : 'TASKS'} - + YOUR TASKS {taskByUser?.length ?? 0} - IN PROGRESS + IN PROGRESS {taskByUser?.filter((task) => task.task_status === 'INPROGRESS') .length ?? 0} - TO DO + TO DO {taskByUser?.filter((task) => task.task_status === 'TODO').length ?? 0} diff --git a/client/components/task_creation/AddressComponent.tsx b/client/components/task_creation/AddressComponent.tsx index 6caa256..fe65abf 100644 --- a/client/components/task_creation/AddressComponent.tsx +++ b/client/components/task_creation/AddressComponent.tsx @@ -5,9 +5,11 @@ export function AddressComponent() { return ( - Street Address + + STREET ADDRESS + @@ -15,16 +17,20 @@ export function AddressComponent() { - City + + CITY + - State + + STATE + @@ -32,18 +38,22 @@ export function AddressComponent() { - Zip Code + + ZIP CODE + - Phone Number + + PHONE NUMBER + { + switch (option) { + case 'Pills': + return ; + case 'Liquid': + return ; + case 'Shot': + return ; + // bathing symbol needs to be fixed + case 'Bathing': + return ; + case 'Toileting': + return ; + default: + return ; + } + }; + return ( - {title} + + {title.toUpperCase()} + - {options.map((option, index) => ( - { - handleOptionSelect(option); - }} - > - - {option} - - ))} + {options.map((option, index) => { + return ( + { + handleOptionSelect(option); + }} + > + {renderIcon(option)} + + {option} + + + ); + })} ); diff --git a/client/components/task_creation/TextInputLine.tsx b/client/components/task_creation/TextInputLine.tsx index 38fb8d2..e9084db 100644 --- a/client/components/task_creation/TextInputLine.tsx +++ b/client/components/task_creation/TextInputLine.tsx @@ -18,9 +18,11 @@ export function TextInputLine({ title, onChange }: TextInputLineProps) { return ( - {title} + + {title.toUpperCase()} + - {title} + + {title.toUpperCase()} + , + tabBarIcon: ({ color }) => , tabBarLabel: () => }} - component={Home} + component={MedicationList} /> , tabBarLabel: () => }} - component={Home} + component={MedicationList} /> - - ); } diff --git a/client/navigation/types.ts b/client/navigation/types.ts index a16fc83..8b07cdf 100644 --- a/client/navigation/types.ts +++ b/client/navigation/types.ts @@ -8,7 +8,6 @@ export type AppStackParamList = { Profile: undefined; PatientView: undefined; ProfileScreens: undefined; - FileUploadScreen: undefined; Landing: undefined; Calendar: undefined; Notifications: undefined; @@ -18,7 +17,6 @@ export type AppStackParamList = { CalendarContainer: { screen: string; params: { screen: string } } | undefined; CalendarTopNav: undefined; TaskCreation: { taskType: string }; - Settings: undefined; }; export type AppStackNavigation = NavigationProp; diff --git a/client/package.json b/client/package.json index 45daca3..7ba0267 100644 --- a/client/package.json +++ b/client/package.json @@ -52,9 +52,9 @@ "devDependencies": { "@babel/core": "^7.20.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0", + "@types/react-native": "^0.73.0", "@types/lodash": "^4.17.0", "@types/react": "^18.2.55", - "@types/react-native": "^0.73.0", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "6.18.0", "eslint": "^8.56.0", diff --git a/client/screens/MedicationList.tsx b/client/screens/MedicationList.tsx new file mode 100644 index 0000000..019604c --- /dev/null +++ b/client/screens/MedicationList.tsx @@ -0,0 +1,177 @@ +import React, { useState } from 'react'; +import { + ActivityIndicator, + Pressable, + ScrollView, + Text, + TextInput, + View +} from 'react-native'; + +import { useNavigation } from '@react-navigation/core'; +import { clsx } from 'clsx'; +import { Divider } from 'react-native-paper'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +import { ClickableCard } from '../components/ClickableCard'; +import { DocPickerButton } from '../components/DocPickerButton'; +import { PopupModal } from '../components/PopupModal'; +import { useCareWalletContext } from '../contexts/CareWalletContext'; +import { AppStackNavigation } from '../navigation/types'; +import { useAuth } from '../services/auth'; +import { useMedication } from '../services/medication'; +import { Medication } from '../types/medication'; + +export default function MedicationList() { + const [selectedMed, setSelectedMed] = useState(); + const navigator = useNavigation(); + + const [newMedState, setNewMedState] = useState({ id: '', name: '' }); + + const [medVisible, setMedVisible] = useState(false); + const [newMedVisible, setNewMedVisible] = useState(false); + const [userGroupVisible, setUserGroupVisible] = useState(false); + + const { user, group } = useCareWalletContext(); + + const { medications, medicationsIsLoading, addMedicationMutation } = + useMedication(); + + const { signOutMutation } = useAuth(); + + if (medicationsIsLoading) + return ( + + + Loading Medications... + + ); + + if (!medications) + return ( + + Could Not Load Medications List + + ); + + return ( + + + + + {selectedMed?.medication_name} + + ID: {selectedMed?.medication_id} + + + + ID: + + setNewMedState({ ...newMedState, id: val }) + } + value={newMedState.id} + /> + + + Name: + + setNewMedState({ ...newMedState, name: val }) + } + value={newMedState.name} + /> + + { + addMedicationMutation({ + medication_id: parseInt(newMedState.id), + medication_name: newMedState.name + }); + setNewMedVisible(false); + }} + > + + Add New Medication + + + + + + { + navigator.navigate('TaskType'); + }} + > + + Add New Task + + + + + {medications.map((med, index) => ( + + { + setSelectedMed(med); + setMedVisible(true); + }} + > + + ID: {med.medication_id} + + + {index !== medications.length - 1 ? : null} + + ))} + + setUserGroupVisible(true)} + className="mb-2 w-80 self-center rounded-md border border-carewallet-gray " + > + + Show User and Group Info + + + + + User ID: {user.userID} + + User Email: {user.userEmail} + + + Group ID: {group.groupID} + + Group Role: {group.role} + + { + setUserGroupVisible(false); + signOutMutation(); + }} + className="w-20 self-center rounded-md border border-carewallet-gray" + > + + Sign Out + + + + + + ); +} diff --git a/client/screens/TaskCreation.tsx b/client/screens/TaskCreation.tsx index 7c275c1..adef70a 100644 --- a/client/screens/TaskCreation.tsx +++ b/client/screens/TaskCreation.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Text, View } from 'react-native'; +import { SafeAreaView, Text, View } from 'react-native'; import { RouteProp, useRoute } from '@react-navigation/native'; import { @@ -7,6 +7,11 @@ import { ScrollView } from 'react-native-gesture-handler'; +import FinancialBg from '../assets/task-creation/financial-bg.svg'; +import HealthBg from '../assets/task-creation/health-bg.svg'; +import HomeBg from '../assets/task-creation/home-bg.svg'; +import OtherBg from '../assets/task-creation/other-bg.svg'; +import PersonalBg from '../assets/task-creation/personal-bg.svg'; import { BackButton } from '../components/nav_buttons/BackButton'; import { AddressComponent } from '../components/task_creation/AddressComponent'; import { RadioGroup } from '../components/task_creation/RadioGroup'; @@ -14,13 +19,26 @@ import { TextInputLine } from '../components/task_creation/TextInputLine'; import { TextInputParagraph } from '../components/task_creation/TextInputParagraph'; import { TaskCreationJson } from '../types/task-creation-json'; +const TaskTitleToColorMap: { [key: string]: string } = { + 'Medication Management': 'carewallet-pink', + 'Physician Appointments': 'carewallet-pink', + Grooming: 'carewallet-purple', + 'Family Conversations': 'carewallet-purple', + 'Shopping & Errands': 'carewallet-purple', + 'Pay Bills': 'carewallet-purple', + Diet: 'carewallet-yellow', + Activities: 'carewallet-yellow', + 'Health Insurance': 'carewallet-green', + Other: 'carewallet-coral' +}; + type ParamList = { mt: { taskType: string; }; }; -export default function TaskCreation() { +export function TaskCreation() { const route = useRoute>(); const { taskType } = route.params; @@ -28,12 +46,38 @@ export default function TaskCreation() { taskType.includes(t.Header) )?.Header; + const renderBackground = (header: string) => { + switch (header) { + case 'Medication Management': + return ; + case 'Physician Appointments': + return ; + case 'Grooming': + return ; + case 'Family Conversations': + return ; + case 'Shopping & Errands': + return ; + case 'Pay Bills': + return ; + case 'Diet': + return ; + case 'Activities': + return ; + case 'Health Insurance': + return ; + case 'Other': + return ; + default: + return null; + } + }; + const body = TaskCreationJson.types.find((t) => taskType.includes(t.Header) )?.Body; const compList: { key: string; value: string }[] = []; - body?.forEach((item) => { Object.entries(item).forEach(([key, value]) => { compList.push({ key, value }); @@ -41,7 +85,6 @@ export default function TaskCreation() { }); const [values, setValues] = useState<{ [key: string]: string }>({}); - const handleChange = (key: string, value: string) => { setValues((prevValues) => ({ ...prevValues, @@ -50,43 +93,53 @@ export default function TaskCreation() { console.log('Current values:', values); }; + const themeColor = TaskTitleToColorMap[header as string]; + console.log('Header:', header); + console.log('Theme color:', themeColor); + return ( - - - - - - - - Step 1 of 2 + + + + + + Step 2 of 3 - {header} - {compList.map((item, index) => ( - - {item.key === 'Address' && } - {item.value === 'TextInputLine' && ( - handleChange(item.key, value)} - /> - )} - {item.value === 'TextInputParagraph' && ( - handleChange(item.key, value)} - /> - )} - {item.value.startsWith('RadioGroup') && ( - handleChange(item.key, value)} - /> - )} - - ))} - - + + {renderBackground(header ?? '')} + + + {header} + + {compList.map((item, index) => ( + + {item.key === 'Address' && } + {item.value === 'TextInputLine' && ( + handleChange(item.key, value)} + /> + )} + {item.value === 'TextInputParagraph' && ( + handleChange(item.key, value)} + /> + )} + {item.value.startsWith('RadioGroup') && ( + handleChange(item.key, value)} + /> + )} + + ))} + + + ); } diff --git a/client/screens/TaskType.tsx b/client/screens/TaskType.tsx index 9def3de..70ba4e6 100644 --- a/client/screens/TaskType.tsx +++ b/client/screens/TaskType.tsx @@ -5,7 +5,7 @@ import React, { useRef, useState } from 'react'; -import { FlatList, View } from 'react-native'; +import { SafeAreaView, View } from 'react-native'; import BottomSheet, { BottomSheetBackdrop, @@ -14,15 +14,23 @@ import BottomSheet, { import { BottomSheetDefaultBackdropProps } from '@gorhom/bottom-sheet/lib/typescript/components/bottomSheetBackdrop/types'; import { useNavigation } from '@react-navigation/native'; import DropDownPicker from 'react-native-dropdown-picker'; -import { GestureHandlerRootView } from 'react-native-gesture-handler'; +import { + GestureHandlerRootView, + ScrollView +} from 'react-native-gesture-handler'; import { Button, Text } from 'react-native-paper'; +import Financial from '../assets/financial.svg'; +import Home from '../assets/home.svg'; +import Other from '../assets/other.svg'; +import Personal from '../assets/personal.svg'; +import RedPill from '../assets/task-type-icon-pill.svg'; import { BackButton } from '../components/nav_buttons/BackButton'; import { CloseButton } from '../components/nav_buttons/CloseButton'; import { AppStackNavigation } from '../navigation/types'; import { Category, CategoryToTypeMap, TypeOfTask } from '../types/type'; -export default function TaskType() { +export function TaskType() { const navigation = useNavigation(); const [open, setOpen] = useState(false); @@ -33,6 +41,7 @@ export default function TaskType() { Object.values(TypeOfTask) ); + selectedTypes; useEffect(() => { setSelectedTypes( selectedCategory @@ -46,7 +55,7 @@ export default function TaskType() { value: filter })); - const bottomSheetSnapPoints = useMemo(() => ['60%'], []); + const bottomSheetSnapPoints = useMemo(() => ['50%'], []); const bottomSheetRef = useRef(null); @@ -70,88 +79,158 @@ export default function TaskType() { [] ); + function getCards(category: Category): string[] { + switch (category) { + case Category.HEALTH: + return ['Medication Management', 'Physician Appointments']; + case Category.FINANCIAL: + return ['Health Insurance']; + case Category.HOME: + return ['Diet', 'Activities']; + case Category.PERSONAL: + return [ + 'Grooming', + 'Shopping & Errands', + 'Family Conversations', + 'Pay Bills' + ]; + default: + return []; + } + } + + function getCategoryIcon(category: Category): JSX.Element | null { + switch (category) { + case Category.HEALTH: + return ; + case Category.PERSONAL: + return ; + case Category.HOME: + return ; + case Category.FINANCIAL: + return ; + case Category.OTHER: + return ; + default: + return null; + } + } + + function getCategoryTitle(category: Category): string | null { + switch (category) { + case Category.HEALTH: + return 'Health & Medical'; + case Category.PERSONAL: + return 'Personal'; + case Category.HOME: + return 'Home & Lifestyle'; + case Category.FINANCIAL: + return 'Financial & Legal'; + // case Category.OTHER: <- currently no cards so grayed out right now + // return 'Other'; + default: + return null; + } + } + return ( - - - - - - - Step 1 of 2 - - - - Type of Task - - - - - - ( - - navigation.navigate('TaskCreation', { - taskType: JSON.stringify(item) - }) - } + + + + + + - - - )} - /> - - - - - - Filter - + Step 1 of 3 + + + + + + + + Choose Type of Task + + + - { - closeBottomSheet(); - }} - style={{ - width: '95%', - marginLeft: 'auto', - marginRight: 'auto', - borderRadius: 0, - borderColor: 'transparent', - borderBottomColor: 'black' - }} - /> - - - + + {Object.values(Category).map((item, index) => ( + + 0 ? 'p-3' : ''} font-carewallet-montserrat-bold text-[11px] uppercase tracking-wide`} + > + {getCategoryTitle(item)} + + + {getCards(item).map((item2, index2) => ( + + navigation.navigate('TaskCreation', { + taskType: JSON.stringify(item2) + }) + } + > + + {getCategoryIcon(item)} + + {item2} + + + + ))} + + + ))} + + + + + + + + Filter + + + + + { + closeBottomSheet(); + }} + style={{ + width: '95%', + marginLeft: 'auto', + marginRight: 'auto', + borderRadius: 0, + borderColor: 'black', + borderBottomColor: 'black' + }} + /> + + + + ); } diff --git a/client/services/file.ts b/client/services/file.ts index 7fd93a7..6a25312 100644 --- a/client/services/file.ts +++ b/client/services/file.ts @@ -13,16 +13,12 @@ interface UploadFileProps { file: DocumentPickerAsset; userId: string; groupId: number; - label: string; - notes: string; } const uploadFile = async ({ file, userId, - groupId, - label, - notes + groupId }: UploadFileProps): Promise => { const uploadResumable = createUploadTask( `${api_url}/files/upload`, @@ -33,9 +29,7 @@ const uploadFile = async ({ fieldName: 'file_data', parameters: { upload_by: userId, - group_id: groupId.toString(), - label_name: label, - notes: notes + group_id: groupId.toString() } } ); diff --git a/client/services/medication.ts b/client/services/medication.ts new file mode 100644 index 0000000..fbfb334 --- /dev/null +++ b/client/services/medication.ts @@ -0,0 +1,72 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import axios from 'axios'; + +import { Medication } from '../types/medication'; +import { api_url } from './api-links'; + +const getAllMedications = async (): Promise => { + await new Promise((r) => setTimeout(r, 2000)); // this is just to show is loading (DONT ADD ANYWHERE ELSE) + const { data } = await axios.get(`${api_url}/medications`); + return data; +}; + +const addMedication = async (med: Medication): Promise => { + const { status } = await axios.post(`${api_url}/medications`, { + medication_id: med.medication_id, + medication_name: med.medication_name + }); + + return status; +}; + +export const useMedication = () => { + const queryClient = useQueryClient(); + + const { data: medications, isLoading: medicationsIsLoading } = useQuery< + Medication[] + >({ + queryKey: ['medList'], // if querying with a value add values here ex. ['medList', {id}] + queryFn: getAllMedications + }); + + const { mutate: addMedicationMutation } = useMutation({ + mutationFn: (med: Medication) => addMedication(med), + //Optimistically update the medlist so it looks like the mutation has been completed + onMutate: async (newMed) => { + // Cancel any outgoing refetches + // (so they don't overwrite the optimistic update) + await queryClient.cancelQueries({ + queryKey: ['medList', newMed.medication_id] + }); + + // Snapshot the previous value + const previousMedication = queryClient.getQueryData(['medList']); + + // Optimistically update to the new value + queryClient.setQueryData(['medList'], (old: Medication[]) => [ + ...old, + newMed + ]); + + // Return a context with the previous and new todo + return { previousMedication }; + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ['medList'] // mark medlist as stale so it refetches + }); + return; + }, + // If the mutation fails, use the context we returned above to return to the previous state + onError: (err, newMed, context) => { + console.log('ERROR: Failed to Add Medication...'); + queryClient.setQueryData(['medList'], context?.previousMedication); + } + }); + + return { + medications, + medicationsIsLoading, + addMedicationMutation + }; +}; diff --git a/client/services/task.ts b/client/services/task.ts index 23498de..85d5a22 100644 --- a/client/services/task.ts +++ b/client/services/task.ts @@ -106,7 +106,7 @@ export const addNewTaskMutation = () => { const { mutate: addTaskMutation } = useMutation({ mutationFn: (newTask: Task) => addNewTask(newTask), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['filteredTaskList'] }); + queryClient.invalidateQueries('filteredTaskList'); }, onError: (err) => { console.error('ERROR: Failed to Add Task. Code:', err); @@ -128,7 +128,7 @@ export const editTaskMutation = () => { updatedTask: Task; }) => editTask(taskId, updatedTask), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['filteredTaskList'] }); + queryClient.invalidateQueries('filteredTaskList'); }, onError: (err) => { console.error('ERROR: Failed to Edit Task. Code:', err); diff --git a/client/tailwind.config.js b/client/tailwind.config.js index d62e259..ce1f1f9 100644 --- a/client/tailwind.config.js +++ b/client/tailwind.config.js @@ -21,7 +21,6 @@ module.exports = { 'carewallet-pink': '#FC2C51', 'carewallet-orange': '#FF8310' }, - fontFamily: { 'carewallet-manrope': ['Manrope_400Regular'], 'carewallet-manrope-semibold': ['Manrope_600SemiBold'], diff --git a/client/tsconfig.json b/client/tsconfig.json index 7886cc1..6543daf 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -2,6 +2,7 @@ "extends": "expo/tsconfig.base", "compilerOptions": { "jsx": "react", - "strict": true + "strict": true, + "allowImportingTsExtensions": true } } diff --git a/client/types/task-creation-json.ts b/client/types/task-creation-json.tsx similarity index 87% rename from client/types/task-creation-json.ts rename to client/types/task-creation-json.tsx index c20d785..da4e5cc 100644 --- a/client/types/task-creation-json.ts +++ b/client/types/task-creation-json.tsx @@ -2,10 +2,11 @@ export const TaskCreationJson = { types: [ { Header: 'Medication Management', + Background: 'client/assets/task-creation/big-bg-med.svg', Body: [ { 'Drug Name': 'TextInputLine' }, { - 'Drug Form': 'RadioGroup: Pills, Liquid, Shot' + 'Drug Form': 'RadioGroup: Pills Liquid Shot' }, { Diagnosis: 'TextInputLine' }, { 'Prescribing Physician': 'TextInputLine' }, @@ -24,7 +25,7 @@ export const TaskCreationJson = { { Header: 'Grooming', Body: [ - { 'Grooming Type': 'RadioGroup: Bathing, Toileting' }, + { 'Grooming Type': 'RadioGroup: Bathing Toileting' }, { 'Care Instructions': 'TextInputParagraph' } ] }, @@ -39,7 +40,7 @@ export const TaskCreationJson = { { Header: 'Pay Bills', Body: [ - { 'Bill Reveicer Name': 'TextInputLine' }, + { 'Bill Receiver Name': 'TextInputLine' }, { Address: '' }, { 'Proxy Agent': 'TextInputLine' } ] diff --git a/client/types/type.tsx b/client/types/type.tsx index 3a6da96..385ca2f 100644 --- a/client/types/type.tsx +++ b/client/types/type.tsx @@ -5,15 +5,16 @@ import HealthMedical from '../assets/calendar/health&medical.svg'; import HomeLifestyle from '../assets/calendar/home&lifestyle.svg'; import Personal from '../assets/calendar/personal.svg'; +// the order of these matter export enum TypeOfTask { MEDICATION = 'Medication Management', - APPOINTMENTS = 'Physician Appointments', + DIET = 'Diet', GROOMING = 'Grooming', CONVERSATIONS = 'Family Conversations', + APPOINTMENTS = 'Physician Appointments', + ACTIVITIES = 'Activities', ERRANDS = 'Shopping & Errands', BILLS = 'Pay Bills', - DIET = 'Diet', - ACTIVITIES = 'Activities', INSURANCE = 'Health Insurance', OTHER = 'Other' } diff --git a/package.json b/package.json new file mode 100644 index 0000000..cd574b2 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "lodash": "^4.17.21" + } +}