From 5d223a3bb45e8d1ba3635ed2e84967b746c4858c Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Wed, 3 Jul 2024 13:50:55 -0700 Subject: [PATCH 1/2] Bunch changes --- .../data/migrations/016.create-incidents.ts | 3 +- api/src/data/migrations/017.create-hazards.ts | 5 +- api/src/data/models/hazard-model.ts | 2 + api/src/data/models/incident-model.ts | 2 + api/src/index.ts | 2 + api/src/routes/attachment-router.ts | 19 ++ api/src/routes/index.ts | 1 + api/src/routes/report-router.ts | 33 ++- api/src/services/incident-service.ts | 18 +- web/src/components/hazard/HazardList.vue | 64 ++++- web/src/components/incident/CreateButton.vue | 14 +- web/src/components/incident/CreatePage.vue | 28 +-- .../components/incident/CreatePageOffline.vue | 220 ++++++++++++++++++ web/src/components/incident/DetailsPage.vue | 87 +++++-- web/src/layouts/DefaultNoAuth.vue | 63 +++-- .../modules/administration/router/index.ts | 58 ++--- web/src/routes.ts | 76 +++--- web/src/store/ApiStore.ts | 6 + web/src/store/InterfaceStore.ts | 19 ++ web/src/store/ReportStore.ts | 26 ++- web/src/urls.ts | 1 + web/src/views/Home.vue | 9 +- 22 files changed, 572 insertions(+), 184 deletions(-) create mode 100644 api/src/routes/attachment-router.ts create mode 100644 web/src/components/incident/CreatePageOffline.vue create mode 100644 web/src/store/InterfaceStore.ts diff --git a/api/src/data/migrations/016.create-incidents.ts b/api/src/data/migrations/016.create-incidents.ts index 31400df..53d1713 100644 --- a/api/src/data/migrations/016.create-incidents.ts +++ b/api/src/data/migrations/016.create-incidents.ts @@ -14,7 +14,8 @@ export async function up(knex: knex.Knex) { table.string("description", 4000).notNullable(); table.datetime("created_at").notNullable().defaultTo(knex.fn.now()); table.datetime("reported_at").nullable(); - table.string("urgency_code").notNullable(); + table.string("urgency_code",8).notNullable(); + table.string("investigation_notes", 4000).nullable(); table.foreign("sensitivity_code").references("sensitivities.code"); table.foreign("status_code").references("incident_statuses.code"); diff --git a/api/src/data/migrations/017.create-hazards.ts b/api/src/data/migrations/017.create-hazards.ts index a0f5be4..08f9acc 100644 --- a/api/src/data/migrations/017.create-hazards.ts +++ b/api/src/data/migrations/017.create-hazards.ts @@ -12,9 +12,10 @@ export async function up(knex: knex.Knex) { table.string("description", 4000).nullable(); table.string("location_detail", 1000).nullable(); table.datetime("created_at").nullable().defaultTo(knex.fn.now()); - table.datetime("reported_at").nullable() + table.datetime("reported_at").nullable(); table.integer("reopen_count").notNullable().defaultTo(0); - table.string("urgency_code").notNullable(); + table.string("urgency_code", 8).notNullable(); + table.string("notes", 4000).nullable(); table.foreign("hazard_type_id").references("hazard_types.id"); table.foreign("location_code").references("locations.code"); diff --git a/api/src/data/models/hazard-model.ts b/api/src/data/models/hazard-model.ts index bdad2ae..47bc504 100644 --- a/api/src/data/models/hazard-model.ts +++ b/api/src/data/models/hazard-model.ts @@ -11,6 +11,8 @@ export interface Hazard { description?: string; location_detail?: string; created_at: Date | Knex.Raw; + reported_at: Date | Knex.Raw; reopen_count: number; urgency_code: string; + notes?: string; } diff --git a/api/src/data/models/incident-model.ts b/api/src/data/models/incident-model.ts index 0d9a4f3..783c339 100644 --- a/api/src/data/models/incident-model.ts +++ b/api/src/data/models/incident-model.ts @@ -15,7 +15,9 @@ export interface Incident { proxy_user_id?: string; description: string; created_at: Date | Knex.Raw; + reported_at: Date | Knex.Raw; urgency_code: string; + investigation_notes?: string; attachments?: any[]; steps?: IncidentStep[]; diff --git a/api/src/index.ts b/api/src/index.ts index 89457cf..ab4f12a 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -13,6 +13,7 @@ import { userRouter, departmentRouter, actionRouter, + attachmentRouter, } from "./routes"; import { checkJwt, loadUser } from "./middleware/authz.middleware"; import migrator from "./data/migrator"; @@ -64,6 +65,7 @@ app.use("/migrate", migrator.migrationRouter); app.use("/api/directory", directoryRouter); app.use("/api/department", departmentRouter); app.use("/api/location", locationRouter); +app.use("/api/attachment", attachmentRouter); app.use("/api/reports", checkJwt, loadUser, reportRouter); app.use("/api/user", checkJwt, loadUser, userRouter); diff --git a/api/src/routes/attachment-router.ts b/api/src/routes/attachment-router.ts new file mode 100644 index 0000000..97622ec --- /dev/null +++ b/api/src/routes/attachment-router.ts @@ -0,0 +1,19 @@ +import express, { Request, Response } from "express"; +import { param } from "express-validator"; + +import { IncidentAttachment } from "../data/models"; +import { db as knex } from "../data"; + +export const attachmentRouter = express.Router(); + +attachmentRouter.get("/incident/:incident_id/attachment/:id", async (req: Request, res: Response) => { + const { incident_id, id } = req.params; + + let attach = await knex("incident_attachments").where({ incident_id, id }).first(); + + if (!attach) return res.status(404).send(); + + res.setHeader("Content-disposition", `attachment; filename="${attach.file_name}"`); + res.setHeader("Content-type", attach.file_type ?? ""); + res.send(attach.file); +}); diff --git a/api/src/routes/index.ts b/api/src/routes/index.ts index ee1db9b..0216ffe 100644 --- a/api/src/routes/index.ts +++ b/api/src/routes/index.ts @@ -1,4 +1,5 @@ export * from "./action-router"; +export * from "./attachment-router"; export * from "./department-router"; export * from "./directory-router"; export * from "./location-router"; diff --git a/api/src/routes/report-router.ts b/api/src/routes/report-router.ts index 7b89674..5b4b8b6 100644 --- a/api/src/routes/report-router.ts +++ b/api/src/routes/report-router.ts @@ -49,8 +49,20 @@ reportRouter.get("/role/:role", async (req: Request, res: Response) => { reportRouter.get("/:id", async (req: Request, res: Response) => { const { id } = req.params; - const list = await db.getById(id); - return res.json({ data: list }); + const data = await db.getById(id); + + if (!data) return res.status(404).send(); + + return res.json({ data }); +}); + +reportRouter.put("/:id", async (req: Request, res: Response) => { + const { id } = req.params; + const { description, investigation_notes } = req.body; + + await knex("incidents").where({ id }).update({ description, investigation_notes }); + + return res.json({ data: {}, messages: [{ variant: "success", text: "Incident Saved" }] }); }); reportRouter.post("/", async (req: Request, res: Response) => { @@ -58,8 +70,17 @@ reportRouter.post("/", async (req: Request, res: Response) => { req.body.email = req.user.email; req.body.status = "Initial Report"; - let { date, eventType, description, location_code, location_detail, supervisor_email, on_behalf, on_behalf_email } = - req.body; + let { + date, + eventType, + description, + location_code, + location_detail, + supervisor_email, + on_behalf, + on_behalf_email, + urgency, + } = req.body; const reporting_person_email = on_behalf == "Yes" ? on_behalf_email : req.user.email; @@ -93,7 +114,7 @@ reportRouter.post("/", async (req: Request, res: Response) => { reopen_count: 0, created_at: InsertableDate(new Date().toISOString()), reported_at: InsertableDate(date), - urgency_code: Urgencies.MEDIUM.code, + urgency_code: urgency, } as Hazard; const incident = { @@ -107,7 +128,7 @@ reportRouter.post("/", async (req: Request, res: Response) => { incident_type_id: defaultIncidentType.id, reporting_person_email, proxy_user_id: req.user.id, - urgency_code: Urgencies.MEDIUM.code, + urgency_code: urgency, } as Incident; const insertedIncidents = await trx("incidents").insert(incident).returning("*"); diff --git a/api/src/services/incident-service.ts b/api/src/services/incident-service.ts index 40c05f7..36d9583 100644 --- a/api/src/services/incident-service.ts +++ b/api/src/services/incident-service.ts @@ -39,14 +39,18 @@ export class IncidentService { item.steps = await db("incident_steps").where({ incident_id: item.id }).orderBy("order"); item.actions = await db("actions").where({ incident_id: item.id }).orderBy("due_date"); - item.hazards = await db("incident_hazards").where({ incident_id: item.id }); - - for (let hazard of item.hazards) { - hazard.hazard = await db("hazards").where({ id: hazard.hazard_id }).first(); - } + item.hazards = await db("incident_hazards") + .where({ incident_id: item.id }) + .innerJoin("incident_hazard_types", "incident_hazards.incident_hazard_type_code", "incident_hazard_types.code") + .select("incident_hazards.*", "incident_hazard_types.name as incident_hazard_type_name"); - for (let hazard of item.hazards) { - hazard.hazard = await db("hazards").where({ id: hazard.hazard_id }).first(); + for (let hazard of item.hazards ?? []) { + hazard.hazard = await db("hazards") + .where({ "hazards.id": hazard.hazard_id }) + .innerJoin("hazard_types", "hazards.hazard_type_id", "hazard_types.id") + .innerJoin("locations", "hazards.location_code", "locations.code") + .select("hazards.*", "hazard_types.name as hazard_type_name", "locations.name as location_name") + .first(); } for (let action of item.actions) { diff --git a/web/src/components/hazard/HazardList.vue b/web/src/components/hazard/HazardList.vue index 90719df..6dea82b 100644 --- a/web/src/components/hazard/HazardList.vue +++ b/web/src/components/hazard/HazardList.vue @@ -1,21 +1,46 @@ - + + diff --git a/web/src/components/incident/CreateButton.vue b/web/src/components/incident/CreateButton.vue index 6534c74..4b6be2c 100644 --- a/web/src/components/incident/CreateButton.vue +++ b/web/src/components/incident/CreateButton.vue @@ -1,8 +1,18 @@ - + diff --git a/web/src/components/incident/CreatePage.vue b/web/src/components/incident/CreatePage.vue index 753cfd0..a66870e 100644 --- a/web/src/components/incident/CreatePage.vue +++ b/web/src/components/incident/CreatePage.vue @@ -172,10 +172,6 @@
Submit - -
- * You must be authenticated to submit -
@@ -186,10 +182,10 @@ diff --git a/web/src/components/incident/DetailsPage.vue b/web/src/components/incident/DetailsPage.vue index 337ff84..424a144 100644 --- a/web/src/components/incident/DetailsPage.vue +++ b/web/src/components/incident/DetailsPage.vue @@ -84,9 +84,23 @@ + +
+ Supporting images + +
+ + mdi-camera + {{ attach.file_name }} +
+
@@ -114,7 +128,7 @@

Hazards

- + @@ -132,23 +146,11 @@ - -
- Supporting images - -
- {{ - attach.file_name - }} -
-
-
- Investigation - + - Save + Save @@ -157,7 +159,16 @@

Urgency

- {{ selectedReport.urgency_code }} + + + @@ -183,7 +194,7 @@ import ActionEdit from "@/components/action/ActionEdit.vue"; import { useReportStore } from "@/store/ReportStore"; const reportStore = useReportStore(); -const { initialize, loadReport } = reportStore; +const { initialize, loadReport, updateReport, openAttachment } = reportStore; const { locations, urgencies, selectedReport } = storeToRefs(reportStore); const router = useRoute(); @@ -196,6 +207,36 @@ const showActionAdd = ref(false); const showActionEdit = ref(false); const actionToEdit = ref(null); +const tickLabels = { + 0: "Low", + 1: "Medium", + 2: "High", +}; + +const urgencyColor = computed(() => { + if (!selectedReport.value) return 0; + switch (selectedReport.value.urgency_code) { + case "Medium": + return "warning"; + case "High": + return "error"; + default: + return "success"; + } +}); + +const urgencyLevel = computed(() => { + if (!selectedReport.value) return 0; + switch (selectedReport.value.urgency_code) { + case "Medium": + return 1; + case "High": + return 2; + default: + return 0; + } +}); + setTimeout(() => { let list = document.getElementsByClassName("v-stepper-item"); @@ -238,6 +279,14 @@ function doShowActionEdit(action) { actionToEdit.value = action; showActionEdit.value = true; } + +async function saveClick() { + await updateReport(); +} + +function openAttachmentClick(attachment) { + openAttachment(attachment); +}