Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/file upload screen #66

Merged
merged 10 commits into from
Apr 4, 2024
5 changes: 4 additions & 1 deletion backend/db/migrations/5.files.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ CREATE TABLE IF NOT EXISTS files (
upload_date timestamp,
file_size integer NOT NULL,
task_id integer,
notes varchar,
label_name varchar NOT NULL,
PRIMARY KEY (file_id),
FOREIGN KEY (group_id) REFERENCES care_group (group_id),
FOREIGN KEY (upload_by) REFERENCES users (user_id),
FOREIGN KEY (task_id) REFERENCES task (task_id)
FOREIGN KEY (task_id) REFERENCES task (task_id),
FOREIGN KEY (group_id, label_name) REFERENCES label (group_id, label_name)
wyattchris marked this conversation as resolved.
Show resolved Hide resolved
);
19 changes: 19 additions & 0 deletions backend/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ 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": {
Expand Down Expand Up @@ -1223,6 +1236,12 @@ const docTemplate = `{
"group_id": {
"type": "integer"
},
"label_name": {
"type": "string"
},
"notes": {
"type": "string"
},
"task_id": {
"type": "integer"
},
Expand Down
19 changes: 19 additions & 0 deletions backend/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@
"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": {
Expand Down Expand Up @@ -1216,6 +1229,12 @@
"group_id": {
"type": "integer"
},
"label_name": {
"type": "string"
},
"notes": {
"type": "string"
},
"task_id": {
"type": "integer"
},
Expand Down
13 changes: 13 additions & 0 deletions backend/docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ definitions:
type: integer
group_id:
type: integer
label_name:
type: string
notes:
type: string
task_id:
type: integer
upload_by:
Expand Down Expand Up @@ -237,6 +241,15 @@ 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
Expand Down
2 changes: 2 additions & 0 deletions backend/models/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ 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"`
}
4 changes: 4 additions & 0 deletions backend/schema/files/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ 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
Expand All @@ -48,6 +50,8 @@ 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")
Expand Down
4 changes: 2 additions & 2 deletions backend/schema/files/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) VALUES ($1, $2, $3, $4, $5) RETURNING file_id;",
file.FileName, file.GroupID, file.UploadBy, file.UploadDate, data.Size).Scan(&file.FileID)
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)
if err != nil {
fmt.Println(err.Error())
return err
Expand Down
4 changes: 2 additions & 2 deletions client/assets/arrow-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions client/assets/file-upload/circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions client/assets/file-upload/heart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions client/components/ChooseFileButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';

export function ChooseFileButton({ onPress }: { onPress: () => void }) {
return (
<TouchableOpacity
onPress={onPress}
className="mt-2 flex h-2/4 w-full items-center justify-center rounded-lg border-[3px] border-dashed border-carewallet-lightgray"
>
<Text className="text-lg text-carewallet-black">Choose File</Text>
</TouchableOpacity>
);
}
2 changes: 1 addition & 1 deletion client/components/nav_buttons/BackButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function BackButton() {

return (
<IconButton
className="align-center m-2 flex h-[50px] w-[52px] justify-center rounded-xl bg-carewallet-gray"
className="align-center m-2 flex h-[50px] w-[52px] justify-center rounded-xl bg-carewallet-blue"
wyattchris marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

@MattCMcCoy MattCMcCoy Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make sure this matches all the current pages that are hifis right now, seems its applying a blue background I think it should be white (at least for profile which is in the app rn)

mode="contained"
icon={({ color }) => <ArrowLeft fill={color} />}
onPress={() => navigation.goBack()}
Expand Down
6 changes: 6 additions & 0 deletions client/navigation/AppStackBottomTabNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Calendar from '../assets/bottom-nav/calendar.svg';
import Home from '../assets/bottom-nav/home.svg';
import User from '../assets/bottom-nav/user.svg';
import TimelineCalendarScreen from '../screens/Calendar';
import FileUploadScreen from '../screens/FileUpload';
import MedicationList from '../screens/MedicationList';
import PatientView from '../screens/Profile/PatientView';
import Profile from '../screens/Profile/Profile';
Expand Down Expand Up @@ -80,6 +81,11 @@ export function ProfileNavigation() {
options={{ headerShown: false }}
component={PatientView}
/>
<AppStack.Screen
name="FileUploadScreen"
options={{ headerShown: false }}
component={FileUploadScreen}
/>
wyattchris marked this conversation as resolved.
Show resolved Hide resolved
</AppStack.Navigator>
);
}
Expand Down
1 change: 1 addition & 0 deletions client/navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type AppStackParamList = {
Profile: undefined;
PatientView: undefined;
ProfileScreens: undefined;
FileUploadScreen: undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add the ability to navigate to file upload screen in the patient view theres already a button for it?

Landing: undefined;
Calendar: undefined;
Notifications: undefined;
Expand Down
137 changes: 137 additions & 0 deletions client/screens/FileUpload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React, { useState } from 'react';
import { Text, TextInput, TouchableOpacity, View } from 'react-native';

import { DocumentPickerAsset, getDocumentAsync } from 'expo-document-picker';
import DropDownPicker from 'react-native-dropdown-picker';

import { ChooseFileButton } from '../components/ChooseFileButton';
import { BackButton } from '../components/nav_buttons/BackButton';
import { useCareWalletContext } from '../contexts/CareWalletContext';
import { useFile } from '../services/file';

// TODO: Add SVGs to background and button, and add functionality for Title, Label, Notes
wyattchris marked this conversation as resolved.
Show resolved Hide resolved
export default function FileUploadScreen() {
const { user, group } = useCareWalletContext();
const { uploadFileMutation } = useFile();
const [open, setOpen] = useState(false);
const [fileTitle, setFileTitle] = useState('');
const [label, setLabel] = useState('Financial');
const [additionalNotes, setAdditionalNotes] = useState('');
const [pickedFile, setPickedFile] = useState<DocumentPickerAsset | null>(
null
);

const handleFileTitleChange = (text: string) => {
setFileTitle(text);
console.log('fileTitle', fileTitle);
wyattchris marked this conversation as resolved.
Show resolved Hide resolved
};

const handleAdditionalNotesChange = (text: string) => {
setAdditionalNotes(text);
console.log('notes', additionalNotes);
wyattchris marked this conversation as resolved.
Show resolved Hide resolved
};

const pickDocument = async () => {
try {
await getDocumentAsync({
type: '*/*',
copyToCacheDirectory: false
}).then((res) => {
if (!res.canceled) {
setPickedFile(res.assets[0]);
}
});
} catch (err) {
console.log('err', err);
}
};

const submitFile = async () => {
try {
if (pickedFile) {
uploadFileMutation({
file: pickedFile,
userId: user.userID,
groupId: group.groupID,
label: label,
notes: additionalNotes
});
}
} catch (err) {
console.log('err', err);
}
};

// TODO: Choosefile border color, dropdown styling, fonts, choose file svg
return (
<View className="flex h-full flex-col items-start bg-carewallet-white p-6">
<View className="w-full flex-row items-center">
<BackButton />
<View className="flex-1">
<Text className="mr-20 text-center text-xl font-medium text-carewallet-blue">
File Upload
</Text>
</View>
</View>
<ChooseFileButton onPress={pickDocument} />
<View className="mt-4 flex flex-row">
<View className="mr-4 flex-1">
<Text className="text-md mb-2 text-carewallet-black">File Title</Text>
<TextInput
className="rounded-md border border-carewallet-gray p-4"
placeholder="Text here"
value={fileTitle}
onChangeText={handleFileTitleChange}
/>
</View>
<View className="flex-1">
<Text className="text-md mb-2 text-carewallet-black">File Label</Text>
<DropDownPicker
style={{
backgroundColor: 'carewallet-blue',
borderColor: 'carewallet-lightgray',
position: 'relative',
zIndex: 10
}}
open={open}
value={label}
items={[
{ label: 'Medication', value: 'Medication' },
{ label: 'Financial', value: 'Financial' },
{ label: 'Appointments', value: 'Appointments' },
{ label: 'Household', value: 'Household' }
wyattchris marked this conversation as resolved.
Show resolved Hide resolved
]}
setOpen={setOpen}
setValue={setLabel}
placeholder="Select Label"
/>
</View>
</View>
<View className="mt-4 flex flex-row">
<View className="flex-1">
<Text className="text-md mb-2 text-carewallet-black">
Additional Notes
</Text>
<TextInput
className="w-full rounded-md border border-carewallet-gray p-8"
placeholder="Text here"
value={additionalNotes}
onChangeText={handleAdditionalNotesChange}
/>
</View>
</View>
<View className="mt-2 flex flex-row">
<View className="flex-1">
<TouchableOpacity
className="mt-2 rounded-lg bg-carewallet-blue px-8 py-5"
onPress={submitFile}
>
<Text className="text-center text-base text-carewallet-white">
Submit
</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}
6 changes: 6 additions & 0 deletions client/screens/Profile/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ export default function Profile() {
<View className="mb-auto flex-1 items-center">
<CircleCard ButtonText="Settings" />
</View>
<View className="flex-1 items-center">
<CircleCard
ButtonText="View Files"
onTouchEnd={() => navigation.navigate('FileUploadScreen')}
/>
</View>
wyattchris marked this conversation as resolved.
Show resolved Hide resolved
<View className="items-center pb-5">
<CircleCard ButtonText="Log Out" onTouchEnd={() => signOutMutation()} />
</View>
Expand Down
10 changes: 8 additions & 2 deletions client/services/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ interface UploadFileProps {
file: DocumentPickerAsset;
userId: string;
groupId: number;
label: string;
notes: string;
}

const uploadFile = async ({
file,
userId,
groupId
groupId,
label,
notes
}: UploadFileProps): Promise<FileSystemUploadResult | undefined> => {
const uploadResumable = createUploadTask(
`${api_url}/files/upload`,
Expand All @@ -29,7 +33,9 @@ const uploadFile = async ({
fieldName: 'file_data',
parameters: {
upload_by: userId,
group_id: groupId.toString()
group_id: groupId.toString(),
label_name: label,
notes: notes
}
}
);
Expand Down