From d0fa13166382cd782929fc11ee3c2973e977bdef Mon Sep 17 00:00:00 2001 From: casperiv0 <53900565+casperiv0@users.noreply.github.com> Date: Sun, 15 Oct 2023 11:02:54 +0200 Subject: [PATCH] feat: able to mark a record as paid --- .../controllers/record/records-controller.ts | 47 +++++++++++++ apps/client/locales/en/citizen.json | 3 +- .../NameSearchModal/tabs/records-tab.tsx | 68 ++++++++++++++----- packages/utils/src/api-url.ts | 4 -- 4 files changed, 99 insertions(+), 23 deletions(-) diff --git a/apps/api/src/controllers/record/records-controller.ts b/apps/api/src/controllers/record/records-controller.ts index 5884b9ad9..3bbe6a04a 100644 --- a/apps/api/src/controllers/record/records-controller.ts +++ b/apps/api/src/controllers/record/records-controller.ts @@ -25,6 +25,7 @@ import { Officer, User, Business, + PaymentStatus, } from "@prisma/client"; import { validateSchema } from "lib/data/validate-schema"; import { combinedUnitProperties, leoProperties } from "utils/leo/includes"; @@ -389,6 +390,52 @@ export class RecordsController { return recordItem; } + @Post("/mark-as-paid/:id") + @Description("Allow a citizen to mark a record as paid") + @IsFeatureEnabled({ + feature: Feature.CITIZEN_RECORD_PAYMENTS, + }) + async markRecordAsPaid( + @Context("cad") cad: { features?: Record }, + @PathParams("id") recordId: string, + @Context("sessionUserId") sessionUserId: string, + ): Promise { + const citizen = await prisma.citizen.findFirst({ + where: { + userId: sessionUserId, + Record: { some: { id: recordId } }, + }, + }); + + if (!citizen) { + throw new NotFound("citizenNotFound"); + } + + const record = await prisma.record.findFirst({ + where: { id: recordId }, + }); + + if (!record) { + throw new NotFound("recordNotFound"); + } + + const isEnabled = isFeatureEnabled({ + feature: Feature.CITIZEN_RECORD_APPROVAL, + features: cad.features, + defaultReturn: false, + }); + + const updatedRecord = await prisma.record.update({ + where: { id: recordId }, + data: { + paymentStatus: PaymentStatus.PAID, + }, + include: recordsInclude(isEnabled).include, + }); + + return updatedRecord; + } + @UseBefore(ActiveOfficer) @Delete("/:id") @Description("Delete a warrant, ticket, written warning or arrest report by its id") diff --git a/apps/client/locales/en/citizen.json b/apps/client/locales/en/citizen.json index a079a3c14..5985e87d8 100644 --- a/apps/client/locales/en/citizen.json +++ b/apps/client/locales/en/citizen.json @@ -68,7 +68,8 @@ "huntingLicenseCategory": "Hunting License Categories", "huntingLicensePoints": "Hunting License Points", "fishingLicensePoints": "Fishing License Points", - "otherLicenseCategory": "Other License Categories" + "otherLicenseCategory": "Other License Categories", + "markAsPaid": "Mark As Paid" }, "Vehicles": { "model": "Model", diff --git a/apps/client/src/components/leo/modals/NameSearchModal/tabs/records-tab.tsx b/apps/client/src/components/leo/modals/NameSearchModal/tabs/records-tab.tsx index fd686c77d..0f70cae1f 100644 --- a/apps/client/src/components/leo/modals/NameSearchModal/tabs/records-tab.tsx +++ b/apps/client/src/components/leo/modals/NameSearchModal/tabs/records-tab.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import compareDesc from "date-fns/compareDesc"; import { useRouter } from "next/router"; -import { Record, RecordType } from "@snailycad/types"; +import { PaymentStatus, Record, RecordType } from "@snailycad/types"; import { useTranslations } from "use-intl"; import { Button, FullDate, Loader, Status, TabsContent } from "@snailycad/ui"; import { ModalIds } from "types/modal-ids"; @@ -14,10 +14,11 @@ import { Table, useTableState } from "components/shared/Table"; import { ManageRecordModal } from "../../manage-record/manage-record-modal"; import { Permissions, usePermission } from "hooks/usePermission"; import { ViolationsColumn } from "components/leo/ViolationsColumn"; -import type { DeleteRecordsByIdData } from "@snailycad/types/api"; +import type { DeleteRecordsByIdData, PutRecordsByIdData } from "@snailycad/types/api"; import { RecordsCaseNumberColumn } from "components/leo/records-case-number-column"; import { CallDescription } from "components/dispatch/active-calls/CallDescription"; import { getAPIUrl } from "@snailycad/utils/api-url"; +import { useFeatureEnabled } from "hooks/useFeatureEnabled"; interface RecordsTabProps { records: Record[]; @@ -95,7 +96,7 @@ export function RecordsTab({ {data.length <= 0 ? (

{noValuesText}

) : ( - + )} ) : ( @@ -151,6 +152,7 @@ export function RecordsTable({ const modalState = useModal(); const t = useTranslations(); const router = useRouter(); + const { execute, state } = useFetch(); const isCitizenCreation = router.pathname === "/citizen/create"; const isCitizen = router.pathname.startsWith("/citizen") && !isCitizenCreation; @@ -158,6 +160,7 @@ export function RecordsTable({ const { generateCallsign } = useGenerateCallsign(); const tableState = useTableState(); const currency = common("currency"); + const { CITIZEN_RECORD_PAYMENTS } = useFeatureEnabled(); const { hasPermissions } = usePermission(); const _hasDeletePermissions = @@ -187,6 +190,17 @@ export function RecordsTable({ document.body.removeChild(anchor); } + async function handleMarkAsPaid(record: Record) { + const { json } = await execute({ + path: `/records/mark-as-paid/${record.id}`, + method: "POST", + }); + + if (json.id) { + onEdit?.(json); + } + } + async function handleExportClick(record: Record) { setExportState("loading"); @@ -265,9 +279,27 @@ export function RecordsTable({ /> ), createdAt: {record.createdAt}, - actions: isCitizen ? null : ( + actions: ( <> - {isCitizenCreation ? null : ( + {isCitizen && CITIZEN_RECORD_PAYMENTS ? ( + record.paymentStatus === PaymentStatus.PAID ? ( + "—" + ) : ( + + ) + ) : null} + + {isCitizen ? null : ( <> + + {_hasDeletePermissions ? ( + + ) : null} )} - - {_hasDeletePermissions ? ( - - ) : null} ), }; @@ -315,7 +347,7 @@ export function RecordsTable({ { header: t("Leo.paymentStatus"), accessorKey: "paymentStatus" }, isCitizen ? { header: t("Leo.totalCost"), accessorKey: "totalCost" } : null, { header: common("createdAt"), accessorKey: "createdAt" }, - isCitizen ? null : { header: common("actions"), accessorKey: "actions" }, + { header: common("actions"), accessorKey: "actions" }, ]} /> diff --git a/packages/utils/src/api-url.ts b/packages/utils/src/api-url.ts index 9143eedb6..4afce3b3b 100644 --- a/packages/utils/src/api-url.ts +++ b/packages/utils/src/api-url.ts @@ -4,10 +4,6 @@ export function getAPIUrl(): string { const envUrl = process.env.NEXT_PUBLIC_PROD_ORIGIN ?? "http://localhost:8080/v1"; - if (process.env.NODE_ENV === "development") { - return "http://172.20.10.2:8080/v1"; - } - if (envUrl.endsWith("/v1")) { return envUrl; }