Skip to content

Commit

Permalink
feat: frontend modal and tab for notes page, submit functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
wilwong89 committed Feb 14, 2024
1 parent 695e70a commit 2faae74
Show file tree
Hide file tree
Showing 19 changed files with 469 additions and 42 deletions.
1 change: 1 addition & 0 deletions app/src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as chefsController } from './chefs';
export { default as documentController } from './document';
export { default as noteController } from './note';
export { default as permitController } from './permit';
export { default as userController } from './user';
25 changes: 25 additions & 0 deletions app/src/controllers/note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { noteService } from '../services';

import type { NextFunction, Request, Response } from '../interfaces/IExpress';

const controller = {
createNote: async (req: Request, res: Response, next: NextFunction) => {
try {
const response = await noteService.createNote(req.body, req.currentUser);
res.status(200).send(response);
} catch (e: unknown) {
next(e);
}
},

async listNotes(req: Request<{ submissionId: string }>, res: Response, next: NextFunction) {
try {
const response = await noteService.listNotes(req.params.submissionId);
res.status(200).send(response);
} catch (e: unknown) {
next(e);
}
}
};

export default controller;
4 changes: 2 additions & 2 deletions app/src/db/migrations/20231212000000_init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,9 @@ export async function up(knex: Knex): Promise<void> {
.inTable('submission')
.onUpdate('CASCADE')
.onDelete('CASCADE');
table.text('category_type').defaultTo('').notNullable();
table.text('note_type').defaultTo('').notNullable();
table.text('note').defaultTo('').notNullable();
table.text('note_type').defaultTo('').notNullable();
table.text('title').defaultTo('').notNullable();
stamps(knex, table);
table.unique(['note_id']);
})
Expand Down
44 changes: 22 additions & 22 deletions app/src/db/models/note.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import { Prisma } from '@prisma/client';
import disconnectRelation from '../utils/disconnectRelation';
import { default as submission } from './submission';

import type { IStamps } from '../../interfaces/IStamps';
import type { Note } from '../../types';
import type { ChefsSubmissionForm, Note } from '../../types';

// Define types
const _note = Prisma.validator<Prisma.noteDefaultArgs>()({});
const _noteWithGraph = Prisma.validator<Prisma.noteDefaultArgs>()({});
const _noteWithGraph = Prisma.validator<Prisma.noteDefaultArgs>()({
include: { submission: { include: { user: true } } }
});

type SubmissionRelation = {
submission:
| {
connect: {
submissionId: string;
};
}
| {
disconnect: boolean;
};
submission: {
connect: {
submissionId: string;
};
};
};

type PrismaRelationNote = Omit<Prisma.noteGetPayload<typeof _note>, 'submissionId' | keyof IStamps> &
type PrismaRelationNote = Omit<Prisma.noteGetPayload<typeof _note>, 'submission_id' | keyof IStamps> &
SubmissionRelation;

