From 0d4658a0453d69e0d00e5d6ff392f703a872c648 Mon Sep 17 00:00:00 2001 From: Kyle Morel Date: Wed, 17 Apr 2024 17:48:55 -0700 Subject: [PATCH 1/2] Refactor SubmissionView + child components to use a store All components now reference a single submissionStore. Number of emits seriously cut down --- frontend/src/components/file/DocumentCard.vue | 18 ++- frontend/src/components/file/FileUpload.vue | 12 +- frontend/src/components/note/NoteModal.vue | 25 +++- frontend/src/components/permit/PermitCard.vue | 68 ++-------- .../src/components/permit/PermitModal.vue | 61 +++++++-- frontend/src/components/roadmap/Roadmap.vue | 56 ++++---- .../components/submission/SubmissionForm.vue | 37 ++++-- .../components/submission/SubmissionList.vue | 2 +- frontend/src/store/index.ts | 1 + frontend/src/store/submissionStore.ts | 123 ++++++++++++++++++ frontend/src/views/SubmissionView.vue | 123 +++++------------- .../unit/components/note/NoteModal.spec.ts | 27 ++-- 12 files changed, 326 insertions(+), 227 deletions(-) create mode 100644 frontend/src/store/submissionStore.ts diff --git a/frontend/src/components/file/DocumentCard.vue b/frontend/src/components/file/DocumentCard.vue index fd2e6a64..7f94aabf 100644 --- a/frontend/src/components/file/DocumentCard.vue +++ b/frontend/src/components/file/DocumentCard.vue @@ -4,6 +4,7 @@ import { ref } from 'vue'; import { Button, Card, useConfirm, useToast } from '@/lib/primevue'; import { documentService } from '@/services'; +import { useSubmissionStore } from '@/store'; import { FILE_CATEGORIES } from '@/utils/constants'; import { formatDateLong } from '@/utils/formatters'; import { getFileCategory } from '@/utils/utils'; @@ -35,7 +36,10 @@ const props = withDefaults(defineProps(), { }); // Emits -const emit = defineEmits(['document:clicked', 'document:deleted']); +const emit = defineEmits(['document:clicked']); + +// Store +const submissionStore = useSubmissionStore(); // State const isSelected: Ref = ref(props.selected); @@ -44,18 +48,18 @@ const isSelected: Ref = ref(props.selected); const confirm = useConfirm(); const toast = useToast(); -const confirmDelete = (documentId: string, filename: string) => { - if (documentId) { +const confirmDelete = (document: Document) => { + if (document) { confirm.require({ - message: `Please confirm that you want to delete ${filename}.`, + message: `Please confirm that you want to delete ${document.filename}.`, header: 'Delete document?', acceptLabel: 'Confirm', rejectLabel: 'Cancel', accept: () => { documentService - .deleteDocument(documentId) + .deleteDocument(document.documentId) .then(() => { - emit('document:deleted', documentId); + submissionStore.removeDocument(document); toast.success('Document deleted'); }) .catch((e: any) => toast.error('Failed to deleted document', e.message)); @@ -133,7 +137,7 @@ function onClick() { aria-label="Delete object" @click=" (e) => { - confirmDelete(props.document.documentId, props.document.filename); + confirmDelete(props.document); e.stopPropagation(); } " diff --git a/frontend/src/components/file/FileUpload.vue b/frontend/src/components/file/FileUpload.vue index 82371df3..a4e9719f 100644 --- a/frontend/src/components/file/FileUpload.vue +++ b/frontend/src/components/file/FileUpload.vue @@ -4,11 +4,10 @@ import { ref } from 'vue'; import { FileUpload, useToast } from '@/lib/primevue'; import { documentService } from '@/services'; -import { useConfigStore } from '@/store'; +import { useConfigStore, useSubmissionStore } from '@/store'; import type { FileUploadUploaderEvent } from 'primevue/fileupload'; import type { Ref } from 'vue'; -import type { Document } from '@/types'; // Props type Props = { @@ -17,10 +16,9 @@ type Props = { const props = withDefaults(defineProps(), {}); -const lastUploadedDocument = defineModel('lastUploadedDocument'); - // Store const { getConfig } = storeToRefs(useConfigStore()); +const submissionStore = useSubmissionStore(); // State const fileInput: Ref = ref(null); @@ -42,10 +40,10 @@ const onUpload = async (file: File) => { ?.data; if (response) { - lastUploadedDocument.value = response; - } + submissionStore.addDocument(response); - toast.success('Document uploaded'); + toast.success('Document uploaded'); + } } catch (e: any) { toast.error('Failed to upload document', e); } diff --git a/frontend/src/components/note/NoteModal.vue b/frontend/src/components/note/NoteModal.vue index 5dcdb1dd..d70ba08f 100644 --- a/frontend/src/components/note/NoteModal.vue +++ b/frontend/src/components/note/NoteModal.vue @@ -1,19 +1,21 @@ @@ -176,10 +138,8 @@ onMounted(() => { diff --git a/frontend/src/components/permit/PermitModal.vue b/frontend/src/components/permit/PermitModal.vue index 480e14d7..24bdf9e6 100644 --- a/frontend/src/components/permit/PermitModal.vue +++ b/frontend/src/components/permit/PermitModal.vue @@ -1,10 +1,13 @@ @@ -127,9 +168,9 @@ function onSubmit(data: PermitForm, { resetForm }) { class="col-12" name="permitType" label="Permit" - :options="permitTypes" + :options="getPermitTypes" :option-label="(e) => `${e.businessDomain}: ${e.name}`" - :loading="permitTypes === undefined" + :loading="getPermitTypes === undefined" autofocus @on-change="(e: DropdownChangeEvent) => onPermitTypeChanged(e, setValues)" /> diff --git a/frontend/src/components/roadmap/Roadmap.vue b/frontend/src/components/roadmap/Roadmap.vue index 20912f93..2f5b61d7 100644 --- a/frontend/src/components/roadmap/Roadmap.vue +++ b/frontend/src/components/roadmap/Roadmap.vue @@ -1,4 +1,5 @@ @@ -234,7 +238,7 @@ watchEffect(async () => { diff --git a/frontend/src/components/submission/SubmissionForm.vue b/frontend/src/components/submission/SubmissionForm.vue index edd36aa2..62cb1b93 100644 --- a/frontend/src/components/submission/SubmissionForm.vue +++ b/frontend/src/components/submission/SubmissionForm.vue @@ -13,8 +13,9 @@ import { InputText, TextArea } from '@/components/form'; -import { Button } from '@/lib/primevue'; -import { userService } from '@/services'; +import { Button, useToast } from '@/lib/primevue'; +import { submissionService, userService } from '@/services'; +import { useSubmissionStore } from '@/store'; import { ApplicationStatusList, ContactPreferenceList, @@ -39,7 +40,10 @@ type Props = { const props = withDefaults(defineProps(), {}); // Emits -const emit = defineEmits(['submit', 'cancel']); +const emit = defineEmits(['submission:submit', 'submission:cancel']); + +// Store +const submissionStore = useSubmissionStore(); // State const assigneeOptions: Ref> = ref([]); @@ -102,6 +106,8 @@ const formSchema = object({ }); // Actions +const toast = useToast(); + const getAssigneeOptionLabel = (e: User) => { return `${e.fullName} [${e.email}]`; }; @@ -119,10 +125,10 @@ const onAssigneeInput = async (e: IInputEvent) => { }; const onCancel = () => { - emit('cancel'); + emit('submission:cancel'); }; -const onSubmit = (values: any) => { +const onSubmit = async (values: any) => { // Ensure child values are reset if parent not set if (!values.addedToATS) { values.atsClientNumber = undefined; @@ -135,16 +141,29 @@ const onSubmit = (values: any) => { values.financiallySupportedHousingCoop = false; } - const submissionData = { + const submissionDataTransform = { ...values, assignedUserId: values.user?.userId ?? undefined, ...values.submissionTypes }; - delete submissionData.submissionTypes; - delete submissionData.user; + delete submissionDataTransform.submissionTypes; + delete submissionDataTransform.user; + + try { + const submissionData = { + ...submissionDataTransform + }; + await submissionService.updateSubmission(submissionData.submissionId, submissionData); - emit('submit', submissionData); + submissionStore.setSubmission(submissionData); + + toast.success('Form saved'); + } catch (e: any) { + toast.error('Failed to save submission', e.message); + } finally { + emit('submission:submit'); + } }; onBeforeMount(async () => { diff --git a/frontend/src/components/submission/SubmissionList.vue b/frontend/src/components/submission/SubmissionList.vue index 8050d0f8..8f460ee5 100644 --- a/frontend/src/components/submission/SubmissionList.vue +++ b/frontend/src/components/submission/SubmissionList.vue @@ -94,7 +94,7 @@ const filters = ref({ @click="handleCreateNewActivity" /> - + >; + notes: Ref>; + permits: Ref>; + permitTypes: Ref>; + submission: Ref; +}; + +export const useSubmissionStore = defineStore('submission', () => { + // State + const state: SubmissionStoreState = { + documents: ref([]), + notes: ref([]), + permits: ref([]), + permitTypes: ref([]), + submission: ref(undefined) + }; + + // Getters + const getters = { + getDocuments: computed(() => state.documents.value), + getNotes: computed(() => state.notes.value), + getPermits: computed(() => state.permits.value), + getPermitTypes: computed(() => state.permitTypes.value), + getSubmission: computed(() => state.submission.value) + }; + + // Actions + function addDocument(data: Document) { + state.documents.value.push(data); + } + + function clearDocuments() { + state.documents.value = []; + } + + function removeDocument(data: Document) { + state.documents.value = state.documents.value.filter((x) => x.documentId !== data.documentId); + } + + function setDocuments(data: Array) { + state.documents.value = data; + } + + function addNote(data: Note, prepend: boolean = false) { + if (prepend) state.notes.value.unshift(data); + else state.notes.value.push(data); + } + + function clearNotes() { + state.notes.value = []; + } + + function removeNote(data: Note) { + state.notes.value = state.notes.value.filter((x) => x.noteId !== data.noteId); + } + + function setNotes(data: Array) { + state.notes.value = data; + } + + function addPermit(data: Permit) { + state.permits.value.push(data); + } + + function clearPermits() { + state.permits.value = []; + } + + function removePermit(data: Permit) { + state.permits.value = state.permits.value.filter((x) => x.permitId !== data.permitId); + } + + function setPermits(data: Array) { + state.permits.value = data; + } + + function updatePermit(data: Permit) { + const idx = state.permits.value.findIndex((x: Permit) => x.permitId === data.permitId); + if (idx >= 0) state.permits.value[idx] = data; + } + + function setPermitTypes(data: Array) { + state.permitTypes.value = data; + } + + function setSubmission(data: Submission | undefined) { + state.submission.value = data; + } + + return { + // State + ...state, + + // Getters + ...getters, + + // Actions + addDocument, + clearDocuments, + removeDocument, + setDocuments, + addNote, + clearNotes, + removeNote, + setNotes, + addPermit, + clearPermits, + removePermit, + setPermits, + updatePermit, + setPermitTypes, + setSubmission + }; +}); + +export default useSubmissionStore; diff --git a/frontend/src/views/SubmissionView.vue b/frontend/src/views/SubmissionView.vue index 9ab4fec4..1372ac6a 100644 --- a/frontend/src/views/SubmissionView.vue +++ b/frontend/src/views/SubmissionView.vue @@ -1,4 +1,5 @@ @@ -116,20 +75,20 @@ onMounted(async () => {

