From ff9ab4d790fb6588a5dd728df18a13355cb5611c Mon Sep 17 00:00:00 2001 From: Dev-CasperTheGhost <53900565+Dev-CasperTheGhost@users.noreply.github.com> Date: Sun, 24 Oct 2021 10:47:06 +0200 Subject: [PATCH] :tada: add truck-logs --- .../migrations/20211024074740_/migration.sql | 20 ++ .../migrations/20211024083227_/migration.sql | 4 + packages/api/prisma/schema.prisma | 36 +++- .../citizen/TruckLogsController.ts | 187 ++++++++++++++++++ packages/client/locales/en/truck-logs.json | 13 ++ .../nav-dropdowns/CitizenDropdown.tsx | 4 +- .../components/truck-logs/ManageTruckLog.tsx | 147 ++++++++++++++ packages/client/src/pages/citizen/index.tsx | 6 +- packages/client/src/pages/truck-logs.tsx | 154 +++++++++++++++ packages/client/src/types/ModalIds.ts | 3 + packages/client/src/types/prisma.ts | 13 ++ packages/schemas/src/index.ts | 1 + packages/schemas/src/truck-log.ts | 8 + 13 files changed, 583 insertions(+), 13 deletions(-) create mode 100644 packages/api/prisma/migrations/20211024074740_/migration.sql create mode 100644 packages/api/prisma/migrations/20211024083227_/migration.sql create mode 100644 packages/api/src/controllers/citizen/TruckLogsController.ts create mode 100644 packages/client/locales/en/truck-logs.json create mode 100644 packages/client/src/components/truck-logs/ManageTruckLog.tsx create mode 100644 packages/client/src/pages/truck-logs.tsx create mode 100644 packages/schemas/src/truck-log.ts diff --git a/packages/api/prisma/migrations/20211024074740_/migration.sql b/packages/api/prisma/migrations/20211024074740_/migration.sql new file mode 100644 index 000000000..0fcd4f2b4 --- /dev/null +++ b/packages/api/prisma/migrations/20211024074740_/migration.sql @@ -0,0 +1,20 @@ +-- CreateTable +CREATE TABLE "TruckLog" ( + "id" TEXT NOT NULL, + "citizenId" TEXT, + "userId" TEXT NOT NULL, + "vehicleId" TEXT, + "startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "endedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "TruckLog_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "TruckLog" ADD CONSTRAINT "TruckLog_citizenId_fkey" FOREIGN KEY ("citizenId") REFERENCES "Citizen"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TruckLog" ADD CONSTRAINT "TruckLog_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TruckLog" ADD CONSTRAINT "TruckLog_vehicleId_fkey" FOREIGN KEY ("vehicleId") REFERENCES "RegisteredVehicle"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/api/prisma/migrations/20211024083227_/migration.sql b/packages/api/prisma/migrations/20211024083227_/migration.sql new file mode 100644 index 000000000..ecb0ef392 --- /dev/null +++ b/packages/api/prisma/migrations/20211024083227_/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "TruckLog" ALTER COLUMN "startedAt" DROP DEFAULT, +ALTER COLUMN "startedAt" SET DATA TYPE VARCHAR(255), +ALTER COLUMN "endedAt" SET DATA TYPE VARCHAR(255); diff --git a/packages/api/prisma/schema.prisma b/packages/api/prisma/schema.prisma index ffe197859..0e75dbe28 100644 --- a/packages/api/prisma/schema.prisma +++ b/packages/api/prisma/schema.prisma @@ -79,6 +79,7 @@ model User { OfficerLog OfficerLog[] emsFdDeputies EmsFdDeputy[] TaxiCall TaxiCall[] + TruckLog TruckLog[] } model Citizen { @@ -122,6 +123,7 @@ model Citizen { emsFdDeputies EmsFdDeputy[] TaxiCall TaxiCall[] createdTaxiCalls TaxiCall[] @relation("taxiCallCreator") + truckLogs TruckLog[] } enum Rank { @@ -138,20 +140,21 @@ enum WhitelistStatus { } model RegisteredVehicle { - id String @id @default(cuid()) - user User @relation(fields: [userId], references: [id]) + id String @id @default(cuid()) + user User @relation(fields: [userId], references: [id]) userId String - citizen Citizen @relation(fields: [citizenId], references: [id]) + citizen Citizen @relation(fields: [citizenId], references: [id]) citizenId String - vinNumber String @unique @db.VarChar(255) - plate String @unique @db.VarChar(255) - model Value @relation("modelToValue", fields: [modelId], references: [id]) + vinNumber String @unique @db.VarChar(255) + plate String @unique @db.VarChar(255) + model Value @relation("modelToValue", fields: [modelId], references: [id]) modelId String - color String @db.VarChar(255) - createdAt DateTime @default(now()) - registrationStatus Value @relation("registrationStatusToValue", fields: [registrationStatusId], references: [id]) + color String @db.VarChar(255) + createdAt DateTime @default(now()) + registrationStatus Value @relation("registrationStatusToValue", fields: [registrationStatusId], references: [id]) registrationStatusId String - insuranceStatus String @db.VarChar(255) + insuranceStatus String @db.VarChar(255) + TruckLog TruckLog[] } model Weapon { @@ -523,6 +526,19 @@ model EmsFdDeputy { // call911Id String? } +// truck logs +model TruckLog { + id String @id @default(uuid()) + citizen Citizen? @relation(fields: [citizenId], references: [id], onDelete: Cascade) + citizenId String? + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String + vehicle RegisteredVehicle? @relation(fields: [vehicleId], references: [id], onDelete: SetNull) + vehicleId String? + startedAt String @db.VarChar(255) + endedAt String @db.VarChar(255) +} + // other enum Feature { BLEETER diff --git a/packages/api/src/controllers/citizen/TruckLogsController.ts b/packages/api/src/controllers/citizen/TruckLogsController.ts new file mode 100644 index 000000000..fc0dd5d99 --- /dev/null +++ b/packages/api/src/controllers/citizen/TruckLogsController.ts @@ -0,0 +1,187 @@ +import { User } from ".prisma/client"; +import { validate } from "@snailycad/schemas"; +import { Controller } from "@tsed/di"; +import { BadRequest, NotFound } from "@tsed/exceptions"; +import { BodyParams, Context, PathParams } from "@tsed/platform-params"; +import { Delete, Get, JsonRequestBody, Post, Put } from "@tsed/schema"; +import { prisma } from "../../lib/prisma"; +import { CREATE_TRUCK_LOG_SCHEMA } from "@snailycad/schemas"; +import { IsAuth } from "../../middlewares"; +import { UseBeforeEach } from "@tsed/platform-middlewares"; + +@Controller("/truck-logs") +@UseBeforeEach(IsAuth) +export class TruckLogsController { + @Get("/") + async getTruckLogs(@Context("user") user: User) { + const logs = await prisma.truckLog.findMany({ + where: { + userId: user.id, + }, + include: { + citizen: true, + vehicle: { + include: { + model: true, + registrationStatus: true, + }, + }, + }, + }); + + const registeredVehicles = await prisma.registeredVehicle.findMany({ + where: { + userId: user.id, + }, + include: { + model: true, + }, + }); + + console.log({ registeredVehicles }); + + return { logs, registeredVehicles }; + } + + @Post("/") + async createTruckLog(@Context("user") user: User, @BodyParams() body: JsonRequestBody) { + const error = validate(CREATE_TRUCK_LOG_SCHEMA, body.toJSON(), true); + if (error) { + throw new BadRequest(error); + } + + const citizen = await prisma.citizen.findFirst({ + where: { + id: body.get("citizenId"), + userId: user.id, + }, + }); + + if (!citizen) { + throw new NotFound("citizenNotFound"); + } + + const vehicle = await prisma.registeredVehicle.findFirst({ + where: { + id: body.get("vehicleId"), + userId: user.id, + citizenId: citizen.id, + }, + }); + + if (!vehicle) { + throw new NotFound("vehicleNotFound"); + } + + const log = await prisma.truckLog.create({ + data: { + userId: user.id, + citizenId: body.get("citizenId"), + endedAt: body.get("endedAt"), + startedAt: body.get("startedAt"), + vehicleId: body.get("vehicleId"), + }, + include: { + citizen: true, + vehicle: { + include: { + model: true, + registrationStatus: true, + }, + }, + }, + }); + + return log; + } + + @Put("/:id") + async updateTruckLog( + @Context("user") user: User, + @BodyParams() body: JsonRequestBody, + @PathParams("id") id: string, + ) { + const error = validate(CREATE_TRUCK_LOG_SCHEMA, body.toJSON(), true); + if (error) { + throw new BadRequest(error); + } + + const log = await prisma.truckLog.findFirst({ + where: { + id, + userId: user.id, + }, + }); + + if (!log) { + throw new NotFound("notFound"); + } + + const citizen = await prisma.citizen.findFirst({ + where: { + id: body.get("citizenId"), + userId: user.id, + }, + }); + + if (!citizen) { + throw new NotFound("citizenNotFound"); + } + + const vehicle = await prisma.registeredVehicle.findFirst({ + where: { + id: body.get("vehicleId"), + userId: user.id, + citizenId: citizen.id, + }, + }); + + if (!vehicle) { + throw new NotFound("vehicleNotFound"); + } + + const updated = await prisma.truckLog.update({ + where: { + id, + }, + data: { + citizenId: body.get("citizenId"), + endedAt: body.get("endedAt"), + vehicleId: body.get("vehicleId"), + }, + include: { + citizen: true, + vehicle: { + include: { + model: true, + registrationStatus: true, + }, + }, + }, + }); + + return updated; + } + + @Delete("/:id") + async deleteTruckLog(@Context("user") user: User, @PathParams("id") id: string) { + const log = await prisma.truckLog.findFirst({ + where: { + id, + userId: user.id, + }, + }); + + if (!log) { + throw new NotFound("notFound"); + } + + await prisma.truckLog.delete({ + where: { + id, + }, + }); + + return true; + } +} diff --git a/packages/client/locales/en/truck-logs.json b/packages/client/locales/en/truck-logs.json new file mode 100644 index 000000000..b7950f375 --- /dev/null +++ b/packages/client/locales/en/truck-logs.json @@ -0,0 +1,13 @@ +{ + "TruckLogs": { + "truckLogs": "Truck Logs", + "createTruckLog": "Create Truck Log", + "editTruckLog": "Edit Truck Log", + "deleteTruckLog": "Delete Truck Log", + "driver": "Driver", + "vehicle": "Vehicle", + "startedAt": "Started At", + "endedAt": "Ended At", + "alert_deleteTruckLog": "Are you sure you want to delete this truck log? This action cannot be undone." + } +} diff --git a/packages/client/src/components/nav-dropdowns/CitizenDropdown.tsx b/packages/client/src/components/nav-dropdowns/CitizenDropdown.tsx index 8a8625b73..1e4871adf 100644 --- a/packages/client/src/components/nav-dropdowns/CitizenDropdown.tsx +++ b/packages/client/src/components/nav-dropdowns/CitizenDropdown.tsx @@ -53,8 +53,8 @@ export const CitizenDropdown = () => { {items.map((item) => { - const upperCase = item.toUpperCase() as Feature; - const lower = item.toLowerCase(); + const upperCase = item.replace(/ +/g, "_").toUpperCase() as Feature; + const lower = item.replace(/ +/g, "-").toLowerCase(); if (!enabled[upperCase]) { return null; diff --git a/packages/client/src/components/truck-logs/ManageTruckLog.tsx b/packages/client/src/components/truck-logs/ManageTruckLog.tsx new file mode 100644 index 000000000..31d81b204 --- /dev/null +++ b/packages/client/src/components/truck-logs/ManageTruckLog.tsx @@ -0,0 +1,147 @@ +import { CREATE_TRUCK_LOG_SCHEMA } from "@snailycad/schemas"; +import { Button } from "components/Button"; +import { Error } from "components/form/Error"; +import { FormField } from "components/form/FormField"; +import { FormRow } from "components/form/FormRow"; +import { Input } from "components/form/Input"; +import { Select } from "components/form/Select"; +import { Loader } from "components/Loader"; +import { Modal } from "components/modal/Modal"; +import { useCitizen } from "context/CitizenContext"; +import { useModal } from "context/ModalContext"; +import { Formik } from "formik"; +import { handleValidate } from "lib/handleValidate"; +import useFetch from "lib/useFetch"; +import { FullTruckLog } from "src/pages/truck-logs"; +import { ModalIds } from "types/ModalIds"; +import { RegisteredVehicle } from "types/prisma"; +import { useTranslations } from "use-intl"; + +interface Props { + log: FullTruckLog | null; + registeredVehicles: RegisteredVehicle[]; + onUpdate?: (old: FullTruckLog, newLog: FullTruckLog) => void; + onCreate?: (log: FullTruckLog) => void; + onClose?: () => void; +} + +export const ManageTruckLogModal = ({ + onUpdate, + onCreate, + onClose, + registeredVehicles, + log, +}: Props) => { + const common = useTranslations("Common"); + const t = useTranslations("TruckLogs"); + const { isOpen, closeModal } = useModal(); + const { state, execute } = useFetch(); + const { citizens } = useCitizen(); + + function handleClose() { + onClose?.(); + closeModal(ModalIds.ManageTruckLog); + } + + async function onSubmit(values: typeof INITIAL_VALUES) { + if (log) { + const { json } = await execute(`/truck-logs/${log.id}`, { + method: "PUT", + data: values, + }); + + if (json.id) { + onUpdate?.(log, json); + closeModal(ModalIds.ManageTruckLog); + } + } else { + const { json } = await execute("/truck-logs", { + method: "POST", + data: values, + }); + + if (json.id) { + onCreate?.(json); + closeModal(ModalIds.ManageTruckLog); + } + } + } + + const INITIAL_VALUES = { + endedAt: log?.endedAt ?? "", + startedAt: log?.startedAt ?? "", + vehicleId: log?.vehicleId ?? "", + citizenId: log?.citizenId ?? "", + }; + + const validate = handleValidate(CREATE_TRUCK_LOG_SCHEMA); + + return ( + + + {({ handleSubmit, handleChange, values, isValid, errors }) => ( +
+ + + + {errors.startedAt} + + + + + {errors.endedAt} + + + + + ({ + label: vehicle.model.value, + value: vehicle.id, + }))} + value={values.vehicleId} + /> + {errors.vehicleId} + + + +
+ )} +
+
+ ); +}; diff --git a/packages/client/src/pages/citizen/index.tsx b/packages/client/src/pages/citizen/index.tsx index 3f241b179..e9261f234 100644 --- a/packages/client/src/pages/citizen/index.tsx +++ b/packages/client/src/pages/citizen/index.tsx @@ -18,6 +18,7 @@ import { makeImageUrl } from "lib/utils"; import { ManageCallModal } from "components/citizen/tow/ManageTowCall"; import { Manage911CallModal } from "components/modals/Manage911CallModal"; import { useFeatureEnabled } from "hooks/useFeatureEnabled"; +import { useAreaOfPlay } from "hooks/useAreaOfPlay"; interface Props { citizens: Citizen[]; @@ -28,6 +29,7 @@ export default function CitizenPage({ citizens }: Props) { const { openModal, closeModal } = useModal(); const [modal, setModal] = React.useState(null); const { TOW, TAXI } = useFeatureEnabled(); + const { showAop, areaOfPlay } = useAreaOfPlay(); return ( @@ -35,7 +37,9 @@ export default function CitizenPage({ citizens }: Props) { {t("citizens")} - SnailyCAD -

Citizens

+

+ Citizens{showAop ? - AOP: {areaOfPlay} : null} +