From 647b4c746ff989d65f2f80cc8e406dfee0507efc Mon Sep 17 00:00:00 2001
From: Dev-CasperTheGhost
<53900565+Dev-CasperTheGhost@users.noreply.github.com>
Date: Thu, 4 Nov 2021 11:49:36 +0100
Subject: [PATCH 1/5] :tada: initial impl. for Quick Call Events
---
.../components/modals/Manage911CallModal.tsx | 24 +++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/packages/client/src/components/modals/Manage911CallModal.tsx b/packages/client/src/components/modals/Manage911CallModal.tsx
index 5202f382e..c05c6b0c9 100644
--- a/packages/client/src/components/modals/Manage911CallModal.tsx
+++ b/packages/client/src/components/modals/Manage911CallModal.tsx
@@ -20,6 +20,7 @@ import { SocketEvents } from "@snailycad/config";
import { CallEventsArea } from "./911Call/EventsArea";
import { useGenerateCallsign } from "hooks/useGenerateCallsign";
import { makeUnitName } from "lib/utils";
+import { ContextMenu } from "components/context-menu/ContextMenu";
interface Props {
call: Full911Call | null;
@@ -273,6 +274,29 @@ export const Manage911CallModal = ({ setCall, call, onClose }: Props) => {
{call ? : null}
+
+
Assigned Units
+
+ {call?.assignedUnits.map((unit) => (
+
+
+
+ {generateCallsign(unit.unit)}
+ {makeUnitName(unit.unit)}
+
+
+
+ ))}
+
+
{call ? (
Date: Fri, 5 Nov 2021 07:41:46 +0100
Subject: [PATCH 2/5] :tada: start on quick-events
---
.../migrations/20211105062536_/migration.sql | 5 +++++
packages/api/prisma/schema.prisma | 12 +++++++---
packages/api/src/controllers/admin/Values.ts | 2 ++
.../admin/values/ManageValueModal.tsx | 22 +++++++++++++++++++
.../src/components/ems-fd/StatusesArea.tsx | 2 +-
.../client/src/components/form/FormField.tsx | 14 +++++++++---
.../src/components/leo/StatusesArea.tsx | 2 +-
packages/client/src/types/prisma.ts | 8 +++++++
8 files changed, 59 insertions(+), 8 deletions(-)
create mode 100644 packages/api/prisma/migrations/20211105062536_/migration.sql
diff --git a/packages/api/prisma/migrations/20211105062536_/migration.sql b/packages/api/prisma/migrations/20211105062536_/migration.sql
new file mode 100644
index 000000000..194b0880a
--- /dev/null
+++ b/packages/api/prisma/migrations/20211105062536_/migration.sql
@@ -0,0 +1,5 @@
+-- CreateEnum
+CREATE TYPE "StatusValueType" AS ENUM ('STATUS_CODE', 'SITUATION_CODE');
+
+-- AlterTable
+ALTER TABLE "StatusValue" ADD COLUMN "type" "StatusValueType" NOT NULL DEFAULT E'STATUS_CODE';
diff --git a/packages/api/prisma/schema.prisma b/packages/api/prisma/schema.prisma
index be750b109..17a81112d 100644
--- a/packages/api/prisma/schema.prisma
+++ b/packages/api/prisma/schema.prisma
@@ -494,13 +494,14 @@ model Officer {
}
model StatusValue {
- id String @id @default(uuid())
- value Value @relation("StatusValueToValue", fields: [valueId], references: [id], onDelete: Cascade)
+ id String @id @default(uuid())
+ value Value @relation("StatusValueToValue", fields: [valueId], references: [id], onDelete: Cascade)
valueId String
- shouldDo ShouldDoType @default(SET_STATUS)
+ shouldDo ShouldDoType @default(SET_STATUS)
position Int?
whatPages WhatPages[]
color String?
+ type StatusValueType @default(STATUS_CODE)
officerStatusToValue Officer[] @relation("officerStatusToValue")
emsFdStatusToValue EmsFdDeputy[] @relation("emsFdStatusToValue")
@@ -542,6 +543,11 @@ model LeoIncident {
updatedAt DateTime @default(now()) @updatedAt
}
+enum StatusValueType {
+ STATUS_CODE
+ SITUATION_CODE
+}
+
enum StatusEnum {
ON_DUTY
OFF_DUTY
diff --git a/packages/api/src/controllers/admin/Values.ts b/packages/api/src/controllers/admin/Values.ts
index 5b10e569e..2b6d9d8c3 100644
--- a/packages/api/src/controllers/admin/Values.ts
+++ b/packages/api/src/controllers/admin/Values.ts
@@ -144,6 +144,7 @@ export class ValuesController {
valueId: value.id,
position: Number(body.get("position")),
color: body.get("color") || null,
+ type: body.get("type") || "STATUS_CODE",
},
include: {
value: true,
@@ -302,6 +303,7 @@ export class ValuesController {
shouldDo: body.get("shouldDo"),
position: Number(body.get("position")),
color: body.get("color") || null,
+ type: body.get("type") || "STATUS_CODE",
},
include: {
value: true,
diff --git a/packages/client/src/components/admin/values/ManageValueModal.tsx b/packages/client/src/components/admin/values/ManageValueModal.tsx
index b1d8e5b39..9a70590e5 100644
--- a/packages/client/src/components/admin/values/ManageValueModal.tsx
+++ b/packages/client/src/components/admin/values/ManageValueModal.tsx
@@ -262,6 +262,28 @@ export const ManageValueModal = ({ onCreate, onUpdate, clType: dlType, type, val
{errors.color}
+
+
+ setFieldValue("type", "STATUS_CODE")}
+ checked={values.type === "STATUS_CODE"}
+ />
+
+
+
+ setFieldValue("type", "SITUATION_CODE")}
+ checked={values.type === "SITUATION_CODE"}
+ />
+
>
) : null}
diff --git a/packages/client/src/components/ems-fd/StatusesArea.tsx b/packages/client/src/components/ems-fd/StatusesArea.tsx
index c7e38efcc..63d08902b 100644
--- a/packages/client/src/components/ems-fd/StatusesArea.tsx
+++ b/packages/client/src/components/ems-fd/StatusesArea.tsx
@@ -74,7 +74,7 @@ export const StatusesArea = () => {
{codes10.values
- .filter((v) => v.shouldDo !== ShouldDoType.SET_ON_DUTY)
+ .filter((v) => v.shouldDo !== ShouldDoType.SET_ON_DUTY && v.type === "STATUS_CODE")
.sort((a, b) => Number(a.position) - Number(b.position))
.map((code) => {
const isActive = code.id === activeDeputy?.statusId;
diff --git a/packages/client/src/components/form/FormField.tsx b/packages/client/src/components/form/FormField.tsx
index 86499b389..9bb178b11 100644
--- a/packages/client/src/components/form/FormField.tsx
+++ b/packages/client/src/components/form/FormField.tsx
@@ -17,11 +17,19 @@ export const FormField = ({ checkbox, children, label, className, fieldId }: Pro
className,
)}
>
-
+ {!checkbox ? (
+
+ ) : null}
{children}
+
+ {checkbox ? (
+
+ ) : null}
);
};
diff --git a/packages/client/src/components/leo/StatusesArea.tsx b/packages/client/src/components/leo/StatusesArea.tsx
index d72c060b1..65fa32e1d 100644
--- a/packages/client/src/components/leo/StatusesArea.tsx
+++ b/packages/client/src/components/leo/StatusesArea.tsx
@@ -74,7 +74,7 @@ export const StatusesArea = () => {
{codes10.values
- .filter((v) => v.shouldDo !== ShouldDoType.SET_ON_DUTY)
+ .filter((v) => v.shouldDo !== ShouldDoType.SET_ON_DUTY && v.type === "STATUS_CODE")
.sort((a, b) => Number(a.position) - Number(b.position))
.map((code) => {
const isActive = code.id === activeOfficer?.statusId;
diff --git a/packages/client/src/types/prisma.ts b/packages/client/src/types/prisma.ts
index 9a19ae07d..74a223df1 100644
--- a/packages/client/src/types/prisma.ts
+++ b/packages/client/src/types/prisma.ts
@@ -310,6 +310,7 @@ export type StatusValue = {
whatPages: WhatPages[];
departmentId: string;
color?: string;
+ type: StatusValueType;
};
/**
@@ -647,3 +648,10 @@ export const DepartmentType = {
} as const;
export type DepartmentType = typeof DepartmentType[keyof typeof DepartmentType];
+
+export const StatusValueType = {
+ STATUS_CODE: "STATUS_CODE",
+ SITUATION_CODE: "SITUATION_CODE",
+} as const;
+
+export type StatusValueType = typeof StatusValueType[keyof typeof StatusValueType];
From 84eb0c185c2b19eb83f1134e6634ac2aced0edfb Mon Sep 17 00:00:00 2001
From: Dev-CasperTheGhost
<53900565+Dev-CasperTheGhost@users.noreply.github.com>
Date: Fri, 5 Nov 2021 08:21:34 +0100
Subject: [PATCH 3/5] :tada: progress on quick-events
---
.../dispatch/Calls911Controller.ts | 72 +++---
.../controllers/dispatch/StatusController.ts | 225 ++++++++++++++++++
.../src/controllers/ems-fd/EmsFdController.ts | 159 +------------
.../api/src/controllers/leo/LeoController.ts | 174 +-------------
.../components/context-menu/ContextMenu.tsx | 2 +-
.../components/dispatch/modals/ManageUnit.tsx | 41 ++--
.../src/components/ems-fd/StatusesArea.tsx | 2 +-
.../components/ems-fd/modals/SelectDeputy.tsx | 2 +-
.../client/src/components/form/Select.tsx | 55 ++++-
.../src/components/leo/StatusesArea.tsx | 2 +-
.../leo/modals/SelectOfficerModal.tsx | 2 +-
.../components/modals/Manage911CallModal.tsx | 24 --
packages/config/src/routes.ts | 5 +-
13 files changed, 344 insertions(+), 421 deletions(-)
create mode 100644 packages/api/src/controllers/dispatch/StatusController.ts
diff --git a/packages/api/src/controllers/dispatch/Calls911Controller.ts b/packages/api/src/controllers/dispatch/Calls911Controller.ts
index 5a4c4c0d1..e823b9330 100644
--- a/packages/api/src/controllers/dispatch/Calls911Controller.ts
+++ b/packages/api/src/controllers/dispatch/Calls911Controller.ts
@@ -277,7 +277,7 @@ export class Calls911Controller {
throw new BadRequest("unitIsRequired");
}
- const { unit, type } = await this.findUnit(rawUnit, undefined, true);
+ const { unit, type } = await findUnit(rawUnit, undefined, true);
if (!unit) {
throw new NotFound("unitNotFound");
@@ -324,38 +324,6 @@ export class Calls911Controller {
return this.officerOrDeputyToUnit(updated);
}
- private async findUnit(
- id: string,
- extraFind?: any,
- withType?: false,
- ): Promise;
- private async findUnit(
- id: string,
- extraFind?: any,
- withType?: true,
- ): Promise<{ unit: Officer | EmsFdDeputy; type: "leo" | "ems-fd" }>;
- private async findUnit(id: string, extraFind?: any, withType?: boolean) {
- let type: "leo" | "ems-fd" = "leo";
- let unit = await prisma.officer.findFirst({
- where: { id, ...extraFind },
- });
-
- if (!unit) {
- type = "ems-fd";
- unit = await prisma.emsFdDeputy.findFirst({ where: { id, ...extraFind } });
- }
-
- if (!unit) {
- return null;
- }
-
- if (withType) {
- return { type, unit };
- }
-
- return unit;
- }
-
private officerOrDeputyToUnit(call: any & { assignedUnits: any[] }) {
return {
...call,
@@ -371,7 +339,7 @@ export class Calls911Controller {
private async assignUnitsToCall(callId: string, units: string[]) {
await Promise.all(
units.map(async (id) => {
- const { unit, type } = await this.findUnit(
+ const { unit, type } = await findUnit(
id,
{
NOT: { status: { shouldDo: ShouldDoType.SET_OFF_DUTY } },
@@ -404,3 +372,39 @@ export class Calls911Controller {
);
}
}
+
+export async function findUnit(
+ id: string,
+ extraFind?: any,
+ withType?: false,
+): Promise;
+export async function findUnit(
+ id: string,
+ extraFind?: any,
+ withType?: true,
+): Promise<{ unit: Officer | EmsFdDeputy | null; type: "leo" | "ems-fd" }>;
+export async function findUnit(id: string, extraFind?: any, withType?: boolean) {
+ let type: "leo" | "ems-fd" = "leo";
+ let unit = await prisma.officer.findFirst({
+ where: { id, ...extraFind },
+ });
+
+ if (!unit) {
+ type = "ems-fd";
+ unit = await prisma.emsFdDeputy.findFirst({ where: { id, ...extraFind } });
+ }
+
+ if (!unit) {
+ if (withType) {
+ return { type, unit: null };
+ }
+
+ return null;
+ }
+
+ if (withType) {
+ return { type, unit };
+ }
+
+ return unit;
+}
diff --git a/packages/api/src/controllers/dispatch/StatusController.ts b/packages/api/src/controllers/dispatch/StatusController.ts
new file mode 100644
index 000000000..2403246d5
--- /dev/null
+++ b/packages/api/src/controllers/dispatch/StatusController.ts
@@ -0,0 +1,225 @@
+import { User, ShouldDoType, MiscCadSettings, cad } from ".prisma/client";
+import { UPDATE_OFFICER_STATUS_SCHEMA, validate } from "@snailycad/schemas";
+import { Req, Res, UseBeforeEach } from "@tsed/common";
+import { Controller } from "@tsed/di";
+import { BadRequest, NotFound } from "@tsed/exceptions";
+import { BodyParams, Context, PathParams } from "@tsed/platform-params";
+import { JsonRequestBody, Put } from "@tsed/schema";
+import { prisma } from "../../lib/prisma";
+import { findUnit } from "./Calls911Controller";
+import { unitProperties } from "../../lib/officer";
+import { setCookie } from "../../utils/setCookie";
+import { Cookie } from "@snailycad/config";
+import { signJWT } from "../../utils/jwt";
+import { getWebhookData, sendDiscordWebhook } from "../../lib/discord";
+import { Socket } from "../../services/SocketService";
+import { APIWebhook } from "discord-api-types";
+import { IsAuth } from "../../middlewares";
+
+@Controller("/dispatch/status")
+@UseBeforeEach(IsAuth)
+export class StatusController {
+ private socket: Socket;
+ constructor(socket: Socket) {
+ this.socket = socket;
+ }
+
+ @Put("/:unitId")
+ async updateUnitStatus(
+ @PathParams("unitId") unitId: string,
+ @Context("user") user: User,
+ @BodyParams() body: JsonRequestBody,
+ @Res() res: Res,
+ @Req() req: Req,
+ @Context("cad") cad: cad & { miscCadSettings: MiscCadSettings },
+ ) {
+ const error = validate(UPDATE_OFFICER_STATUS_SCHEMA, body.toJSON(), true);
+ if (error) {
+ throw new BadRequest(error);
+ }
+
+ const statusId = body.get("status");
+
+ const isFromDispatch = req.headers["is-from-dispatch"]?.toString() === "true";
+ const isDispatch = isFromDispatch && user.isDispatch;
+
+ const { type, unit } = await findUnit(
+ unitId,
+ { userId: isDispatch ? undefined : user.id },
+ true,
+ );
+
+ if (!unit) {
+ throw new NotFound("unitNotFound");
+ }
+
+ if (unit.suspended) {
+ throw new BadRequest("unitSuspended");
+ }
+
+ const code = await prisma.statusValue.findFirst({
+ where: {
+ id: statusId,
+ },
+ include: {
+ value: true,
+ },
+ });
+
+ if (!code) {
+ throw new NotFound("statusNotFound");
+ }
+
+ // reset all units for user
+ if (type === "leo") {
+ await prisma.officer.updateMany({
+ where: {
+ userId: user.id,
+ },
+ data: {
+ statusId: null,
+ },
+ });
+ } else {
+ await prisma.emsFdDeputy.updateMany({
+ where: {
+ userId: user.id,
+ },
+ data: {
+ statusId: null,
+ },
+ });
+ }
+
+ let updatedUnit;
+ if (type === "leo") {
+ updatedUnit = await prisma.officer.update({
+ where: {
+ id: unit.id,
+ },
+ data: {
+ statusId: code.shouldDo === ShouldDoType.SET_OFF_DUTY ? null : code.id,
+ },
+ include: unitProperties,
+ });
+ } else {
+ updatedUnit = await prisma.emsFdDeputy.update({
+ where: {
+ id: unit.id,
+ },
+ data: {
+ statusId: code.shouldDo === ShouldDoType.SET_OFF_DUTY ? null : code.id,
+ },
+ include: unitProperties,
+ });
+ }
+
+ if (type === "leo") {
+ const officerLog = await prisma.officerLog.findFirst({
+ where: {
+ officerId: unit.id,
+ endedAt: null,
+ },
+ });
+
+ if (code.shouldDo === ShouldDoType.SET_ON_DUTY) {
+ if (!officerLog) {
+ await prisma.officerLog.create({
+ data: {
+ officerId: unit.id,
+ userId: user.id,
+ startedAt: new Date(),
+ },
+ });
+ }
+ } else {
+ if (code.shouldDo === ShouldDoType.SET_OFF_DUTY) {
+ // unassign officer from call
+ await prisma.assignedUnit.deleteMany({
+ where: {
+ officerId: unit.id,
+ },
+ });
+
+ if (officerLog) {
+ await prisma.officerLog.update({
+ where: {
+ id: officerLog.id,
+ },
+ data: {
+ endedAt: new Date(),
+ },
+ });
+ }
+ }
+ }
+ } else {
+ // unassign deputy from call
+ await prisma.assignedUnit.deleteMany({
+ where: {
+ emsFdDeputyId: unit.id,
+ },
+ });
+ }
+
+ if (code.shouldDo === ShouldDoType.SET_OFF_DUTY) {
+ setCookie({
+ res,
+ name: Cookie.ActiveOfficer,
+ value: "",
+ expires: -1,
+ });
+ } else {
+ const cookieName = type === "leo" ? Cookie.ActiveOfficer : Cookie.ActiveOfficer;
+ const cookiePayloadName = type === "leo" ? "officerId" : "deputyId";
+
+ // expires after 3 hours.
+ setCookie({
+ res,
+ name: cookieName,
+ value: signJWT({ [cookiePayloadName]: updatedUnit.id }, 60 * 60 * 3),
+ expires: 60 * 60 * 1000 * 3,
+ });
+ }
+
+ if (cad.discordWebhookURL) {
+ const webhook = await getWebhookData(cad.discordWebhookURL);
+ if (!webhook) return;
+ const data = createWebhookData(webhook, updatedUnit);
+
+ await sendDiscordWebhook(webhook, data);
+ }
+
+ if (type === "leo") {
+ this.socket.emitUpdateOfficerStatus();
+ } else {
+ this.socket.emitUpdateDeputyStatus();
+ }
+
+ return updatedUnit;
+ }
+}
+
+function createWebhookData(webhook: APIWebhook, unit: any) {
+ const status = unit.status.value.value;
+ const department = unit.department.value.value;
+ const officerName = `${unit.badgeNumber} - ${unit.name} ${unit.callsign} (${department})`;
+
+ return {
+ avatar_url: webhook.avatar,
+ embeds: [
+ {
+ title: "Status Change",
+ type: "rich",
+ description: `Unit **${officerName}** has changed their status to ${status}`,
+ fields: [
+ {
+ name: "Status",
+ value: status,
+ inline: true,
+ },
+ ],
+ },
+ ],
+ };
+}
diff --git a/packages/api/src/controllers/ems-fd/EmsFdController.ts b/packages/api/src/controllers/ems-fd/EmsFdController.ts
index 83a86add6..64230732e 100644
--- a/packages/api/src/controllers/ems-fd/EmsFdController.ts
+++ b/packages/api/src/controllers/ems-fd/EmsFdController.ts
@@ -1,30 +1,12 @@
-import {
- Res,
- Controller,
- UseBeforeEach,
- Use,
- Req,
- MultipartFile,
- PlatformMulterFile,
-} from "@tsed/common";
+import { Controller, UseBeforeEach, Use, MultipartFile, PlatformMulterFile } from "@tsed/common";
import { Delete, Get, JsonRequestBody, Post, Put } from "@tsed/schema";
-import {
- CREATE_OFFICER_SCHEMA,
- MEDICAL_RECORD_SCHEMA,
- UPDATE_OFFICER_STATUS_SCHEMA,
- validate,
-} from "@snailycad/schemas";
+import { CREATE_OFFICER_SCHEMA, MEDICAL_RECORD_SCHEMA, validate } from "@snailycad/schemas";
import { BodyParams, Context, PathParams } from "@tsed/platform-params";
import { BadRequest, NotFound } from "@tsed/exceptions";
import { prisma } from "../../lib/prisma";
-import { cad, ShouldDoType, MiscCadSettings, User } from ".prisma/client";
-import { setCookie } from "../../utils/setCookie";
-import { AllowedFileExtension, allowedFileExtensions, Cookie } from "@snailycad/config";
+import { ShouldDoType, User } from ".prisma/client";
+import { AllowedFileExtension, allowedFileExtensions } from "@snailycad/config";
import { IsAuth } from "../../middlewares";
-import { signJWT } from "../../utils/jwt";
-import { Socket } from "../../services/SocketService";
-import { getWebhookData, sendDiscordWebhook } from "../../lib/discord";
-import { APIWebhook } from "discord-api-types/payloads/v9/webhook";
import { ActiveDeputy } from "../../middlewares/ActiveDeputy";
import fs from "node:fs";
import { unitProperties } from "../../lib/officer";
@@ -32,11 +14,6 @@ import { unitProperties } from "../../lib/officer";
@Controller("/ems-fd")
@UseBeforeEach(IsAuth)
export class EmsFdController {
- private socket: Socket;
- constructor(socket: Socket) {
- this.socket = socket;
- }
-
@Get("/")
async getUserDeputies(@Context("user") user: User) {
const deputies = await prisma.emsFdDeputy.findMany({
@@ -164,110 +141,6 @@ export class EmsFdController {
return updated;
}
- @Put("/:id/status")
- async setDeputyStatus(
- @PathParams("id") deputyId: string,
- @BodyParams() body: JsonRequestBody,
- @Context("user") user: User,
- @Context("cad") cad: cad & { miscCadSettings: MiscCadSettings },
- @Res() res: Res,
- @Req() req: Req,
- ) {
- const error = validate(UPDATE_OFFICER_STATUS_SCHEMA, body.toJSON(), true);
-
- if (error) {
- throw new BadRequest(error);
- }
-
- const statusId = body.get("status");
- const isFromDispatch = req.headers["is-from-dispatch"]?.toString() === "true";
- const isDispatch = isFromDispatch && user.isDispatch;
-
- const deputy = await prisma.emsFdDeputy.findFirst({
- where: {
- userId: isDispatch ? undefined : user.id,
- id: deputyId,
- },
- });
-
- if (!deputy) {
- throw new NotFound("deputyNotFound");
- }
-
- if (deputy.suspended) {
- throw new BadRequest("deputySuspended");
- }
-
- const code = await prisma.statusValue.findFirst({
- where: {
- id: statusId,
- },
- include: {
- value: true,
- },
- });
-
- if (!code) {
- throw new NotFound("statusNotFound");
- }
-
- // reset all user
- await prisma.emsFdDeputy.updateMany({
- where: {
- userId: user.id,
- },
- data: {
- statusId: null,
- },
- });
-
- const updatedDeputy = await prisma.emsFdDeputy.update({
- where: {
- id: deputy.id,
- },
- data: {
- statusId: code.shouldDo === ShouldDoType.SET_OFF_DUTY ? null : code.id,
- },
- include: unitProperties,
- });
-
- if (code.shouldDo === ShouldDoType.SET_OFF_DUTY) {
- setCookie({
- res,
- name: Cookie.ActiveDeputy,
- value: "",
- expires: -1,
- });
-
- // unassign deputy from call
- await prisma.assignedUnit.deleteMany({
- where: {
- emsFdDeputyId: deputy.id,
- },
- });
- } else {
- // expires after 3 hours.
- setCookie({
- res,
- name: Cookie.ActiveDeputy,
- value: signJWT({ deputyId: updatedDeputy.id }, 60 * 60 * 3),
- expires: 60 * 60 * 1000 * 3,
- });
- }
-
- if (cad.discordWebhookURL) {
- const webhook = await getWebhookData(cad.discordWebhookURL);
- if (!webhook) return;
- const data = createWebhookData(webhook, updatedDeputy);
-
- await sendDiscordWebhook(webhook, data);
- }
-
- this.socket.emitUpdateDeputyStatus();
-
- return updatedDeputy;
- }
-
@Delete("/:id")
async deleteDeputy(@PathParams("id") id: string, @Context() ctx: Context) {
const deputy = await prisma.emsFdDeputy.findFirst({
@@ -410,27 +283,3 @@ export class EmsFdController {
return data;
}
}
-
-export function createWebhookData(webhook: APIWebhook, officer: any) {
- const status = officer.status.value.value;
- const department = officer.department.value.value;
- const officerName = `${officer.badgeNumber} - ${officer.name} ${officer.callsign} (${department})`;
-
- return {
- avatar_url: webhook.avatar,
- embeds: [
- {
- title: "Status Change",
- type: "rich",
- description: `Officer **${officerName}** has changed their status to ${status}`,
- fields: [
- {
- name: "Status",
- value: status,
- inline: true,
- },
- ],
- },
- ],
- };
-}
diff --git a/packages/api/src/controllers/leo/LeoController.ts b/packages/api/src/controllers/leo/LeoController.ts
index cd88c5c7a..8298dc8f5 100644
--- a/packages/api/src/controllers/leo/LeoController.ts
+++ b/packages/api/src/controllers/leo/LeoController.ts
@@ -1,30 +1,23 @@
import {
- Res,
Controller,
UseBeforeEach,
- Req,
PlatformMulterFile,
MultipartFile,
UseBefore,
} from "@tsed/common";
import { Delete, Get, JsonRequestBody, Post, Put } from "@tsed/schema";
-import { CREATE_OFFICER_SCHEMA, UPDATE_OFFICER_STATUS_SCHEMA, validate } from "@snailycad/schemas";
+import { CREATE_OFFICER_SCHEMA, validate } from "@snailycad/schemas";
import { BodyParams, Context, PathParams } from "@tsed/platform-params";
import { BadRequest, NotFound } from "@tsed/exceptions";
import { prisma } from "../../lib/prisma";
-import { Officer, cad, ShouldDoType, MiscCadSettings, User } from ".prisma/client";
-import { setCookie } from "../../utils/setCookie";
-import { AllowedFileExtension, allowedFileExtensions, Cookie } from "@snailycad/config";
+import { Officer, ShouldDoType, User } from ".prisma/client";
+import { AllowedFileExtension, allowedFileExtensions } from "@snailycad/config";
import { IsAuth } from "../../middlewares";
-import { signJWT } from "../../utils/jwt";
import { ActiveOfficer } from "../../middlewares/ActiveOfficer";
import { Socket } from "../../services/SocketService";
-import { getWebhookData, sendDiscordWebhook } from "../../lib/discord";
-import { APIWebhook } from "discord-api-types/payloads/v9/webhook";
import fs from "node:fs";
import { unitProperties } from "../../lib/officer";
-// todo: check for leo permissions
@Controller("/leo")
@UseBeforeEach(IsAuth)
export class LeoController {
@@ -161,143 +154,6 @@ export class LeoController {
return updated;
}
- @Put("/:id/status")
- async setOfficerStatus(
- @PathParams("id") officerId: string,
- @BodyParams() body: JsonRequestBody,
- @Context("user") user: User,
- @Context("cad") cad: cad & { miscCadSettings: MiscCadSettings },
- @Res() res: Res,
- @Req() req: Req,
- ) {
- const error = validate(UPDATE_OFFICER_STATUS_SCHEMA, body.toJSON(), true);
-
- if (error) {
- throw new BadRequest(error);
- }
-
- const statusId = body.get("status");
-
- const isFromDispatch = req.headers["is-from-dispatch"]?.toString() === "true";
- const isDispatch = isFromDispatch && user.isDispatch;
-
- const officer = await prisma.officer.findFirst({
- where: {
- userId: isDispatch ? undefined : user.id,
- id: officerId,
- },
- });
-
- if (!officer) {
- throw new NotFound("officerNotFound");
- }
-
- if (officer.suspended) {
- throw new BadRequest("officerSuspended");
- }
-
- const code = await prisma.statusValue.findFirst({
- where: {
- id: statusId,
- },
- include: {
- value: true,
- },
- });
-
- if (!code) {
- throw new NotFound("statusNotFound");
- }
-
- // reset all officers for user
- await prisma.officer.updateMany({
- where: {
- userId: user.id,
- },
- data: {
- statusId: null,
- },
- });
-
- const updatedOfficer = await prisma.officer.update({
- where: {
- id: officer.id,
- },
- data: {
- statusId: code.shouldDo === ShouldDoType.SET_OFF_DUTY ? null : code.id,
- },
- include: unitProperties,
- });
-
- const officerLog = await prisma.officerLog.findFirst({
- where: {
- officerId: officer.id,
- endedAt: null,
- },
- });
-
- if (code.shouldDo === ShouldDoType.SET_ON_DUTY) {
- if (!officerLog) {
- await prisma.officerLog.create({
- data: {
- officerId: officer.id,
- userId: user.id,
- startedAt: new Date(),
- },
- });
- }
- } else {
- if (code.shouldDo === ShouldDoType.SET_OFF_DUTY) {
- // unassign officer from call
- await prisma.assignedUnit.deleteMany({
- where: {
- officerId: officer.id,
- },
- });
-
- if (officerLog) {
- await prisma.officerLog.update({
- where: {
- id: officerLog.id,
- },
- data: {
- endedAt: new Date(),
- },
- });
- }
- }
- }
-
- if (code.shouldDo === ShouldDoType.SET_OFF_DUTY) {
- setCookie({
- res,
- name: Cookie.ActiveOfficer,
- value: "",
- expires: -1,
- });
- } else {
- // expires after 3 hours.
- setCookie({
- res,
- name: Cookie.ActiveOfficer,
- value: signJWT({ officerId: updatedOfficer.id }, 60 * 60 * 3),
- expires: 60 * 60 * 1000 * 3,
- });
- }
-
- if (cad.discordWebhookURL) {
- const webhook = await getWebhookData(cad.discordWebhookURL);
- if (!webhook) return;
- const data = createWebhookData(webhook, updatedOfficer);
-
- await sendDiscordWebhook(webhook, data);
- }
-
- this.socket.emitUpdateOfficerStatus();
-
- return updatedOfficer;
- }
-
@Delete("/:id")
async deleteOfficer(@PathParams("id") officerId: string, @Context() ctx: Context) {
const officer = await prisma.officer.findFirst({
@@ -475,27 +331,3 @@ export class LeoController {
return true;
}
}
-
-export function createWebhookData(webhook: APIWebhook, officer: any) {
- const status = officer.status.value.value;
- const department = officer.department.value.value;
- const officerName = `${officer.badgeNumber} - ${officer.name} ${officer.callsign} (${department})`;
-
- return {
- avatar_url: webhook.avatar,
- embeds: [
- {
- title: "Status Change",
- type: "rich",
- description: `Officer **${officerName}** has changed their status to ${status}`,
- fields: [
- {
- name: "Status",
- value: status,
- inline: true,
- },
- ],
- },
- ],
- };
-}
diff --git a/packages/client/src/components/context-menu/ContextMenu.tsx b/packages/client/src/components/context-menu/ContextMenu.tsx
index e361a6cf9..29b590071 100644
--- a/packages/client/src/components/context-menu/ContextMenu.tsx
+++ b/packages/client/src/components/context-menu/ContextMenu.tsx
@@ -46,7 +46,7 @@ export const ContextMenu = ({ items, children }: Props) => {
"flex flex-col",
"shadow-md",
"bg-white dark:bg-dark-bright shadow-sm",
- "p-1.5 rounded-md w-36",
+ "p-1.5 rounded-md min-w-[12rem] max-h-[25rem] overflow-auto",
)}
>
{items.map((item) => {
diff --git a/packages/client/src/components/dispatch/modals/ManageUnit.tsx b/packages/client/src/components/dispatch/modals/ManageUnit.tsx
index b356db861..ecf2ef04a 100644
--- a/packages/client/src/components/dispatch/modals/ManageUnit.tsx
+++ b/packages/client/src/components/dispatch/modals/ManageUnit.tsx
@@ -37,33 +37,24 @@ export const ManageUnitModal = ({ type = "leo", unit, onClose }: Props) => {
async function onSubmit(values: typeof INITIAL_VALUES) {
if (!unit) return;
- if (type === "leo") {
- const { json } = await execute(`/leo/${unit.id}/status`, {
- method: "PUT",
- data: { ...values },
- });
+ const { json } = await execute(`/dispatch/status/${unit.id}`, {
+ method: "PUT",
+ data: { ...values },
+ });
- if (json.id) {
- setActiveOfficers(
- activeOfficers.map((officer) => {
- if (officer.id === json.id) {
- return { ...officer, ...json };
- }
+ if (type === "leo" && json.id) {
+ setActiveOfficers(
+ activeOfficers.map((officer) => {
+ if (officer.id === json.id) {
+ return { ...officer, ...json };
+ }
- return officer;
- }),
- );
- handleClose();
- }
- } else {
- const { json } = await execute(`/ems-fd/${unit.id}/status`, {
- method: "PUT",
- data: { ...values },
- });
-
- if (json.id) {
- handleClose();
- }
+ return officer;
+ }),
+ );
+ handleClose();
+ } else if (json.id) {
+ handleClose();
}
}
diff --git a/packages/client/src/components/ems-fd/StatusesArea.tsx b/packages/client/src/components/ems-fd/StatusesArea.tsx
index 63d08902b..77c66e651 100644
--- a/packages/client/src/components/ems-fd/StatusesArea.tsx
+++ b/packages/client/src/components/ems-fd/StatusesArea.tsx
@@ -46,7 +46,7 @@ export const StatusesArea = () => {
if (!activeDeputy) return;
if (status.id === activeDeputy?.statusId) return;
- const { json } = await execute(`/ems-fd/${activeDeputy.id}/status`, {
+ const { json } = await execute(`/dispatch/status/${activeDeputy.id}`, {
method: "PUT",
data: {
status: status.id,
diff --git a/packages/client/src/components/ems-fd/modals/SelectDeputy.tsx b/packages/client/src/components/ems-fd/modals/SelectDeputy.tsx
index a6ca854c1..52f1f628e 100644
--- a/packages/client/src/components/ems-fd/modals/SelectDeputy.tsx
+++ b/packages/client/src/components/ems-fd/modals/SelectDeputy.tsx
@@ -32,7 +32,7 @@ export const SelectDeputyModal = () => {
async function onSubmit(values: typeof INITIAL_VALUES) {
if (!onDutyCode) return;
- const { json } = await execute(`/ems-fd/${values.deputy}/status`, {
+ const { json } = await execute(`/dispatch/status/${values.deputy}`, {
method: "PUT",
data: {
...values,
diff --git a/packages/client/src/components/form/Select.tsx b/packages/client/src/components/form/Select.tsx
index 8347fadb1..41fabcd45 100644
--- a/packages/client/src/components/form/Select.tsx
+++ b/packages/client/src/components/form/Select.tsx
@@ -1,7 +1,17 @@
import * as React from "react";
import { useTranslations } from "use-intl";
-import ReactSelect, { Props as SelectProps, GroupBase, StylesConfig } from "react-select";
+import ReactSelect, {
+ Props as SelectProps,
+ GroupBase,
+ StylesConfig,
+ components,
+ MultiValueGenericProps,
+} from "react-select";
import { useAuth } from "context/AuthContext";
+import { ContextMenu } from "components/context-menu/ContextMenu";
+import { useValues } from "context/ValuesContext";
+import useFetch from "lib/useFetch";
+import { StatusValue } from "types/prisma";
export interface SelectValue {
label: string;
@@ -17,6 +27,41 @@ interface Props extends Exclude {
disabled?: boolean;
}
+const MultiValueContainer = (props: MultiValueGenericProps) => {
+ const { codes10 } = useValues();
+ const { execute } = useFetch();
+
+ const unitId = props.data.value;
+
+ async function setCode(status: StatusValue) {
+ const { json } = await execute(`/dispatch/status/${unitId}`, {
+ method: "PUT",
+ data: { status: status.id },
+ });
+
+ console.log({ json });
+ }
+
+ return (
+ ({
+ name: v.value.value,
+ onClick: () => setCode(v),
+ "aria-label":
+ v.type === "STATUS_CODE"
+ ? `Set status to ${v.value.value}`
+ : `Add code to event: ${v.value.value} `,
+ title:
+ v.type === "STATUS_CODE"
+ ? `Set status to ${v.value.value}`
+ : `Add code to event: ${v.value.value} `,
+ }))}
+ >
+
+
+ );
+};
+
export const Select = ({ name, onChange, ...rest }: Props) => {
const { user } = useAuth();
const common = useTranslations("Common");
@@ -45,6 +90,7 @@ export const Select = ({ name, onChange, ...rest }: Props) => {
styles={styles(theme)}
className="border-gray-500"
menuPortalTarget={(typeof document !== "undefined" && document.body) || undefined}
+ components={{ MultiValueContainer }}
/>
);
};
@@ -91,7 +137,8 @@ export const styles = ({
multiValue: (base) => ({
...base,
color: "#000",
- borderColor: "rgba(229, 231, 235, 0.5)",
+ borderColor: backgroundColor === "white" ? "#cccccc" : "#2f2f2f",
+ backgroundColor: backgroundColor === "white" ? "#cccccc" : "#2f2f2f",
}),
noOptionsMessage: (base) => ({
...base,
@@ -99,14 +146,14 @@ export const styles = ({
}),
multiValueLabel: (base) => ({
...base,
- backgroundColor: "#cccccc",
+ backgroundColor: backgroundColor === "white" ? "#cccccc" : "#2f2f2f",
color,
padding: "0.2rem",
borderRadius: "2px 0 0 2px",
}),
multiValueRemove: (base) => ({
...base,
- backgroundColor: "#cccccc",
+ backgroundColor: backgroundColor === "white" ? "#cccccc" : "#2f2f2f",
color,
borderRadius: "0 2px 2px 0",
cursor: "pointer",
diff --git a/packages/client/src/components/leo/StatusesArea.tsx b/packages/client/src/components/leo/StatusesArea.tsx
index 65fa32e1d..6e43ba174 100644
--- a/packages/client/src/components/leo/StatusesArea.tsx
+++ b/packages/client/src/components/leo/StatusesArea.tsx
@@ -46,7 +46,7 @@ export const StatusesArea = () => {
if (!activeOfficer) return;
if (status.id === activeOfficer.statusId) return;
- const { json } = await execute(`/leo/${activeOfficer.id}/status`, {
+ const { json } = await execute(`/dispatch/status/${activeOfficer.id}`, {
method: "PUT",
data: {
status: status.id,
diff --git a/packages/client/src/components/leo/modals/SelectOfficerModal.tsx b/packages/client/src/components/leo/modals/SelectOfficerModal.tsx
index f542339f4..34a373fd3 100644
--- a/packages/client/src/components/leo/modals/SelectOfficerModal.tsx
+++ b/packages/client/src/components/leo/modals/SelectOfficerModal.tsx
@@ -31,7 +31,7 @@ export const SelectOfficerModal = () => {
async function onSubmit(values: typeof INITIAL_VALUES) {
if (!onDutyCode) return;
- const { json } = await execute(`/leo/${values.officer}/status`, {
+ const { json } = await execute(`/dispatch/status/${values.officer}`, {
method: "PUT",
data: {
...values,
diff --git a/packages/client/src/components/modals/Manage911CallModal.tsx b/packages/client/src/components/modals/Manage911CallModal.tsx
index c05c6b0c9..5202f382e 100644
--- a/packages/client/src/components/modals/Manage911CallModal.tsx
+++ b/packages/client/src/components/modals/Manage911CallModal.tsx
@@ -20,7 +20,6 @@ import { SocketEvents } from "@snailycad/config";
import { CallEventsArea } from "./911Call/EventsArea";
import { useGenerateCallsign } from "hooks/useGenerateCallsign";
import { makeUnitName } from "lib/utils";
-import { ContextMenu } from "components/context-menu/ContextMenu";
interface Props {
call: Full911Call | null;
@@ -274,29 +273,6 @@ export const Manage911CallModal = ({ setCall, call, onClose }: Props) => {
{call ? : null}
-
-
Assigned Units
-
- {call?.assignedUnits.map((unit) => (
-
-
-
- {generateCallsign(unit.unit)}
- {makeUnitName(unit.unit)}
-
-
-
- ))}
-
-
{call ? (
export const PERMISSION_ROUTES: PermissionRoute[] = [
[
- ["PUT"],
- // /v1/leo/:officerId/status
- /\/v1\/(leo|ems-fd)\/[A-Z0-9]+\/status/i,
+ "*",
+ /\/v1\/dispatch\/status\/\w+/i,
(u) => u.isLeo || u.isSupervisor || u.isDispatch || u.isEmsFd,
],
[["GET"], /\/v1\/leo\/active-(officers|officer)/, (u) => u.isLeo || u.isDispatch],
From 6648f1f802d16ea84de4083801c32b68b4dc8e49 Mon Sep 17 00:00:00 2001
From: Dev-CasperTheGhost
<53900565+Dev-CasperTheGhost@users.noreply.github.com>
Date: Fri, 5 Nov 2021 08:30:38 +0100
Subject: [PATCH 4/5] :tada: progress on quick-events
---
.../dispatch/Calls911Controller.ts | 2 +-
.../client/src/components/form/Select.tsx | 30 +++++++++++++++----
.../client/src/components/leo/ActiveCalls.tsx | 2 +-
.../components/modals/911Call/EventsArea.tsx | 8 +++--
4 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/packages/api/src/controllers/dispatch/Calls911Controller.ts b/packages/api/src/controllers/dispatch/Calls911Controller.ts
index e823b9330..5959cd7da 100644
--- a/packages/api/src/controllers/dispatch/Calls911Controller.ts
+++ b/packages/api/src/controllers/dispatch/Calls911Controller.ts
@@ -327,7 +327,7 @@ export class Calls911Controller {
private officerOrDeputyToUnit(call: any & { assignedUnits: any[] }) {
return {
...call,
- assignedUnits: call.assignedUnits.map((v: any) => ({
+ assignedUnits: (call.assignedUnits ?? [])?.map((v: any) => ({
...v,
officer: undefined,
deputy: undefined,
diff --git a/packages/client/src/components/form/Select.tsx b/packages/client/src/components/form/Select.tsx
index 41fabcd45..e99de7235 100644
--- a/packages/client/src/components/form/Select.tsx
+++ b/packages/client/src/components/form/Select.tsx
@@ -12,6 +12,11 @@ import { ContextMenu } from "components/context-menu/ContextMenu";
import { useValues } from "context/ValuesContext";
import useFetch from "lib/useFetch";
import { StatusValue } from "types/prisma";
+import { useGenerateCallsign } from "hooks/useGenerateCallsign";
+import { Full911Call, useDispatchState } from "state/dispatchState";
+import { makeUnitName } from "lib/utils";
+import { useModal } from "context/ModalContext";
+import { ModalIds } from "types/ModalIds";
export interface SelectValue {
label: string;
@@ -30,16 +35,31 @@ interface Props extends Exclude {
const MultiValueContainer = (props: MultiValueGenericProps) => {
const { codes10 } = useValues();
const { execute } = useFetch();
+ const { getPayload } = useModal();
+ const generateCallsign = useGenerateCallsign();
+ const call = getPayload(ModalIds.Manage911Call);
+ const { allDeputies, allOfficers } = useDispatchState();
const unitId = props.data.value;
+ const unit = [...allDeputies, ...allOfficers].find((v) => v.id === unitId);
async function setCode(status: StatusValue) {
- const { json } = await execute(`/dispatch/status/${unitId}`, {
- method: "PUT",
- data: { status: status.id },
- });
+ if (!unit) return;
- console.log({ json });
+ if (status.type === "STATUS_CODE") {
+ await execute(`/dispatch/status/${unitId}`, {
+ method: "PUT",
+ data: { status: status.id },
+ });
+ } else {
+ if (!call) return;
+ await execute(`/911-calls/events/${call.id}`, {
+ method: "POST",
+ data: {
+ description: `${generateCallsign(unit)} ${makeUnitName(unit)} / ${status.value.value}`,
+ },
+ });
+ }
}
return (
diff --git a/packages/client/src/components/leo/ActiveCalls.tsx b/packages/client/src/components/leo/ActiveCalls.tsx
index f5d54cad3..93db9ac0f 100644
--- a/packages/client/src/components/leo/ActiveCalls.tsx
+++ b/packages/client/src/components/leo/ActiveCalls.tsx
@@ -85,7 +85,7 @@ export const ActiveCalls = () => {
function handleManageClick(call: Full911Call) {
setTempCall(call);
- openModal(ModalIds.Manage911Call);
+ openModal(ModalIds.Manage911Call, call);
}
function handleCallTow(call: Full911Call) {
diff --git a/packages/client/src/components/modals/911Call/EventsArea.tsx b/packages/client/src/components/modals/911Call/EventsArea.tsx
index f7d97228f..6f2d57ed9 100644
--- a/packages/client/src/components/modals/911Call/EventsArea.tsx
+++ b/packages/client/src/components/modals/911Call/EventsArea.tsx
@@ -131,20 +131,22 @@ const EventItem = ({ event, setTempEvent }: { event: Call911Event; setTempEvent:
return (
- {formatted}:
+
+ {formatted}:
+
{event.description}
-
From 792b9b4f5e0534dc965300e6f89b211c45a1960f Mon Sep 17 00:00:00 2001
From: Dev-CasperTheGhost
<53900565+Dev-CasperTheGhost@users.noreply.github.com>
Date: Fri, 5 Nov 2021 09:39:24 +0100
Subject: [PATCH 5/5] :tada: minor improvements for quick-events
---
.../components/context-menu/ContextMenu.tsx | 31 ++++++++++---
.../client/src/components/form/Select.tsx | 45 +++++++++++--------
.../components/modals/911Call/EventsArea.tsx | 2 +-
.../components/modals/Manage911CallModal.tsx | 3 +-
4 files changed, 56 insertions(+), 25 deletions(-)
diff --git a/packages/client/src/components/context-menu/ContextMenu.tsx b/packages/client/src/components/context-menu/ContextMenu.tsx
index 29b590071..bac957217 100644
--- a/packages/client/src/components/context-menu/ContextMenu.tsx
+++ b/packages/client/src/components/context-menu/ContextMenu.tsx
@@ -16,7 +16,8 @@ type ButtonProps = React.DetailedHTMLProps<
interface ContextItem extends ButtonProps {
name: string;
- component?: string;
+ // eslint-disable-next-line @typescript-eslint/ban-types
+ component?: keyof typeof components | (string & {});
}
export const ContextMenu = ({ items, children }: Props) => {
@@ -46,17 +47,23 @@ export const ContextMenu = ({ items, children }: Props) => {
"flex flex-col",
"shadow-md",
"bg-white dark:bg-dark-bright shadow-sm",
- "p-1.5 rounded-md min-w-[12rem] max-h-[25rem] overflow-auto",
+ "p-1.5 rounded-md min-w-[15rem] max-h-[25rem] overflow-auto",
)}
+ style={{ scrollbarWidth: "thin" }}
>
{items.map((item) => {
const { component = "Item", ...rest } = typeof item === "object" ? item : {};
+ // @ts-expect-error ignore
const Component = components[component] ?? components.Item;
return typeof item === "boolean" ? (
) : Component ? (
-
+
{item.name}
) : null;
@@ -90,13 +97,13 @@ export const ContextMenu = ({ items, children }: Props) => {
);
};
-const components: Record any> = {
+const components = {
Item: ({ children, ...rest }: any) => (
any> = {
{children}
),
+ Label: ({ children, ...rest }: any) => (
+
+ {children}
+
+ ),
};
diff --git a/packages/client/src/components/form/Select.tsx b/packages/client/src/components/form/Select.tsx
index e99de7235..cc7965933 100644
--- a/packages/client/src/components/form/Select.tsx
+++ b/packages/client/src/components/form/Select.tsx
@@ -30,6 +30,7 @@ interface Props extends Exclude {
hasError?: boolean;
isClearable?: boolean;
disabled?: boolean;
+ showContextMenuForUnits?: boolean;
}
const MultiValueContainer = (props: MultiValueGenericProps) => {
@@ -38,10 +39,10 @@ const MultiValueContainer = (props: MultiValueGenericProps) => {
const { getPayload } = useModal();
const generateCallsign = useGenerateCallsign();
const call = getPayload(ModalIds.Manage911Call);
- const { allDeputies, allOfficers } = useDispatchState();
+ const { activeDeputies, activeOfficers } = useDispatchState();
const unitId = props.data.value;
- const unit = [...allDeputies, ...allOfficers].find((v) => v.id === unitId);
+ const unit = [...activeDeputies, ...activeOfficers].find((v) => v.id === unitId);
async function setCode(status: StatusValue) {
if (!unit) return;
@@ -62,21 +63,28 @@ const MultiValueContainer = (props: MultiValueGenericProps) => {
}
}
+ const codesMapped: any[] = codes10.values.map((v) => ({
+ name: v.value.value,
+ onClick: () => setCode(v),
+ "aria-label":
+ v.type === "STATUS_CODE"
+ ? `Set status to ${v.value.value}`
+ : `Add code to event: ${v.value.value} `,
+ title:
+ v.type === "STATUS_CODE"
+ ? `Set status to ${v.value.value}`
+ : `Add code to event: ${v.value.value} `,
+ }));
+
+ if (unit) {
+ codesMapped.unshift({
+ name: `${generateCallsign(unit)} ${makeUnitName(unit)}`,
+ component: "Label",
+ });
+ }
+
return (
- ({
- name: v.value.value,
- onClick: () => setCode(v),
- "aria-label":
- v.type === "STATUS_CODE"
- ? `Set status to ${v.value.value}`
- : `Add code to event: ${v.value.value} `,
- title:
- v.type === "STATUS_CODE"
- ? `Set status to ${v.value.value}`
- : `Add code to event: ${v.value.value} `,
- }))}
- >
+
);
@@ -87,6 +95,7 @@ export const Select = ({ name, onChange, ...rest }: Props) => {
const common = useTranslations("Common");
const value =
typeof rest.value === "string" ? rest.values.find((v) => v.value === rest.value) : rest.value;
+ const { canBeClosed } = useModal();
const useDarkTheme =
user?.isDarkTheme &&
@@ -102,7 +111,7 @@ export const Select = ({ name, onChange, ...rest }: Props) => {
return (
handleChange(v)}
@@ -110,7 +119,7 @@ export const Select = ({ name, onChange, ...rest }: Props) => {
styles={styles(theme)}
className="border-gray-500"
menuPortalTarget={(typeof document !== "undefined" && document.body) || undefined}
- components={{ MultiValueContainer }}
+ components={rest.showContextMenuForUnits ? { MultiValueContainer } : undefined}
/>
);
};
diff --git a/packages/client/src/components/modals/911Call/EventsArea.tsx b/packages/client/src/components/modals/911Call/EventsArea.tsx
index 6f2d57ed9..fc2215df9 100644
--- a/packages/client/src/components/modals/911Call/EventsArea.tsx
+++ b/packages/client/src/components/modals/911Call/EventsArea.tsx
@@ -47,7 +47,7 @@ export const CallEventsArea = ({ call }: Props) => {
}
return (
-
+
{common("events")}
diff --git a/packages/client/src/components/modals/Manage911CallModal.tsx b/packages/client/src/components/modals/Manage911CallModal.tsx
index 5202f382e..91a6e9a28 100644
--- a/packages/client/src/components/modals/Manage911CallModal.tsx
+++ b/packages/client/src/components/modals/Manage911CallModal.tsx
@@ -199,7 +199,7 @@ export const Manage911CallModal = ({ setCall, call, onClose }: Props) => {
isOpen={isOpen(ModalIds.Manage911Call)}
onClose={handleClose}
title={call ? "Manage 911 Call" : t("create911Call")}
- className={call ? "w-[1000px]" : "w-[650px]"}
+ className={call ? "w-[1200px]" : "w-[650px]"}
>
@@ -228,6 +228,7 @@ export const Manage911CallModal = ({ setCall, call, onClose }: Props) => {
{isDispatch ? (