Skip to content

Commit

Permalink
feat: deleteable penal codes (#1854)
Browse files Browse the repository at this point in the history
<!--
Thanks for opening a PR! Your contribution is much appreciated!
-->

## Bug

- [ ] Related issues linked using `closes: #number`

## Feature

- [x] Related issues linked using closes #
- [x] There is only 1 db migration with no breaking changes.

## Translations

- [ ] `.json` files are formatted: `pnpm format`
- [ ] Translations are correct
- [ ] New translation? It's been added to `i18n.config.mjs`
  • Loading branch information
casperiv0 authored Oct 21, 2023
1 parent bf6d39f commit 8087b66
Show file tree
Hide file tree
Showing 11 changed files with 68 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- DropForeignKey
ALTER TABLE "Violation" DROP CONSTRAINT "Violation_penalCodeId_fkey";

-- AlterTable
ALTER TABLE "Violation" ALTER COLUMN "penalCodeId" DROP NOT NULL;

-- AddForeignKey
ALTER TABLE "Violation" ADD CONSTRAINT "Violation_penalCodeId_fkey" FOREIGN KEY ("penalCodeId") REFERENCES "PenalCode"("id") ON DELETE SET NULL ON UPDATE CASCADE;
6 changes: 3 additions & 3 deletions apps/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -709,13 +709,13 @@ model WarningNotApplicable {
}

model Violation {
id String @id @default(uuid())
id String @id @default(uuid())
fine Int?
jailTime Int?
bail Int?
communityService String?
penalCode PenalCode @relation(fields: [penalCodeId], references: [id])
penalCodeId String
penalCode PenalCode? @relation(fields: [penalCodeId], references: [id], onDelete: SetNull)
penalCodeId String?
records Record[]
counts Int?
}
Expand Down
3 changes: 2 additions & 1 deletion apps/client/locales/en/leo.json
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,8 @@
"all": "All",
"ungrouped": "Ungrouped",
"counts": "Counts",
"stats": "Stats"
"stats": "Stats",
"deletedPenalCode": "Deleted Penal Code"
},
"Bolos": {
"activeBolos": "Active Bolos",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,6 @@ function ResultsForm({ result, onSuccess, handleClose }: ResultProps) {
export function getTitles(
record: GetManageExpungementRequests["pendingExpungementRequests"][number]["records"][number],
) {
const titles = record.violations.map((v) => v.penalCode.title);
const titles = record.violations.map((v) => v.penalCode?.title).filter(Boolean) as string[];
return titles.join(", ");
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { components, type MultiValueGenericProps } from "react-select";
import type { PenalCode } from "@snailycad/types";
import { dataToSlate, Editor } from "components/editor/editor";
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@snailycad/ui";
import { useTranslations } from "use-intl";

export function MultiValueContainerPenalCode(props: MultiValueGenericProps<any>) {
const penalCode = props.data.value as PenalCode;
const t = useTranslations("Leo");

return (
<HoverCard>
Expand All @@ -15,7 +17,7 @@ export function MultiValueContainerPenalCode(props: MultiValueGenericProps<any>)
</HoverCardTrigger>

<HoverCardContent pointerEvents>
<h3 className="text-lg font-semibold px-2">{penalCode.title}</h3>
<h3 className="text-lg font-semibold px-2">{penalCode.title || t("deletedPenalCode")}</h3>

<div className="dark:text-gray-200 mt-2 text-base">
<Editor isReadonly value={dataToSlate(penalCode)} />
Expand Down
26 changes: 20 additions & 6 deletions apps/client/src/components/leo/ViolationsColumn.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import type { Record } from "@snailycad/types";
import type { Record, Violation } from "@snailycad/types";
import { dataToSlate, Editor } from "components/editor/editor";
import { useValues } from "context/ValuesContext";
import { useTranslations } from "next-intl";
Expand All @@ -12,12 +12,27 @@ interface Props {
export function ViolationsColumn({ violations }: Props) {
const { penalCode } = useValues();
const common = useTranslations("Common");
const t = useTranslations("Leo");

const getPenalCode = React.useCallback(
(penalCodeId: string) => {
return penalCode.values.find((p) => p.id === penalCodeId) ?? null;
(violation: Violation | { penalCodeId: string | null }) => {
if (!violation.penalCodeId) {
return { title: t("deletedPenalCode"), description: common("none") };
}

const isViolation = "penalCode" in violation;
if (isViolation) {
return violation.penalCode;
}

const isString = "penalCodeId" in violation;
if (isString) {
return penalCode.values.find((p) => p.id === violation.penalCodeId) ?? null;
}

return null;
},
[penalCode.values],
[penalCode.values, common, t],
);

if (violations.length <= 0) {
Expand All @@ -29,9 +44,8 @@ export function ViolationsColumn({ violations }: Props) {
{violations.map((violation, idx) => {
const comma = idx !== violations.length - 1 ? ", " : "";
const key = "id" in violation ? violation.id : idx;
const penalCode = getPenalCode(violation);

const penalCode =
"penalCode" in violation ? violation.penalCode : getPenalCode(violation.penalCodeId);
if (!penalCode) return null;

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ interface Props {
}

interface CreateInitialRecordValuesOptions {
t: ReturnType<typeof useTranslations<"Leo">>;
type: RecordType;
record?: Record | null;
penalCodes: PenalCode[];
Expand All @@ -98,7 +99,7 @@ export function createInitialRecordValues(options: CreateInitialRecordValuesOpti

violations: (options.record?.violations ?? []).map((v) => {
return {
label: v.penalCode.title,
label: v.penalCode?.title ?? options.t("deletedPenalCode"),
value: {
...v.penalCode,
fine: { enabled: Boolean(v.fine), value: v.fine },
Expand Down Expand Up @@ -264,6 +265,7 @@ export function ManageRecordModal(props: Props) {
const validate = handleValidate(schema);

const INITIAL_VALUES = createInitialRecordValues({
t,
type: props.type,
record: props.record,
penalCodes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export function PenalCodesTable({ isReadOnly, penalCodes, isWithinCardOrModal =
let sum = 0;

for (const violation of values.violations) {
// skip deleted penal code
if (!violation.value.id) continue;
const counts = parseInt(String(violation.value.counts.value)) || 1;

if (violation.value[type].value) {
Expand Down Expand Up @@ -53,7 +55,7 @@ export function PenalCodesTable({ isReadOnly, penalCodes, isWithinCardOrModal =
tableState={tableState}
data={penalCodes.map((penalCode) => ({
id: penalCode.id,
title: penalCode.title,
title: penalCode.title || t("deletedPenalCode"),
data: <TableItemForm isReadOnly={isReadOnly} penalCode={penalCode} />,
}))}
columns={[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,20 @@ interface HandleValueChangeOptions {
export function TableItemForm({ penalCode, isReadOnly }: Props) {
const t = useTranslations("Leo");
const { LEO_BAIL } = useFeatureEnabled();
const isDeleted = !penalCode.id;

const minFine = getPenalCodeMinFines(penalCode);
const maxFine = getPenalCodeMaxFines(penalCode);

const [minJailTime, maxJailTime] = penalCode.warningNotApplicable?.prisonTerm ?? [];
const [minBail, maxBail] = penalCode.warningNotApplicable?.bail ?? [];

const jailTimeDisabled = isReadOnly || !penalCode.warningNotApplicable?.prisonTerm.length;
const bailDisabled = isReadOnly || !penalCode.warningNotApplicable?.bail.length;
const jailTimeDisabled =
isDeleted || isReadOnly || !penalCode.warningNotApplicable?.prisonTerm.length;
const bailDisabled = isDeleted || isReadOnly || !penalCode.warningNotApplicable?.bail.length;
const warningNotApplicableDisabled =
isReadOnly || !penalCode.warningNotApplicableId || jailTimeDisabled;
const finesDisabled = isReadOnly || !hasFines(penalCode);
isDeleted || isReadOnly || !penalCode.warningNotApplicableId || jailTimeDisabled;
const finesDisabled = isDeleted || isReadOnly || !hasFines(penalCode);

const { setFieldValue, values, errors } =
useFormikContext<ReturnType<typeof createInitialRecordValues>>();
Expand All @@ -102,7 +104,7 @@ export function TableItemForm({ penalCode, isReadOnly }: Props) {
>;

const current = values.violations.find(
(v: SelectValue<PenalCode>) => v.value?.id === penalCode.id,
(v: SelectValue<Partial<PenalCode>>) => v.value?.id === penalCode.id,
);

const currentValue = parseCurrentValue(current?.value ?? null);
Expand Down Expand Up @@ -166,7 +168,7 @@ export function TableItemForm({ penalCode, isReadOnly }: Props) {
name="fine.counts"
onChange={(event) => handleValueChange({ fieldName: "counts", event })}
type="number"
disabled={isReadOnly}
disabled={isDeleted || isReadOnly}
className="max-w-[125px] min-w-[125px] py-0.5"
value={transformField(currentValue.counts.value, "1")}
/>
Expand Down Expand Up @@ -199,7 +201,7 @@ export function TableItemForm({ penalCode, isReadOnly }: Props) {
min={minFine}
max={maxFine}
type="number"
disabled={isReadOnly || !currentValue.fine.enabled}
disabled={isDeleted || isReadOnly || !currentValue.fine.enabled}
className="max-w-[125px] min-w-[125px] ml-5 py-0.5"
value={
!isNaN(parseFloat(String(currentValue.fine.value))) ? currentValue.fine.value : ""
Expand All @@ -210,6 +212,7 @@ export function TableItemForm({ penalCode, isReadOnly }: Props) {
<FieldWrapper errorMessage={violationErrors[penalCode.id]?.communityService}>
<div className="flex items-center">
<CheckboxField
isDisabled={isDeleted || isReadOnly}
isSelected={currentValue.communityService.enabled}
className="mb-0"
onChange={(isSelected) =>
Expand All @@ -222,7 +225,7 @@ export function TableItemForm({ penalCode, isReadOnly }: Props) {
<Textarea
name="communityService.value"
onChange={(event) => handleValueChange({ fieldName: "communityService", event })}
disabled={isReadOnly || !currentValue.communityService.enabled}
disabled={isDeleted || isReadOnly || !currentValue.communityService.enabled}
className="max-w-[250px] min-w-[250px] ml-5 py-0.5"
value={currentValue.communityService.value}
/>
Expand Down Expand Up @@ -253,7 +256,10 @@ export function TableItemForm({ penalCode, isReadOnly }: Props) {
max={maxJailTime}
type="number"
disabled={
isReadOnly || warningNotApplicableDisabled || !currentValue.jailTime.enabled
isDeleted ||
isReadOnly ||
warningNotApplicableDisabled ||
!currentValue.jailTime.enabled
}
className="max-w-[125px] min-w-[125px] ml-5 py-0.5"
value={!isNaN(Number(currentValue.jailTime.value)) ? currentValue.jailTime.value : ""}
Expand All @@ -266,6 +272,7 @@ export function TableItemForm({ penalCode, isReadOnly }: Props) {
onChange={(event) => handleValueChange({ fieldName: "bail", event })}
name="bail.value"
disabled={
isDeleted ||
isReadOnly ||
bailDisabled ||
warningNotApplicableDisabled ||
Expand Down Expand Up @@ -310,7 +317,7 @@ function FieldWrapper({
/** appends default data to the current penal code */
export function parseCurrentValue(
penalCode:
| (PenalCode & Partial<Record<PenalCodeValueName, PenalCodeValue<PenalCodeValueName>>>)
| (Partial<PenalCode> & Partial<Record<PenalCodeValueName, PenalCodeValue<PenalCodeValueName>>>)
| null,
) {
return {
Expand Down
12 changes: 7 additions & 5 deletions packages/types/src/api/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,12 @@ export type GetManageCitizenByIdData =
export type PostCitizenRecordLogsData = Prisma.Record & {
officer?: Types.Officer | null;
violations: (Prisma.Violation & {
penalCode: Prisma.PenalCode & {
warningApplicable: Prisma.WarningApplicable | null;
warningNotApplicable: Prisma.WarningNotApplicable | null;
};
penalCode?:
| (Prisma.PenalCode & {
warningApplicable: Prisma.WarningApplicable | null;
warningNotApplicable: Prisma.WarningNotApplicable | null;
})
| null;
})[];
seizedItems: Prisma.SeizedItem[];
};
Expand Down Expand Up @@ -410,7 +412,7 @@ export interface GetManageExpungementRequests {
citizen: Prisma.Citizen;
warrants: Prisma.Warrant[];
records: (Prisma.Record & {
violations: (Prisma.Violation & { penalCode: Prisma.PenalCode })[];
violations: (Prisma.Violation & { penalCode?: Prisma.PenalCode | null })[];
})[];
})[];
totalCount: number;
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export type WarningApplicable = Prisma.WarningApplicable;
export type WarningNotApplicable = Prisma.WarningNotApplicable;

export type Violation = Prisma.Violation & {
penalCode: PenalCode;
penalCode: PenalCode | null;
};

export type SeizedItem = Prisma.SeizedItem;
Expand Down

0 comments on commit 8087b66

Please sign in to comment.