From 7f4ae04747836b8ab8fee0a84ffa1ac055d5feda Mon Sep 17 00:00:00 2001 From: casperiv0 <53900565+casperiv0@users.noreply.github.com> Date: Sun, 17 Sep 2023 18:42:42 +0200 Subject: [PATCH] feat: able to edit dashboard layout --- .../migration.sql | 7 + apps/api/prisma/schema.prisma | 12 ++ .../controllers/auth/user/user-controller.ts | 23 ++- apps/api/src/lib/auth/getSessionUser.ts | 3 + .../edit-dashboard-layout-modal.tsx | 156 ++++++++++++++++++ .../statuses-area.tsx} | 0 .../utility-panel.tsx} | 20 ++- apps/client/src/pages/dispatch/index.tsx | 23 ++- apps/client/src/pages/ems-fd/index.tsx | 42 ++++- apps/client/src/pages/officer/index.tsx | 59 ++++++- apps/client/src/types/modal-ids.ts | 1 + packages/schemas/src/auth.ts | 5 + packages/types/src/enums.ts | 12 ++ packages/types/src/index.ts | 5 +- 14 files changed, 346 insertions(+), 22 deletions(-) create mode 100644 apps/api/prisma/migrations/20230917161539_dashboard_layout_order/migration.sql create mode 100644 apps/client/src/components/shared/utility-panel/edit-dashboard-layout-modal.tsx rename apps/client/src/components/shared/{StatusesArea.tsx => utility-panel/statuses-area.tsx} (100%) rename apps/client/src/components/shared/{UtilityPanel.tsx => utility-panel/utility-panel.tsx} (73%) diff --git a/apps/api/prisma/migrations/20230917161539_dashboard_layout_order/migration.sql b/apps/api/prisma/migrations/20230917161539_dashboard_layout_order/migration.sql new file mode 100644 index 000000000..7853a9c1a --- /dev/null +++ b/apps/api/prisma/migrations/20230917161539_dashboard_layout_order/migration.sql @@ -0,0 +1,7 @@ +-- CreateEnum +CREATE TYPE "DashboardLayoutCardType" AS ENUM ('ACTIVE_CALLS', 'ACTIVE_BOLOS', 'ACTIVE_WARRANTS', 'ACTIVE_OFFICERS', 'ACTIVE_DEPUTIES', 'ACTIVE_INCIDENTS'); + +-- AlterTable +ALTER TABLE "User" ADD COLUMN "dispatchLayoutOrder" "DashboardLayoutCardType"[], +ADD COLUMN "emsFdLayoutOrder" "DashboardLayoutCardType"[], +ADD COLUMN "officerLayoutOrder" "DashboardLayoutCardType"[]; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 616a6548a..4730f332a 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -274,6 +274,9 @@ model User { toAddDefaultPermissions ToAddDefaultPermissions[] lastSeen DateTime @default(now()) developerMode Boolean @default(false) + dispatchLayoutOrder DashboardLayoutCardType[] + emsFdLayoutOrder DashboardLayoutCardType[] + officerLayoutOrder DashboardLayoutCardType[] // relational data citizens Citizen[] @@ -1954,3 +1957,12 @@ enum Feature { LEO_EDITABLE_CITIZEN_PROFILE // see #1698 ALLOW_MULTIPLE_UNITS_DEPARTMENTS_PER_USER // see #1722 } + +enum DashboardLayoutCardType { + ACTIVE_CALLS + ACTIVE_BOLOS + ACTIVE_WARRANTS + ACTIVE_OFFICERS + ACTIVE_DEPUTIES + ACTIVE_INCIDENTS +} diff --git a/apps/api/src/controllers/auth/user/user-controller.ts b/apps/api/src/controllers/auth/user/user-controller.ts index 36473f577..aaa945c09 100644 --- a/apps/api/src/controllers/auth/user/user-controller.ts +++ b/apps/api/src/controllers/auth/user/user-controller.ts @@ -1,14 +1,18 @@ import { Context, Res, BodyParams, QueryParams } from "@tsed/common"; import { Controller } from "@tsed/di"; import { UseBefore } from "@tsed/platform-middlewares"; -import { ContentType, Delete, Description, Patch, Post } from "@tsed/schema"; +import { ContentType, Delete, Description, Patch, Post, Put } from "@tsed/schema"; import { Cookie } from "@snailycad/config"; import { prisma } from "lib/data/prisma"; import { IsAuth } from "middlewares/auth/is-auth"; import { setCookie } from "utils/set-cookie"; import { cad, Rank, ShouldDoType, StatusViewMode, TableActionsAlignment } from "@prisma/client"; import { NotFound } from "@tsed/exceptions"; -import { CHANGE_PASSWORD_SCHEMA, CHANGE_USER_SCHEMA } from "@snailycad/schemas"; +import { + CHANGE_PASSWORD_SCHEMA, + CHANGE_USER_SCHEMA, + DASHBOARD_LAYOUT_SCHEMA, +} from "@snailycad/schemas"; import { compareSync, genSaltSync, hashSync } from "bcrypt"; import { userProperties } from "lib/auth/getSessionUser"; import { validateSchema } from "lib/data/validate-schema"; @@ -280,4 +284,19 @@ export class UserController { return true; } + + @Put("/dashboard-layout") + async editDashboardLayout(@BodyParams() body: unknown, @Context("user") user: User) { + const data = validateSchema(DASHBOARD_LAYOUT_SCHEMA, body); + + const updatedUser = await prisma.user.update({ + where: { id: user.id }, + data: { + [data.type]: data.layout, + }, + select: userProperties, + }); + + return updatedUser; + } } diff --git a/apps/api/src/lib/auth/getSessionUser.ts b/apps/api/src/lib/auth/getSessionUser.ts index 51b093b4b..f6d859fa4 100644 --- a/apps/api/src/lib/auth/getSessionUser.ts +++ b/apps/api/src/lib/auth/getSessionUser.ts @@ -47,6 +47,9 @@ export const userProperties = Prisma.validator()({ updatedAt: true, lastSeen: true, developerMode: true, + dispatchLayoutOrder: true, + emsFdLayoutOrder: true, + officerLayoutOrder: true, }); interface GetSessionUserOptions { diff --git a/apps/client/src/components/shared/utility-panel/edit-dashboard-layout-modal.tsx b/apps/client/src/components/shared/utility-panel/edit-dashboard-layout-modal.tsx new file mode 100644 index 000000000..de6b7e752 --- /dev/null +++ b/apps/client/src/components/shared/utility-panel/edit-dashboard-layout-modal.tsx @@ -0,0 +1,156 @@ +import * as React from "react"; +import { DashboardLayoutCardType } from "@snailycad/types"; +import { Modal } from "components/modal/Modal"; +import { useRouter } from "next/router"; +import { useModal } from "state/modalState"; +import { ModalIds } from "types/modal-ids"; +import { ReactSortable } from "react-sortablejs"; +import { ArrowsMove } from "react-bootstrap-icons"; +import { useAuth } from "context/AuthContext"; +import { Button, Loader } from "@snailycad/ui"; +import { useTranslations } from "use-intl"; +import useFetch from "lib/useFetch"; +import { toastMessage } from "lib/toastMessage"; + +const cardTypes: Record<"ems-fd" | "officer" | "dispatch", DashboardLayoutCardType[]> = { + "ems-fd": [ + DashboardLayoutCardType.ACTIVE_CALLS, + DashboardLayoutCardType.ACTIVE_DEPUTIES, + DashboardLayoutCardType.ACTIVE_OFFICERS, + ], + officer: [ + DashboardLayoutCardType.ACTIVE_CALLS, + DashboardLayoutCardType.ACTIVE_BOLOS, + DashboardLayoutCardType.ACTIVE_WARRANTS, + DashboardLayoutCardType.ACTIVE_OFFICERS, + DashboardLayoutCardType.ACTIVE_DEPUTIES, + ], + dispatch: [ + DashboardLayoutCardType.ACTIVE_OFFICERS, + DashboardLayoutCardType.ACTIVE_DEPUTIES, + DashboardLayoutCardType.ACTIVE_CALLS, + DashboardLayoutCardType.ACTIVE_INCIDENTS, + DashboardLayoutCardType.ACTIVE_BOLOS, + ], +}; + +export function EditDashboardLayoutModal() { + const common = useTranslations("Common"); + + const { setUser, user } = useAuth(); + const [sortedList, setSortedList] = React.useState([]); + + const modalState = useModal(); + const router = useRouter(); + const type = getCardsType(router.pathname); + const columnName = getColumnName(type); + const { execute, state } = useFetch(); + + const cardsForType = cardTypes[type]; + + function handleListChange(list: { id: DashboardLayoutCardType }[]) { + setSortedList(list.map((l) => l.id)); + } + + async function handleSave() { + if (sortedList.length <= 0) return; + if (!user || !columnName) return; + + const { json } = await execute({ + method: "PUT", + path: "/user/dashboard-layout", + data: { + type: columnName, + layout: sortedList, + }, + }); + + if (json) { + setUser({ ...user, [columnName]: sortedList }); + toastMessage({ + icon: "success", + title: "Layout Saved", + message: "The layout has been saved", + }); + } + } + + React.useEffect(() => { + const userSortedList = columnName ? user?.[columnName] ?? [] : []; + + const list = cardsForType.sort((a, b) => { + return userSortedList.indexOf(a) - userSortedList.indexOf(b); + }); + + setSortedList(list); + }, [cardsForType, columnName, user]); + + return ( + modalState.closeModal(ModalIds.EditDashboardLayout)} + className="w-[650px]" + > + ({ id: type }))} + className="flex flex-col gap-y-2 mt-5" + > + {sortedList.map((type) => ( +
  • + {type} + + +
  • + ))} +
    + +
    + +
    +
    + ); +} + +function getCardsType(pathname: string) { + if (pathname.includes("/dispatch")) { + return "dispatch"; + } + + if (pathname.includes("/officer")) { + return "officer"; + } + + return "ems-fd"; +} + +function getColumnName(type: "ems-fd" | "officer" | "dispatch") { + switch (type) { + case "ems-fd": { + return "emsFdLayoutOrder"; + } + case "officer": { + return "officerLayoutOrder"; + } + case "dispatch": { + return "dispatchLayoutOrder"; + } + default: { + return null; + } + } +} diff --git a/apps/client/src/components/shared/StatusesArea.tsx b/apps/client/src/components/shared/utility-panel/statuses-area.tsx similarity index 100% rename from apps/client/src/components/shared/StatusesArea.tsx rename to apps/client/src/components/shared/utility-panel/statuses-area.tsx diff --git a/apps/client/src/components/shared/UtilityPanel.tsx b/apps/client/src/components/shared/utility-panel/utility-panel.tsx similarity index 73% rename from apps/client/src/components/shared/UtilityPanel.tsx rename to apps/client/src/components/shared/utility-panel/utility-panel.tsx index 5dc3c9354..010fc04f7 100644 --- a/apps/client/src/components/shared/UtilityPanel.tsx +++ b/apps/client/src/components/shared/utility-panel/utility-panel.tsx @@ -4,8 +4,12 @@ import { useTime } from "hooks/shared/useTime"; import { useFeatureEnabled } from "hooks/useFeatureEnabled"; import { classNames } from "lib/classNames"; import { useTranslations } from "next-intl"; -import { Wifi } from "react-bootstrap-icons"; +import { Grid1x2Fill, Wifi } from "react-bootstrap-icons"; import dynamic from "next/dynamic"; +import { Button } from "@snailycad/ui"; +import { useModal } from "state/modalState"; +import { ModalIds } from "types/modal-ids"; +import { EditDashboardLayoutModal } from "./edit-dashboard-layout-modal"; const DispatchAreaOfPlay = dynamic(async () => { return (await import("components/dispatch/dispatch-area-of-play")).DispatchAreaOfPlay; @@ -22,6 +26,7 @@ export function UtilityPanel({ children, isDispatch }: Props) { const t = useTranslations("Leo"); const { activeDispatchersCount, hasActiveDispatchers } = useActiveDispatchers(); const { ACTIVE_DISPATCHERS } = useFeatureEnabled(); + const modalState = useModal(); return (
    @@ -53,6 +58,19 @@ export function UtilityPanel({ children, isDispatch }: Props) { {children} + +
    + + + +
    ); } diff --git a/apps/client/src/pages/dispatch/index.tsx b/apps/client/src/pages/dispatch/index.tsx index 622c02d92..bb29765e1 100644 --- a/apps/client/src/pages/dispatch/index.tsx +++ b/apps/client/src/pages/dispatch/index.tsx @@ -13,7 +13,7 @@ import { requestAll } from "lib/utils"; import { useSignal100 } from "hooks/shared/useSignal100"; import { usePanicButton } from "hooks/shared/usePanicButton"; import { Title } from "components/shared/Title"; -import { ValueType } from "@snailycad/types"; +import { DashboardLayoutCardType, ValueType } from "@snailycad/types"; import { useFeatureEnabled } from "hooks/useFeatureEnabled"; import { ModalIds } from "types/modal-ids"; import { useModal } from "state/modalState"; @@ -25,13 +25,15 @@ import type { GetBolosData, GetDispatchData, GetEmsFdActiveDeputies, + GetUserData, } from "@snailycad/types/api"; -import { UtilityPanel } from "components/shared/UtilityPanel"; +import { UtilityPanel } from "components/shared/utility-panel/utility-panel"; import { useCall911State } from "state/dispatch/call-911-state"; import { useActiveDispatcherState } from "state/dispatch/active-dispatcher-state"; import { Infofield } from "@snailycad/ui"; import { ActiveOfficers } from "components/dispatch/active-units/officers/active-officers"; import { ActiveDeputies } from "components/dispatch/active-units/deputies/active-deputies"; +import { useAuth } from "context/AuthContext"; const ActiveIncidents = dynamic(async () => { return (await import("components/dispatch/active-incidents/active-incidents")).ActiveIncidents; @@ -64,6 +66,7 @@ export interface DispatchPageProps extends GetDispatchData { activeOfficers: GetActiveOfficersData; calls: Get911CallsData; bolos: GetBolosData; + session: GetUserData | null; } export default function DispatchDashboard(props: DispatchPageProps) { @@ -90,6 +93,8 @@ export default function DispatchDashboard(props: DispatchPageProps) { const set911Calls = useCall911State((state) => state.setCalls); const t = useTranslations("Leo"); const { CALLS_911, ACTIVE_INCIDENTS } = useFeatureEnabled(); + const { user } = useAuth(); + const session = user ?? props.session; React.useEffect(() => { set911Calls(props.calls.calls); @@ -105,35 +110,47 @@ export default function DispatchDashboard(props: DispatchPageProps) { const cards = [ { + type: DashboardLayoutCardType.ACTIVE_OFFICERS, isEnabled: true, children: , }, { + type: DashboardLayoutCardType.ACTIVE_DEPUTIES, isEnabled: true, children: , }, { + type: DashboardLayoutCardType.ACTIVE_CALLS, isEnabled: CALLS_911, children: , }, { + type: DashboardLayoutCardType.ACTIVE_INCIDENTS, isEnabled: ACTIVE_INCIDENTS, children: , }, { + type: DashboardLayoutCardType.ACTIVE_BOLOS, isEnabled: true, children: , }, ]; + const layoutOrder = session?.dispatchLayoutOrder ?? []; + const sortedCards = cards.sort((a, b) => { + return layoutOrder.indexOf(a.type) - layoutOrder.indexOf(b.type); + }); + return ( {t("dispatch")} - {cards.map((card) => (card.isEnabled ? card.children : null))} + {sortedCards.map((card) => + card.isEnabled ? {card.children} : null, + )} diff --git a/apps/client/src/pages/ems-fd/index.tsx b/apps/client/src/pages/ems-fd/index.tsx index af39b55d9..ac15bfcf9 100644 --- a/apps/client/src/pages/ems-fd/index.tsx +++ b/apps/client/src/pages/ems-fd/index.tsx @@ -7,7 +7,7 @@ import type { GetServerSideProps } from "next"; import { getSessionUser } from "lib/auth"; import { getTranslations } from "lib/getTranslation"; import { useTranslations } from "use-intl"; -import { StatusesArea } from "components/shared/StatusesArea"; +import { StatusesArea } from "components/shared/utility-panel/statuses-area"; import { useEmsFdState } from "state/ems-fd-state"; import { useDispatchState } from "state/dispatch/dispatch-state"; import { requestAll } from "lib/utils"; @@ -15,8 +15,8 @@ import { ActiveOfficers } from "components/dispatch/active-units/officers/active import { ActiveDeputies } from "components/dispatch/active-units/deputies/active-deputies"; import { useSignal100 } from "hooks/shared/useSignal100"; import { Title } from "components/shared/Title"; -import { UtilityPanel } from "components/shared/UtilityPanel"; -import { ActiveToneType, ValueType } from "@snailycad/types"; +import { UtilityPanel } from "components/shared/utility-panel/utility-panel"; +import { ActiveToneType, DashboardLayoutCardType, ValueType } from "@snailycad/types"; import { defaultPermissions, Permissions } from "@snailycad/permissions"; import { usePanicButton } from "hooks/shared/usePanicButton"; import { useTones } from "hooks/global/use-tones"; @@ -26,18 +26,21 @@ import type { GetActiveOfficersData, GetEmsFdActiveDeputies, GetEmsFdActiveDeputy, + GetUserData, } from "@snailycad/types/api"; import { useCall911State } from "state/dispatch/call-911-state"; import { usePermission } from "hooks/usePermission"; import { useFeatureEnabled } from "hooks/useFeatureEnabled"; import { useModal } from "state/modalState"; import { ModalIds } from "types/modal-ids"; +import { useAuth } from "context/AuthContext"; interface Props { activeDeputy: GetEmsFdActiveDeputy | null; activeDeputies: GetEmsFdActiveDeputies; activeOfficers: GetActiveOfficersData; calls: Get911CallsData; + session: GetUserData | null; } const NotepadModal = dynamic( @@ -80,6 +83,7 @@ export default function EmsFDDashboard({ calls, activeOfficers, activeDeputies, + session: _session, }: Props) { useLoadValuesClientSide({ valueTypes: [ @@ -99,6 +103,9 @@ export default function EmsFDDashboard({ setActiveDeputies: state.setActiveDeputies, })); const set911Calls = useCall911State((state) => state.setCalls); + const t = useTranslations(); + const { user } = useAuth(); + const session = user ?? _session; React.useEffect(() => { setActiveDeputy(activeDeputy); @@ -108,7 +115,28 @@ export default function EmsFDDashboard({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeDeputies, activeDeputy, calls]); - const t = useTranslations(); + const cards = [ + { + type: DashboardLayoutCardType.ACTIVE_CALLS, + isEnabled: CALLS_911, + children: , + }, + { + type: DashboardLayoutCardType.ACTIVE_DEPUTIES, + isEnabled: true, + children: , + }, + { + type: DashboardLayoutCardType.ACTIVE_OFFICERS, + isEnabled: true, + children: , + }, + ]; + + const layoutOrder = session?.emsFdLayoutOrder ?? []; + const sortedCards = cards.sort((a, b) => { + return layoutOrder.indexOf(a.type) - layoutOrder.indexOf(b.type); + }); return ( @@ -116,9 +144,9 @@ export default function EmsFDDashboard({ - {CALLS_911 ? : null} - - + {sortedCards.map((card) => + card.isEnabled ? {card.children} : null, + )} diff --git a/apps/client/src/pages/officer/index.tsx b/apps/client/src/pages/officer/index.tsx index 0c32f92b7..2b35f4b1f 100644 --- a/apps/client/src/pages/officer/index.tsx +++ b/apps/client/src/pages/officer/index.tsx @@ -2,12 +2,18 @@ import * as React from "react"; import dynamic from "next/dynamic"; import { useTranslations } from "use-intl"; import { Layout } from "components/Layout"; -import { StatusesArea } from "components/shared/StatusesArea"; +import { StatusesArea } from "components/shared/utility-panel/statuses-area"; import { getSessionUser } from "lib/auth"; import { getTranslations } from "lib/getTranslation"; import type { GetServerSideProps } from "next"; import { useLeoState } from "state/leo-state"; -import { ActiveToneType, Record, RecordType, ValueType } from "@snailycad/types"; +import { + ActiveToneType, + DashboardLayoutCardType, + Record, + RecordType, + ValueType, +} from "@snailycad/types"; import { ActiveCalls } from "components/dispatch/active-calls/active-calls"; import { useDispatchState } from "state/dispatch/dispatch-state"; import { ModalButtons } from "components/leo/ModalButtons"; @@ -19,7 +25,7 @@ import { ActiveWarrants } from "components/leo/active-warrants/active-warrants"; import { useSignal100 } from "hooks/shared/useSignal100"; import { usePanicButton } from "hooks/shared/usePanicButton"; import { Title } from "components/shared/Title"; -import { UtilityPanel } from "components/shared/UtilityPanel"; +import { UtilityPanel } from "components/shared/utility-panel/utility-panel"; import { useModal } from "state/modalState"; import { ModalIds } from "types/modal-ids"; import { defaultPermissions, Permissions } from "@snailycad/permissions"; @@ -33,10 +39,12 @@ import type { GetActiveOfficersData, GetBolosData, GetEmsFdActiveDeputies, + GetUserData, } from "@snailycad/types/api"; import { CreateWarrantModal } from "components/leo/modals/CreateWarrantModal"; import { useCall911State } from "state/dispatch/call-911-state"; import { usePermission } from "hooks/usePermission"; +import { useAuth } from "context/AuthContext"; const Modals = { CreateWarrantModal: dynamic( @@ -112,6 +120,7 @@ interface Props { calls: Get911CallsData; bolos: GetBolosData; activeDeputies: GetEmsFdActiveDeputies; + session: GetUserData | null; } export default function OfficerDashboard({ @@ -120,6 +129,7 @@ export default function OfficerDashboard({ activeOfficer, activeOfficers, activeDeputies, + session: _session, }: Props) { useLoadValuesClientSide({ valueTypes: [ @@ -145,6 +155,8 @@ export default function OfficerDashboard({ const set911Calls = useCall911State((state) => state.setCalls); const t = useTranslations("Leo"); const { ACTIVE_WARRANTS, CALLS_911 } = useFeatureEnabled(); + const { user } = useAuth(); + const session = user ?? _session; React.useEffect(() => { setActiveOfficer(activeOfficer); @@ -158,17 +170,48 @@ export default function OfficerDashboard({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [bolos, calls, activeOfficers, activeDeputies, activeOfficer]); + const cards = [ + { + type: DashboardLayoutCardType.ACTIVE_OFFICERS, + isEnabled: true, + children: , + }, + { + type: DashboardLayoutCardType.ACTIVE_DEPUTIES, + isEnabled: true, + children: , + }, + { + type: DashboardLayoutCardType.ACTIVE_CALLS, + isEnabled: CALLS_911, + children: , + }, + { + type: DashboardLayoutCardType.ACTIVE_WARRANTS, + isEnabled: ACTIVE_WARRANTS, + children: , + }, + { + type: DashboardLayoutCardType.ACTIVE_BOLOS, + isEnabled: true, + children: , + }, + ]; + + const layoutOrder = session?.officerLayoutOrder ?? []; + const sortedCards = cards.sort((a, b) => { + return layoutOrder.indexOf(a.type) - layoutOrder.indexOf(b.type); + }); + return ( {t("officer")} - {CALLS_911 ? : null} - - {ACTIVE_WARRANTS ? : null} - - + {sortedCards.map((card) => + card.isEnabled ? {card.children} : null, + )} diff --git a/apps/client/src/types/modal-ids.ts b/apps/client/src/types/modal-ids.ts index 714b427a2..2093a1faa 100644 --- a/apps/client/src/types/modal-ids.ts +++ b/apps/client/src/types/modal-ids.ts @@ -56,6 +56,7 @@ export const enum ModalIds { Manage911Call = "Manage911CallModal", MergeUnit = "MergeUnitModal", SelectMapServer = "SelectMapServerModal", + EditDashboardLayout = "EditDashboardLayoutModal", ManageVehicleFlags = "ManageVehicleFlagsModal", ManageCitizenFlags = "ManageCitizenFlagsModal", diff --git a/packages/schemas/src/auth.ts b/packages/schemas/src/auth.ts index 92bcaca32..a2d89baf6 100644 --- a/packages/schemas/src/auth.ts +++ b/packages/schemas/src/auth.ts @@ -36,3 +36,8 @@ export const TEMP_PASSWORD_SCHEMA = z.object({ newPassword: z.string().min(8).max(255), confirmPassword: z.string().min(8).max(255), }); + +export const DASHBOARD_LAYOUT_SCHEMA = z.object({ + type: z.string(), + layout: z.array(z.string()), +}); diff --git a/packages/types/src/enums.ts b/packages/types/src/enums.ts index 084407c21..fc22676f7 100644 --- a/packages/types/src/enums.ts +++ b/packages/types/src/enums.ts @@ -326,3 +326,15 @@ export const CourthouseType = { } as const; export type CourthouseType = (typeof CourthouseType)[keyof typeof CourthouseType]; + +export const DashboardLayoutCardType = { + ACTIVE_CALLS: "ACTIVE_CALLS", + ACTIVE_BOLOS: "ACTIVE_BOLOS", + ACTIVE_WARRANTS: "ACTIVE_WARRANTS", + ACTIVE_OFFICERS: "ACTIVE_OFFICERS", + ACTIVE_DEPUTIES: "ACTIVE_DEPUTIES", + ACTIVE_INCIDENTS: "ACTIVE_INCIDENTS", +} as const; + +export type DashboardLayoutCardType = + (typeof DashboardLayoutCardType)[keyof typeof DashboardLayoutCardType]; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index ff11e116d..6d35373dd 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -117,7 +117,10 @@ type UserPicks = | "roles" | "lastSeen" | "hasPassword" - | "developerMode"; + | "developerMode" + | "dispatchLayoutOrder" + | "emsFdLayoutOrder" + | "officerLayoutOrder"; export type User = Pick< Prisma.User & {