Skip to content

Commit

Permalink
🎉 feat: able to revoke warrants (#225)
Browse files Browse the repository at this point in the history
  • Loading branch information
casperiv0 authored Jan 1, 2022
1 parent d52a475 commit fce1c6c
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 16 deletions.
4 changes: 2 additions & 2 deletions packages/api/src/controllers/leo/SearchController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ const citizenSearchInclude = {
driversLicense: true,
ccw: true,
pilotLicense: true,
warrants: true,
warrants: { include: { officer: { include: unitProperties } } },
Record: {
include: {
officer: {
select: unitProperties,
include: unitProperties,
},
violations: {
include: {
Expand Down
56 changes: 48 additions & 8 deletions packages/api/src/controllers/record/RecordsController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Delete, JsonRequestBody, Post } from "@tsed/schema";
import { Delete, JsonRequestBody, Post, Put } from "@tsed/schema";
import { CREATE_TICKET_SCHEMA, CREATE_WARRANT_SCHEMA, validate } from "@snailycad/schemas";
import { BodyParams, Context, PathParams } from "@tsed/platform-params";
import { BadRequest, NotFound } from "@tsed/exceptions";
Expand Down Expand Up @@ -48,6 +48,30 @@ export class RecordsController {
return warrant;
}

@Put("/:id")
async updateWarrant(@BodyParams() body: JsonRequestBody, @PathParams("id") warrantId: string) {
if (!body.get("status")) {
throw new BadRequest("statusIsRequired");
}

const warrant = await prisma.warrant.findUnique({
where: { id: warrantId },
});

if (!warrant) {
throw new NotFound("warrantNotFound");
}

const updated = await prisma.warrant.update({
where: { id: warrantId },
data: {
status: body.get("status"),
},
});

return updated;
}

@Post("/")
async createTicket(@BodyParams() body: JsonRequestBody, @Context() ctx: Context) {
const error = validate(CREATE_TICKET_SCHEMA, body.toJSON(), true);
Expand Down Expand Up @@ -175,16 +199,32 @@ export class RecordsController {

@UseBefore(ActiveOfficer)
@Delete("/:id")
async deleteRecord(@PathParams("id") id: string) {
const record = await prisma.record.findUnique({
where: { id },
});
async deleteRecord(@PathParams("id") id: string, @BodyParams() body: JsonRequestBody) {
// eslint-disable-next-line @typescript-eslint/ban-types
const type = body.get("type") as "WARRANT" | (string & {});

if (type === "WARRANT") {
const warrant = await prisma.warrant.findUnique({
where: { id },
});

if (!warrant) {
throw new NotFound("warrantNotFound");
}
} else {
const record = await prisma.record.findUnique({
where: { id },
});

if (!record) {
throw new NotFound("recordNotFound");
if (!record) {
throw new NotFound("recordNotFound");
}
}

await prisma.record.delete({
const name = type === "WARRANT" ? "warrant" : "record";

// @ts-expect-error simple shortcut
await prisma[name].delete({
where: { id },
});

Expand Down
7 changes: 6 additions & 1 deletion packages/client/locales/en/leo.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,16 @@
"showBlips": "Show Blips",
"citizenLogs": "Citizen Logs",
"noLogs": "There are no logs yet.",
"revoke": "Revoke",
"warrants": "Warrants",
"noWarrants": "This citizen does not have any warrants",
"revokeWarrant": "Revoke Warrant",
"releaseCitizen": "You are about to release this citizen. Please select the reason why they are getting released.",
"alert_deleteRecord": "Are you sure you want to delete this record? This action cannot be undone.",
"alert_deleteOfficer": "Are you sure you want to delete <span>{officer}</span>? This action cannot be undone.",
"alert_allowCheckout": "Are you sure you to checkout this vehicle from the impound lot?",
"alert_deleteIncident": "Are you sure you want to delete this incident? This action cannot be undone."
"alert_deleteIncident": "Are you sure you want to delete this incident? This action cannot be undone.",
"alert_revokeWarrant": "Are you sure you want to revoke this warrant? This action cannot be undone."
},
"Bolos": {
"activeBolos": "Active Bolos",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,10 @@ export function NameSearchModal() {
) : null}

{toggled === Toggled.RECORDS ? (
<RecordsArea records={currentResult.Record} />
<RecordsArea
warrants={currentResult.warrants}
records={currentResult.Record}
/>
) : null}
</>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from "react";
import compareDesc from "date-fns/compareDesc";
import format from "date-fns/format";
import { useRouter } from "next/router";
import { Record, RecordType, Violation } from "types/prisma";
import { Record, RecordType, Violation, Warrant } from "types/prisma";
import { useTranslations } from "use-intl";
import { Button } from "components/Button";
import { ModalIds } from "types/ModalIds";
Expand All @@ -14,13 +14,15 @@ import { makeUnitName } from "lib/utils";
import { useGenerateCallsign } from "hooks/useGenerateCallsign";
import { FullOfficer } from "state/dispatchState";
import { Table } from "components/table/Table";
import { Select } from "components/form/Select";

export type FullRecord = Record & { officer: FullOfficer; violations: Violation[] };
interface Props {
records: FullRecord[];
warrants?: (Warrant & { officer: FullOfficer })[];
}

export function RecordsArea({ records }: Props) {
export function RecordsArea({ warrants, records }: Props) {
const t = useTranslations();
const router = useRouter();
const { state, execute } = useFetch();
Expand All @@ -47,7 +49,7 @@ export function RecordsArea({ records }: Props) {
});

if (json) {
if (typeof currentResult === "object" && currentResult) {
if (currentResult) {
setCurrentResult({
...currentResult,
Record: currentResult.Record.filter((v) => v.id !== tempItem.id),
Expand All @@ -74,6 +76,14 @@ export function RecordsArea({ records }: Props) {
</section>
))}

{warrants ? (
<section className="my-2 mb-5">
<h3 className="text-xl font-semibold">{t("Leo.warrants")}</h3>

{warrants.length <= 0 ? <p>{t("Leo.noWarrants")}</p> : <WarrantsTable data={warrants} />}
</section>
) : null}

<AlertModal
id={ModalIds.AlertDeleteRecord}
onDeleteClick={handleDelete}
Expand Down Expand Up @@ -131,3 +141,113 @@ function RecordsTable({ data }: { data: FullRecord[] }) {
</div>
);
}

const values = [
{ label: "Inactive", value: "inactive" },
{ label: "Active", value: "active" },
];

function WarrantsTable({ data }: { data: (Warrant & { officer: FullOfficer })[] }) {
const common = useTranslations("Common");
const { openModal, closeModal, getPayload } = useModal();
const t = useTranslations();
const generateCallsign = useGenerateCallsign();
const { state, execute } = useFetch();
const { currentResult, setCurrentResult } = useNameSearch();

async function handleDelete() {
const warrant = getPayload<Warrant>(ModalIds.AlertRevokeWarrant);
if (!warrant) return;

const { json } = await execute(`/records/${warrant.id}`, {
data: { type: "WARRANT" },
method: "DELETE",
});

if (json) {
if (currentResult) {
setCurrentResult({
...currentResult,
warrants: currentResult.warrants.filter((v) => v.id !== warrant.id),
});
}

closeModal(ModalIds.AlertRevokeWarrant);
}
}

function handleDeleteClick(warrant: Warrant) {
openModal(ModalIds.AlertRevokeWarrant, warrant);
}

async function handleChange(value: string, warrant: Warrant) {
const { json } = await execute(`/records/${warrant.id}`, {
data: { status: value.toUpperCase(), type: "WARRANT" },
method: "PUT",
});

if (json && currentResult) {
setCurrentResult({
...currentResult,
warrants: currentResult.warrants.map((v) => {
if (v.id === warrant.id) {
return { ...v, ...json };
}

return v;
}),
});
}
}

return (
<div>
<Table
data={data
.sort((a, b) => compareDesc(new Date(a.createdAt), new Date(b.createdAt)))
.map((warrant) => {
const value = values.find((v) => v.value === warrant.status.toLowerCase());

return {
officer: `${generateCallsign(warrant.officer)} ${makeUnitName(warrant.officer)}`,
description: warrant.description,
createdAt: format(new Date(warrant.createdAt), "yyyy-MM-dd"),
actions: (
<div className="flex gap-2">
<Select
onChange={(e) => handleChange(e.target.value, warrant)}
className="w-40"
values={values}
value={value ?? null}
/>
<Button
type="button"
onClick={() => handleDeleteClick(warrant)}
small
variant="danger"
>
{t("Leo.revoke")}
</Button>
</div>
),
};
})}
columns={[
{ Header: t("Leo.officer"), accessor: "officer" },
{ Header: common("description"), accessor: "description" },
{ Header: common("createdAt"), accessor: "createdAt" },
{ Header: common("actions"), accessor: "actions" },
]}
/>

<AlertModal
id={ModalIds.AlertRevokeWarrant}
onDeleteClick={handleDelete}
description={t("Leo.alert_revokeWarrant")}
title={t("Leo.revokeWarrant")}
deleteText={t("Leo.revoke")}
state={state}
/>
</div>
);
}
3 changes: 2 additions & 1 deletion packages/client/src/state/nameSearchState.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { FullRecord } from "components/leo/modals/NameSearchModal/RecordsArea";
import type { Citizen, RegisteredVehicle, Warrant, Weapon } from "types/prisma";
import create from "zustand";
import { FullOfficer } from "./dispatchState";

export interface NameSearchResult extends Citizen {
vehicles: RegisteredVehicle[];
weapons: Weapon[];
Record: FullRecord[];
warrants: Warrant[];
warrants: (Warrant & { officer: FullOfficer })[];
}

interface NameSearchState {
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/types/ModalIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ export const enum ModalIds {
AlertDeleteIncident = "AlertDeleteIncidentModal",
AlertReleaseCitizen = "AlertReleaseCitizenModal",
AlertDeleteGroup = "AlertDeleteGroupModal",
AlertRevokeWarrant = "AlertRevokeWarrantModal",
}

0 comments on commit fce1c6c

Please sign in to comment.