type PrismaGraphNote = Prisma.noteGetPayload<typeof _noteWithGraph>;
Expand All @@ -29,24 +27,26 @@ export default {
toPrismaModel(input: Note): PrismaRelationNote {
// Note: submissionId conversion to submission_id will be required here
return {
note_id: input.note_id as string,
submission: input.submission_id ? { connect: { submissionId: input.submission_id } } : disconnectRelation,
category_type: input.category_type,
note_id: input.noteId as string,
note: input.note,
note_type: input.note_type
note_type: input.noteType,
submission: { connect: { submissionId: input.submissionId } },
title: input.title
};
},

fromPrismaModel(input: PrismaGraphNote | null): Note | null {
if (!input) return null;

return {
note_id: input.note_id,
submission_id: input.submissionId as string,
category_type: input.category_type || '',
noteId: input.note_id,
note: input.note || '',
note_type: input.note_type || '',
createdAt: input.createdAt?.toISOString()
noteType: input.note_type || '',
submission: submission.fromPrismaModel(input.submission) as ChefsSubmissionForm,
submissionId: input.submission_id as string,
title: input.title || '',
createdAt: input.createdAt?.toISOString() ?? null,
createdBy: input.createdBy
};
}
};
10 changes: 5 additions & 5 deletions app/src/db/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ model identity_provider {

model note {
note_id String @id @unique(map: "note_note_id_unique") @db.Uuid
submissionId String @db.Uuid
category_type String?
note_type String?
note String?
submission_id String @db.Uuid
note String @default("")
note_type String @default("")
title String @default("")
createdBy String? @default("00000000-0000-0000-0000-000000000000")
createdAt DateTime? @default(now()) @db.Timestamptz(6)
updatedBy String?
updatedAt DateTime? @db.Timestamptz(6)
submission submission @relation(fields: [submissionId], references: [submissionId], onDelete: Cascade, map: "note_submissionid_foreign")
submission submission @relation(fields: [submission_id], references: [submissionId], onDelete: Cascade, map: "note_submission_id_foreign")
}

model permit {
Expand Down
4 changes: 3 additions & 1 deletion app/src/routes/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { hasAccess } from '../../middleware/authorization';
import express from 'express';
import chefs from './chefs';
import document from './document';
import note from './note';
import permit from './permit';
import user from './user';

Expand All @@ -13,12 +14,13 @@ router.use(hasAccess);
// Base v1 Responder
router.get('/', (_req, res) => {
res.status(200).json({
endpoints: ['/chefs', '/document', '/permit', '/user']
endpoints: ['/chefs', '/document', '/note', '/permit', '/user']
});
});

router.use('/chefs', chefs);
router.use('/document', document);
router.use('/note', note);
router.use('/permit', permit);
router.use('/user', user);

Expand Down
20 changes: 20 additions & 0 deletions app/src/routes/v1/note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import express from 'express';
import { noteController } from '../../controllers';
import { requireSomeAuth } from '../../middleware/requireSomeAuth';

import type { NextFunction, Request, Response } from '../../interfaces/IExpress';

const router = express.Router();
router.use(requireSomeAuth);

// note create endpoint
router.put('/', (req: Request, res: Response, next: NextFunction): void => {
noteController.createNote(req, res, next);
});

// note list by submission endpoint
router.get('/list/:submission_id', (req: Request, res: Response, next: NextFunction): void => {
noteController.listNotes(req, res, next);
});

export default router;
1 change: 1 addition & 0 deletions app/src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as chefsService } from './chefs';
export { default as documentService } from './document';
export { default as noteService } from './note';
export { default as permitService } from './permit';
export { default as userService } from './user';
73 changes: 73 additions & 0 deletions app/src/services/note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import prisma from '../db/dataConnection';
import { note } from '../db/models';
import { v4 as uuidv4 } from 'uuid';
import { addDashesToUuid } from '../components/utils';

import type { Note } from '../types';

const service = {
/**
* @function createNote
* Creates a Permit
* @param note Note Object
* @returns {Promise<object>} The result of running the findUnique operation
*/
createNote: async (data: Note, currentUser: unknown) => {
const newNote = { ...data, createdBy: addDashesToUuid(currentUser.tokenPayload.idir_user_guid), noteId: uuidv4() };

const create = await prisma.note.create({
include: {
submission: {
include: { user: true }
}
},
data: note.toPrismaModel(newNote)
});

return note.fromPrismaModel(create);
},

/**
* @function deleteNote
* Delete a note
* @param noteId Note ID
* @returns {Promise<object>} The result of running the delete operation
*/
deleteNote: async (noteId: string) => {
const response = await prisma.note.delete({
include: {
submission: {
include: { user: true }
}
},
where: {
note_id: noteId
}
});

return note.fromPrismaModel(response);
},

/**
* @function listNotes
* Retrieve a list of permits associated with a given submission
* @param submissionId PCNS Submission ID
* @returns {Promise<object>} Array of documents associated with the submission
*/
listNotes: async (submissionId: string) => {
const response = await prisma.note.findMany({
include: {
submission: {
include: { user: true }
}
},
where: {
submission_id: submissionId
}
});

return response.map((x) => note.fromPrismaModel(x));
}
};

export default service;
10 changes: 6 additions & 4 deletions app/src/types/Note.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { IStamps } from '../interfaces/IStamps';
import type { ChefsSubmissionForm } from './ChefsSubmissionForm';

export type Note = {
note_id: string; // Primary Key
submission_id: string;
category_type: string;
noteId: string; // Primary Key
submissionId: string;
note: string;
note_type: string;
noteType: string;
submission: ChefsSubmissionForm;
title: string;
} & Partial<IStamps>;
106 changes: 106 additions & 0 deletions frontend/src/components/note/NoteCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { Card } from '@/lib/primevue';
import { userService } from '@/services';
import { formatDate } from '@/utils/formatters';
import type { Ref } from 'vue';
import type { Note } from '@/types';
// Props
type Props = {
note: Note;
submissionId: string;
};
const props = withDefaults(defineProps<Props>(), {});
// State
const cardData: Ref<Note> = ref(props.note);
const userName: Ref<string> = ref('');
onMounted(async () => {
if (props.note.createdBy) {
const res = (await userService.searchUsers({ userId: [props.note.createdBy] })).data;
userName.value = res.length ? res.pop().fullName : '';
}
});
</script>

<template>
<Card>
<template #header>
<div class="flex flex-row px-3 pt-3">
<div class="flex-grow-1">
<h3>{{ cardData.title }}</h3>
</div>
</div>
</template>
<template #content>
<div class="grid nested-grid">
<!-- Left column -->
<div class="col-12 md:col-6 lg:col-4">
<div class="grid">
<p class="col-12">
<span class="key font-bold">Date:</span>
{{ cardData.createdAt ? formatDate(cardData.createdAt ?? '') : undefined }}
</p>
</div>
</div>
<!-- Middle column -->
<div class="col-12 md:col-6 lg:col-4">
<div class="grid">
<p class="col-12">
<span class="key font-bold">Author:</span>
{{ userName }}
</p>
</div>
</div>
<!-- Right column -->
<div class="col-12 md:col-6 lg:col-4">
<div class="grid">
<p class="col-12">
<span class="key font-bold">Category:</span>
{{ cardData.noteType }}
</p>
</div>
</div>
<div class="col-12">
<p class="col-12">
{{ cardData.note }}
</p>
</div>
</div>
</template>
</Card>
</template>

<style lang="scss">
h2 {
margin: 0;
}
p {
margin-top: 0;
margin-bottom: 0;
}
.key {
color: #38598a;
}
.p-card {
border-style: solid;
border-width: 1px;
.p-card-body {
padding-top: 0;
padding-bottom: 0;
.p-card-content {
padding-bottom: 0;
}
}
}
</style>
Loading

0 comments on commit 2faae74

Please sign in to comment.