Activity submission: - {{ submission.activityId }} + {{ getSubmission.activityId }} - + - - {{ submission.projectName }} + {{ getSubmission.projectName }}

- +
- +
-

Notes ({{ notes.length }})

+

Notes ({{ getNotes.length }})

{ diff --git a/frontend/tests/unit/components/note/NoteModal.spec.ts b/frontend/tests/unit/components/note/NoteModal.spec.ts index 64bd515d..d479d61f 100644 --- a/frontend/tests/unit/components/note/NoteModal.spec.ts +++ b/frontend/tests/unit/components/note/NoteModal.spec.ts @@ -1,5 +1,7 @@ import { createTestingPinia } from '@pinia/testing'; -import { mount } from '@vue/test-utils'; +import PrimeVue from 'primevue/config'; +import ToastService from 'primevue/toastservice'; +import { shallowMount } from '@vue/test-utils'; import NoteModal from '@/components/note/NoteModal.vue'; import { StorageKey } from '@/utils/constants'; @@ -31,10 +33,10 @@ const testNote: Note = { isDeleted: false }; -const wrapperSettings = (visibleProp: boolean = true) => ({ +const wrapperSettings = () => ({ props: { - note: testNote, - visible: visibleProp + activityId: '123', + note: testNote }, global: { plugins: [ @@ -80,18 +82,9 @@ afterEach(() => { }); // Currently, modal functionality hidden behind Primevue component Dialog -describe('noteModal test', () => { - it('sets dialog component prop "visible" to true', async () => { - const noteWrapper = mount(NoteModal, wrapperSettings(true)); - - const dialogComponent = noteWrapper.getComponent({ name: 'Dialog' }); - expect(dialogComponent.props('visible')).toBe(true); - }); - - it('sets dialog component prop "visible" to false', async () => { - const noteWrapper = mount(NoteModal, wrapperSettings(false)); - - const dialogComponent = noteWrapper.getComponent({ name: 'Dialog' }); - expect(dialogComponent.props('visible')).toBe(false); +describe('NoteModal', () => { + it('renders', () => { + const wrapper = shallowMount(NoteModal, wrapperSettings()); + expect(wrapper).toBeTruthy(); }); }); From 15465bb943bcbacc87cc8e6ffb9b333e945a2a08 Mon Sep 17 00:00:00 2001 From: Kyle Morel Date: Fri, 19 Apr 2024 11:53:32 -0700 Subject: [PATCH 2/2] Refactor NoteModal to correctly use default props Minor other tweaks - SubmissionForm always editable --- app/src/controllers/document.ts | 2 +- app/src/controllers/note.ts | 2 +- app/src/controllers/permit.ts | 2 +- app/src/controllers/submission.ts | 44 ++--- app/src/services/note.ts | 28 ++- app/tests/unit/controllers/document.spec.ts | 4 +- app/tests/unit/controllers/note.spec.ts | 4 +- app/tests/unit/controllers/permit.spec.ts | 4 +- frontend/src/components/file/DocumentCard.vue | 2 +- frontend/src/components/file/FileUpload.vue | 9 +- frontend/src/components/note/NoteCard.vue | 44 +---- frontend/src/components/note/NoteModal.vue | 160 +++++++----------- frontend/src/components/permit/PermitCard.vue | 4 +- .../src/components/permit/PermitModal.vue | 10 +- .../SubmissionBringForwardCalendar.vue | 10 +- .../components/submission/SubmissionForm.vue | 98 +++++------ .../components/submission/SubmissionList.vue | 37 ++-- .../submission/SubmissionStatistics.vue | 2 +- frontend/src/lib/primevue/index.ts | 2 + frontend/src/store/submissionStore.ts | 21 +-- frontend/src/utils/constants.ts | 2 +- frontend/src/utils/enums.ts | 3 +- frontend/src/views/SubmissionView.vue | 30 +--- frontend/src/views/SubmissionsView.vue | 2 +- .../unit/components/note/NoteModal.spec.ts | 4 +- 25 files changed, 224 insertions(+), 306 deletions(-) diff --git a/app/src/controllers/document.ts b/app/src/controllers/document.ts index 2062bc8f..a53ed34a 100644 --- a/app/src/controllers/document.ts +++ b/app/src/controllers/document.ts @@ -20,7 +20,7 @@ const controller = { req.body.mimeType, req.body.length ); - res.status(200).json(response); + res.status(201).json(response); } catch (e: unknown) { next(e); } diff --git a/app/src/controllers/note.ts b/app/src/controllers/note.ts index 951619af..d58ec8ee 100644 --- a/app/src/controllers/note.ts +++ b/app/src/controllers/note.ts @@ -17,7 +17,7 @@ const controller = { ...body, createdBy: userId }); - res.status(200).json(response); + res.status(201).json(response); } catch (e: unknown) { next(e); } diff --git a/app/src/controllers/permit.ts b/app/src/controllers/permit.ts index 27e2eabb..5d5ad836 100644 --- a/app/src/controllers/permit.ts +++ b/app/src/controllers/permit.ts @@ -11,7 +11,7 @@ const controller = { try { const userId = await userService.getCurrentUserId(getCurrentIdentity(req.currentUser, NIL), NIL); const response = await permitService.createPermit({ ...(req.body as Permit), updatedBy: userId }); - res.status(200).json(response); + res.status(201).json(response); } catch (e: unknown) { next(e); } diff --git a/app/src/controllers/submission.ts b/app/src/controllers/submission.ts index 1eab45a9..397d63a8 100644 --- a/app/src/controllers/submission.ts +++ b/app/src/controllers/submission.ts @@ -9,28 +9,6 @@ import type { NextFunction, Request, Response } from '../interfaces/IExpress'; import type { ChefsFormConfig, ChefsFormConfigData, Submission, ChefsSubmissionExport, Permit } from '../types'; const controller = { - createEmptySubmission: async (req: Request, res: Response, next: NextFunction) => { - let testSubmissionId; - let submissionQuery; - - // Testing for activityId collisions, which are truncated UUIDs - // If a collision is detected, generate new UUID and test again - do { - testSubmissionId = uuidv4(); - submissionQuery = await submissionService.getSubmission(testSubmissionId.substring(0, 8).toUpperCase()); - } while (submissionQuery); - - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const submitter = (req.currentUser?.tokenPayload as any)?.idir_username; - const result = await submissionService.createEmptySubmission(testSubmissionId, submitter); - - res.status(201).json({ activityId: result.activity_id }); - } catch (e: unknown) { - next(e); - } - }, - checkAndStoreNewSubmissions: async () => { const cfg = config.get('server.chefs.forms') as ChefsFormConfig; @@ -175,6 +153,28 @@ const controller = { notStored.map((x) => x.permits?.map(async (y) => await permitService.createPermit(y))); }, + createEmptySubmission: async (req: Request, res: Response, next: NextFunction) => { + let testSubmissionId; + let submissionQuery; + + // Testing for activityId collisions, which are truncated UUIDs + // If a collision is detected, generate new UUID and test again + do { + testSubmissionId = uuidv4(); + submissionQuery = await submissionService.getSubmission(testSubmissionId.substring(0, 8).toUpperCase()); + } while (submissionQuery); + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const submitter = (req.currentUser?.tokenPayload as any)?.idir_username; + const result = await submissionService.createEmptySubmission(testSubmissionId, submitter); + + res.status(201).json({ activityId: result.activity_id }); + } catch (e: unknown) { + next(e); + } + }, + getStatistics: async ( req: Request, res: Response, diff --git a/app/src/services/note.ts b/app/src/services/note.ts index 2e1b1427..ed62185c 100644 --- a/app/src/services/note.ts +++ b/app/src/services/note.ts @@ -24,6 +24,12 @@ const service = { return note.fromPrismaModel(response); }, + /** + * @function deleteNote + * Soft deletes a note by marking is as deleted + * @param {string} noteId ID of the note to delete + * @returns {Promise} The result of running the update operation + */ deleteNote: async (noteId: string) => { const result = await prisma.note.update({ where: { @@ -76,8 +82,15 @@ const service = { return response.map((x) => note.fromPrismaModel(x)); }, + /** + * @function updateNote + * Updates a note by marking the old note as deleted and creating a new one + * @param {Note} data New Note object + * @returns {Promise} The result of running the transaction + */ updateNote: async (data: Note) => { return await prisma.$transaction(async (trx) => { + // Mark old note as deleted await trx.note.update({ where: { note_id: data.noteId @@ -87,16 +100,15 @@ const service = { } }); - const newNote = { - ...data, - noteId: uuidv4() - }; - - const newCreatedNote = await trx.note.create({ - data: note.toPrismaModel(newNote) + // Create new note + const response = await trx.note.create({ + data: note.toPrismaModel({ + ...data, + noteId: uuidv4() + }) }); - return note.fromPrismaModel(newCreatedNote); + return note.fromPrismaModel(response); }); } }; diff --git a/app/tests/unit/controllers/document.spec.ts b/app/tests/unit/controllers/document.spec.ts index 81e8188b..4277aace 100644 --- a/app/tests/unit/controllers/document.spec.ts +++ b/app/tests/unit/controllers/document.spec.ts @@ -29,7 +29,7 @@ describe('createDocument', () => { // Mock service calls const createSpy = jest.spyOn(documentService, 'createDocument'); - it('should return 200 if all good', async () => { + it('should return 201 if all good', async () => { const req = { body: { documentId: 'abc123', activityId: '1', filename: 'testfile', mimeType: 'imgjpg', length: 1234567 }, currentUser: CURRENT_USER @@ -56,7 +56,7 @@ describe('createDocument', () => { req.body.mimeType, req.body.length ); - expect(res.status).toHaveBeenCalledWith(200); + expect(res.status).toHaveBeenCalledWith(201); expect(res.json).toHaveBeenCalledWith(created); }); diff --git a/app/tests/unit/controllers/note.spec.ts b/app/tests/unit/controllers/note.spec.ts index 415e0c25..d56876cd 100644 --- a/app/tests/unit/controllers/note.spec.ts +++ b/app/tests/unit/controllers/note.spec.ts @@ -34,7 +34,7 @@ describe('createNote', () => { const getCurrentIdentitySpy = jest.spyOn(utils, 'getCurrentIdentity'); const getCurrentUserIdSpy = jest.spyOn(userService, 'getCurrentUserId'); - it('should return 200 if all good', async () => { + it('should return 201 if all good', async () => { const req = { body: { noteId: '123-123', @@ -75,7 +75,7 @@ describe('createNote', () => { expect(getCurrentUserIdSpy).toHaveBeenCalledWith(USR_IDENTITY, NIL); expect(createSpy).toHaveBeenCalledTimes(1); expect(createSpy).toHaveBeenCalledWith({ ...req.body, createdBy: USR_ID }); - expect(res.status).toHaveBeenCalledWith(200); + expect(res.status).toHaveBeenCalledWith(201); expect(res.json).toHaveBeenCalledWith(created); }); diff --git a/app/tests/unit/controllers/permit.spec.ts b/app/tests/unit/controllers/permit.spec.ts index 78f1733c..c1ed3e22 100644 --- a/app/tests/unit/controllers/permit.spec.ts +++ b/app/tests/unit/controllers/permit.spec.ts @@ -34,7 +34,7 @@ describe('createPermit', () => { const getCurrentIdentitySpy = jest.spyOn(utils, 'getCurrentIdentity'); const getCurrentUserIdSpy = jest.spyOn(userService, 'getCurrentUserId'); - it('should return 200 if all good', async () => { + it('should return 201 if all good', async () => { const now = new Date(); const req = { body: { @@ -81,7 +81,7 @@ describe('createPermit', () => { expect(getCurrentUserIdSpy).toHaveBeenCalledWith(USR_IDENTITY, NIL); expect(createSpy).toHaveBeenCalledTimes(1); expect(createSpy).toHaveBeenCalledWith({ ...req.body, updatedBy: USR_ID }); - expect(res.status).toHaveBeenCalledWith(200); + expect(res.status).toHaveBeenCalledWith(201); expect(res.json).toHaveBeenCalledWith(created); }); diff --git a/frontend/src/components/file/DocumentCard.vue b/frontend/src/components/file/DocumentCard.vue index 7f94aabf..7df2f754 100644 --- a/frontend/src/components/file/DocumentCard.vue +++ b/frontend/src/components/file/DocumentCard.vue @@ -23,8 +23,8 @@ import type { Document } from '@/types'; // Props type Props = { - document: Document; deleteButton?: boolean; + document: Document; selectable?: boolean; selected?: boolean; }; diff --git a/frontend/src/components/file/FileUpload.vue b/frontend/src/components/file/FileUpload.vue index a4e9719f..e7c0d078 100644 --- a/frontend/src/components/file/FileUpload.vue +++ b/frontend/src/components/file/FileUpload.vue @@ -26,14 +26,14 @@ const fileInput: Ref = ref(null); // Actions const toast = useToast(); -const onFileUploadDragAndDrop = (event: FileUploadUploaderEvent) => { - onUpload(Array.isArray(event.files) ? event.files[0] : event.files); -}; - const onFileUploadClick = () => { fileInput.value.click(); }; +const onFileUploadDragAndDrop = (event: FileUploadUploaderEvent) => { + onUpload(Array.isArray(event.files) ? event.files[0] : event.files); +}; + const onUpload = async (file: File) => { try { const response = (await documentService.createDocument(file, props.activityId, getConfig.value.coms.bucketId)) @@ -41,7 +41,6 @@ const onUpload = async (file: File) => { if (response) { submissionStore.addDocument(response); - toast.success('Document uploaded'); } } catch (e: any) { diff --git a/frontend/src/components/note/NoteCard.vue b/frontend/src/components/note/NoteCard.vue index f0466a0f..7bd583f6 100644 --- a/frontend/src/components/note/NoteCard.vue +++ b/frontend/src/components/note/NoteCard.vue @@ -1,9 +1,9 @@