diff --git a/apps/dispatch-server/index.ts b/apps/dispatch-server/index.ts index 6ed92107..c75d907f 100644 --- a/apps/dispatch-server/index.ts +++ b/apps/dispatch-server/index.ts @@ -25,6 +25,7 @@ io.on("connection", (socket) => { }); app.use(cors()); +app.use(express.json()); app.use(router); server.listen(process.env.PORT, () => { diff --git a/apps/dispatch-server/routes/mission.ts b/apps/dispatch-server/routes/mission.ts new file mode 100644 index 00000000..a3356188 --- /dev/null +++ b/apps/dispatch-server/routes/mission.ts @@ -0,0 +1,79 @@ +import { prisma } from "@repo/db"; +import { Router } from "express"; + +const router = Router(); + +// Get all missions +router.post("/", async (req, res) => { + try { + const filter = req.body?.filter || {}; + const missions = await prisma.mission.findMany({ + where: filter, + }); + res.json(missions); + } catch (error) { + console.error(error); + res.status(500).json({ error: "Failed to fetch missions" }); + } +}); + +// Get a single mission by ID +router.get("/:id", async (req, res) => { + const { id } = req.params; + try { + const mission = await prisma.mission.findUnique({ + where: { id: Number(id) }, + }); + if (mission) { + res.json(mission); + } else { + res.status(404).json({ error: "Mission not found" }); + } + } catch (error) { + console.error(error); + res.status(500).json({ error: "Failed to fetch mission" }); + } +}); + +// Create a new mission +router.put("/", async (req, res) => { + try { + const newMission = await prisma.mission.create({ + data: req.body, + }); + res.status(201).json(newMission); + } catch (error) { + res.status(500).json({ error: "Failed to create mission" }); + } +}); + +// Update a mission by ID +router.patch("/:id", async (req, res) => { + const { id } = req.params; + try { + const updatedMission = await prisma.mission.update({ + where: { id: Number(id) }, + data: req.body, + }); + res.json(updatedMission); + } catch (error) { + console.error(error); + res.status(500).json({ error: "Failed to update mission" }); + } +}); + +// Delete a mission by ID +router.delete("/:id", async (req, res) => { + const { id } = req.params; + try { + await prisma.mission.delete({ + where: { id: Number(id) }, + }); + res.status(204).send(); + } catch (error) { + console.error(error); + res.status(500).json({ error: "Failed to delete mission" }); + } +}); + +export default router; diff --git a/apps/dispatch-server/routes/router.ts b/apps/dispatch-server/routes/router.ts index 61170c33..d5d83ffd 100644 --- a/apps/dispatch-server/routes/router.ts +++ b/apps/dispatch-server/routes/router.ts @@ -1,10 +1,12 @@ import { Router } from "express"; import livekitRouter from "./livekit"; import dispatcherRotuer from "./dispatcher"; +import missionRouter from "./mission"; const router = Router(); router.use("/livekit", livekitRouter); router.use("/dispatcher", dispatcherRotuer); +router.use("/mission", missionRouter); export default router; diff --git a/apps/dispatch/app/_components/SmartPopup.tsx b/apps/dispatch/app/_components/SmartPopup.tsx index 193d85e9..b0418bb2 100644 --- a/apps/dispatch/app/_components/SmartPopup.tsx +++ b/apps/dispatch/app/_components/SmartPopup.tsx @@ -16,7 +16,7 @@ export const useSmartPopup = () => { return context; }; -export const useConflict = (id: string, mode: "popup" | "marker") => { +export const calculateAnchor = (id: string, mode: "popup" | "marker") => { const otherMarkers = document.querySelectorAll(".map-collision"); // get markers and check if they are overlapping const ownMarker = @@ -105,7 +105,7 @@ export const SmartPopup = ( >("topleft"); const handleConflict = () => { - const newAnchor = useConflict(id, "popup"); + const newAnchor = calculateAnchor(id, "popup"); setAnchor(newAnchor); }; diff --git a/apps/dispatch/app/_store/mapStore.ts b/apps/dispatch/app/_store/mapStore.ts index 46a75180..20b8a6fa 100644 --- a/apps/dispatch/app/_store/mapStore.ts +++ b/apps/dispatch/app/_store/mapStore.ts @@ -11,12 +11,12 @@ interface MapStore { zoom: number; }; openMissionMarker: { - id: string; - tab: "home" | ""; + id: number; + tab: "home" | "details" | "chat" | "log"; }[]; setOpenMissionMarker: (mission: { open: MapStore["openMissionMarker"]; - close: string[]; + close: number[]; }) => void; openAircraftMarker: { id: string; @@ -58,22 +58,16 @@ interface MapStore { aircraftId: string, tab: MapStore["aircraftTabs"][string], ) => void; - missionTabs: { - [missionId: string]: "home" | "details" | "chat"; - }; - setMissionTab: ( - missionId: string, - tab: MapStore["missionTabs"][string], - ) => void; } export const useMapStore = create((set, get) => ({ openMissionMarker: [], setOpenMissionMarker: ({ open, close }) => { - set((state) => ({ - openMissionMarker: [...state.openMissionMarker, ...open].filter( - (marker) => !close.includes(marker.id), - ), + const oldMarkers = get().openMissionMarker.filter( + (m) => !close.includes(m.id) && !open.find((o) => o.id === m.id), + ); + set(() => ({ + openMissionMarker: [...oldMarkers, ...open], })); }, openAircraftMarker: [], @@ -81,7 +75,7 @@ export const useMapStore = create((set, get) => ({ const oldMarkers = get().openAircraftMarker.filter( (m) => !close.includes(m.id) && !open.find((o) => o.id === m.id), ); - + console.log("oldMarkers", oldMarkers, open); set(() => ({ openAircraftMarker: [...oldMarkers, ...open], })); @@ -114,11 +108,4 @@ export const useMapStore = create((set, get) => ({ }, })), missionTabs: {}, - setMissionTab: (missionId, tab) => - set((state) => ({ - missionTabs: { - ...state.missionTabs, - [missionId]: tab, - }, - })), })); diff --git a/apps/dispatch/app/_store/missionsStore.ts b/apps/dispatch/app/_store/missionsStore.ts index fd86001a..95dc59f8 100644 --- a/apps/dispatch/app/_store/missionsStore.ts +++ b/apps/dispatch/app/_store/missionsStore.ts @@ -1,46 +1,20 @@ import { Mission, Prisma } from "@repo/db"; import { MissionOptionalDefaults } from "@repo/db/zod"; +import { serverApi } from "helpers/axios"; import { create } from "zustand"; +import { toast } from "react-hot-toast"; interface MissionStore { missions: Mission[]; setMissions: (missions: Mission[]) => void; getMissions: () => Promise; createMission: (mission: MissionOptionalDefaults) => Promise; - setMission: (mission: Mission) => void; + deleteMission: (id: number) => Promise; + editMission: (id: number, mission: Partial) => Promise; } export const useMissionsStore = create((set) => ({ - missions: [ - { - id: 1, - type: "primär", - state: "draft", - addressCity: "Berlin", - addressStreet: "Alexanderplatz", - addressZip: "10178", - addressOSMways: [], - missionAdditionalInfo: "", - addressLat: 52.520008, - addressLng: 13.404954, - missionKeywordName: "TestKName", - missionKeywordCategory: "TestKCategory", - missionKeywordAbbreviation: "TestKAbbreviation", - missionPatientInfo: "TestPatientInfo", - missionStationIds: [], - createdUserId: "1", - hpgMissionString: null, - missionLog: [], - missionStationUserIds: [], - hpgLocationLat: 52.520008, - hpgLocationLng: 13.404954, - hpgAmbulanceState: null, - hpgFireEngineState: null, - hpgPoliceState: null, - createdAt: new Date(), - updatedAt: new Date(), - }, - ], + missions: [], setMissions: (missions) => set({ missions }), createMission: async (mission) => { const res = await fetch("/api/mission", { @@ -55,41 +29,46 @@ export const useMissionsStore = create((set) => ({ set((state) => ({ missions: [...state.missions, data] })); return data; }, + editMission: async (id, mission) => { + const { data, status } = await serverApi.patch( + `/mission/${id}`, + mission, + ); + if (status.toString().startsWith("2") && data) { + set((state) => ({ + missions: state.missions.map((m) => (m.id === id ? data : m)), + })); + toast.success("Mission updated successfully"); + } else { + toast.error("Failed to update mission"); + } + }, + deleteMission: async (id) => { + serverApi + .delete(`/mission/${id}`) + .then((res) => { + if (res.status.toString().startsWith("2")) { + set((state) => ({ + missions: state.missions.filter((mission) => mission.id !== id), + })); + toast.success("Mission deleted successfully"); + } else { + toast.error("Failed to delete mission"); + } + }) + .catch((err) => { + toast.error("Failed to delete mission"); + }); + }, getMissions: async () => { - const res = await fetch("/api/mission", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - OR: [ - { - state: "draft", - }, - { - state: "running", - }, - ], - } as Prisma.MissionWhereInput), + const { data } = await serverApi.post("/mission", { + filter: { + OR: [{ state: "draft" }, { state: "running" }], + } as Prisma.MissionWhereInput, }); - if (!res.ok) return undefined; - const data = await res.json(); - //set({ missions: data }); + set({ missions: data }); return undefined; }, - setMission: (mission) => - set((state) => { - const existingMissionIndex = state.missions.findIndex( - (m) => m.id === mission.id, - ); - if (existingMissionIndex !== -1) { - const updatedMissions = [...state.missions]; - updatedMissions[existingMissionIndex] = mission; - return { missions: updatedMissions }; - } else { - return { missions: [...state.missions, mission] }; - } - }), })); useMissionsStore diff --git a/apps/dispatch/app/dispatch/_components/map/AircraftMarker.tsx b/apps/dispatch/app/dispatch/_components/map/AircraftMarker.tsx index c3c76132..41219f38 100644 --- a/apps/dispatch/app/dispatch/_components/map/AircraftMarker.tsx +++ b/apps/dispatch/app/dispatch/_components/map/AircraftMarker.tsx @@ -17,7 +17,11 @@ import { MessageSquareText, Minimize2, } from "lucide-react"; -import { SmartPopup, useConflict, useSmartPopup } from "_components/SmartPopup"; +import { + SmartPopup, + calculateAnchor, + useSmartPopup, +} from "_components/SmartPopup"; import FMSStatusHistory, { FMSStatusSelector, RettungsmittelTab, @@ -249,24 +253,25 @@ const AircraftMarker = ({ aircraft }: { aircraft: Aircraft }) => { }); } }; - markerRef.current?.on("click", handleClick); + const marker = markerRef.current; + marker?.on("click", handleClick); return () => { - markerRef.current?.off("click", handleClick); + marker?.off("click", handleClick); }; - }, [markerRef.current, aircraft.id, openAircraftMarker]); + }, [aircraft.id, openAircraftMarker, setOpenAircraftMarker]); const [anchor, setAnchor] = useState< "topleft" | "topright" | "bottomleft" | "bottomright" >("topleft"); - const handleConflict = () => { - const newAnchor = useConflict(aircraft.id, "marker"); + const handleConflict = useCallback(() => { + const newAnchor = calculateAnchor(aircraft.id, "marker"); setAnchor(newAnchor); - }; + }, [aircraft.id]); useEffect(() => { handleConflict(); - }, [aircrafts, openAircraftMarker]); + }, [aircrafts, openAircraftMarker, handleConflict]); useEffect(() => {}); @@ -279,7 +284,7 @@ const AircraftMarker = ({ aircraft }: { aircraft: Aircraft }) => { return () => { map.off("zoom", handleConflict); }; - }, [map, openAircraftMarker]); + }, [map, openAircraftMarker, handleConflict]); const getMarkerHTML = ( aircraft: Aircraft, diff --git a/apps/dispatch/app/dispatch/_components/map/MissionMarkers.tsx b/apps/dispatch/app/dispatch/_components/map/MissionMarkers.tsx index 2b9d3f6f..a8ee23cf 100644 --- a/apps/dispatch/app/dispatch/_components/map/MissionMarkers.tsx +++ b/apps/dispatch/app/dispatch/_components/map/MissionMarkers.tsx @@ -12,7 +12,11 @@ import { } from "react"; import { cn } from "helpers/cn"; import { ClipboardList, Cross, House, Minimize2, Route } from "lucide-react"; -import { SmartPopup, useConflict, useSmartPopup } from "_components/SmartPopup"; +import { + calculateAnchor, + SmartPopup, + useSmartPopup, +} from "_components/SmartPopup"; import { Mission, MissionState } from "@repo/db"; import Einsatzdetails, { FMSStatusHistory, @@ -31,36 +35,42 @@ export const MISSION_STATUS_TEXT_COLORS: Record = { }; const MissionPopupContent = ({ mission }: { mission: Mission }) => { - const setMissionTab = useMapStore((state) => state.setMissionTab); + const setMissionMarker = useMapStore((state) => state.setOpenMissionMarker); const currentTab = useMapStore( (state) => - state.missionTabs[mission.id] || - ("home" as "home" | "details" | "chat" | "log"), + state.openMissionMarker.find((m) => m.id === mission.id)?.tab ?? "home", ); const handleTabChange = useCallback( (tab: "home" | "details" | "chat" | "log") => { - if (currentTab !== tab) { - setMissionTab(mission.id, tab); - } + console.log("handleTabChange", tab); + setMissionMarker({ + open: [ + { + id: mission.id, + tab, + }, + ], + close: [], + }); }, - [currentTab, mission.id, setMissionTab], + [setMissionMarker, mission.id], ); const renderTabContent = useMemo(() => { switch (currentTab) { case "home": - return ; + return ; case "details": return
Details Content
; case "chat": return
Chat Content
; case "log": - return ; + return ; default: return Error; } - }, [currentTab]); + }, [currentTab, mission]); const setOpenMissionMarker = useMapStore( (state) => state.setOpenMissionMarker, @@ -169,7 +179,6 @@ const MissionPopupContent = ({ mission }: { mission: Mission }) => { }; const MissionMarker = ({ mission }: { mission: Mission }) => { - const missions = useMissionsStore((state) => state.missions); const map = useMap(); const markerRef = useRef(null); const popupRef = useRef(null); @@ -198,24 +207,25 @@ const MissionMarker = ({ mission }: { mission: Mission }) => { }); } }; - markerRef.current?.on("click", handleClick); + const markerCopy = markerRef.current; + markerCopy?.on("click", handleClick); return () => { - markerRef.current?.off("click", handleClick); + markerCopy?.off("click", handleClick); }; - }, [markerRef.current, mission.id, openMissionMarker]); + }, [mission.id, openMissionMarker, setOpenMissionMarker]); const [anchor, setAnchor] = useState< "topleft" | "topright" | "bottomleft" | "bottomright" >("topleft"); - const handleConflict = () => { - const newAnchor = useConflict(mission.id, "marker"); + const handleConflict = useCallback(() => { + const newAnchor = calculateAnchor(mission.id.toString(), "marker"); setAnchor(newAnchor); - }; + }, [mission.id]); useEffect(() => { handleConflict(); - }, [missions, openMissionMarker]); + }, [handleConflict]); useEffect(() => {}); @@ -228,7 +238,7 @@ const MissionMarker = ({ mission }: { mission: Mission }) => { return () => { map.off("zoom", handleConflict); }; - }, [map, openMissionMarker]); + }, [map, openMissionMarker, handleConflict]); const getMarkerHTML = ( mission: Mission, @@ -258,7 +268,7 @@ const MissionMarker = ({ mission }: { mission: Mission }) => { " > - ${mission.missionKeywordCategory} + ${mission.missionKeywordAbbreviation} ${mission.missionKeywordName}
{ /> {openMissionMarker.some((m) => m.id === mission.id) && ( { +const Einsatzdetails = ({ mission }: { mission: Mission }) => { + const { deleteMission } = useMissionsStore((state) => state); + const { setMissionFormValues } = usePannelStore((state) => state); return (

@@ -22,66 +34,76 @@ const Einsatzdetails = () => {

  • - AB_ATMUNG + {mission.missionKeywordCategory}
  • - Gleichbleibende Atembeschwerden + {mission.missionKeywordName}
  • - __202504161 + __{mission.id}

- 52.520008, 13.404954 + {mission.addressLat} {mission.addressLng}

- Alexanderplatz + {mission.addressStreet}

- Berlin, 10178 + {mission.addressZip} {mission.addressCity}

-
- - + {mission.state === "draft" && ( + + )}
); }; -const FMSStatusHistory = () => { +const FMSStatusHistory = ({ mission }: { mission: Mission }) => { + const session = useSession(); const [isAddingNote, setIsAddingNote] = useState(false); + const { editMission } = useMissionsStore((state) => state); const [note, setNote] = useState(""); - const dummyData = [ - { - status: "N", - time: "12:39", - unit: "RTH defekt, verbleibt an Einsatzort", - unitshort: "", - }, - { status: "1", time: "12:38", unit: "Christoph 31", unitshort: "CHX31" }, - { status: "7", time: "12:34", unit: "RTW", unitshort: "RTW" }, - { status: "4", time: "12:11", unit: "Christoph 31", unitshort: "CHX31" }, - { status: "4", time: "12:09", unit: "RTW", unitshort: "RTW" }, - { status: "3", time: "12:01", unit: "Christoph 31", unitshort: "CHX31" }, - { status: "3", time: "12:00", unit: "RTW", unitshort: "RTW" }, - ]; - + if (!session.data?.user) return null; return (
@@ -99,16 +121,32 @@ const FMSStatusHistory = () => { setNote(e.target.value)} />
    - {dummyData.map((entry, index) => ( -
  • - {entry.time} - - {entry.status} - - {entry.unit} -
  • - ))} + {(mission.missionLog as unknown as MissionLog[]).map((entry, index) => { + if (entry.type === "station-log") + return ( +
  • + {entry.timeStamp} + + {entry.data.newFMSstatus} + + + {entry.data.station.bosCallsign} + +
  • + ); + if (entry.type === "message-log") + return ( +
  • + + {new Date(entry.timeStamp).toLocaleTimeString()} + + + {entry.data.user.firstname} {entry.data.user.lastname} + + {entry.data.message} +
  • + ); + + return null; + })}
); diff --git a/apps/dispatch/app/dispatch/_components/pannel/MissionForm.tsx b/apps/dispatch/app/dispatch/_components/pannel/MissionForm.tsx index a44138e5..e98de916 100644 --- a/apps/dispatch/app/dispatch/_components/pannel/MissionForm.tsx +++ b/apps/dispatch/app/dispatch/_components/pannel/MissionForm.tsx @@ -24,10 +24,14 @@ export const MissionForm = () => { createdUserId: session.data?.user.id, type: "primär", addressOSMways: [], + missionKeywordAbbreviation: "", + missionKeywordCategory: "", + missionKeywordName: "", hpgFireEngineState: null, hpgAmbulanceState: null, hpgPoliceState: null, hpgMissionString: null, + missionLog: [], }) as Partial, [session.data?.user.id], @@ -54,10 +58,27 @@ export const MissionForm = () => { useEffect(() => { if (missionFormValues) { - form.reset({ - ...missionFormValues, - ...defaultFormValues, - }); + if (Object.keys(missionFormValues).length === 0) { + console.log("resetting form"); + form.reset(/* { + addressStreet: "", + addressCity: "", + addressZip: "", + missionStationIds: [], + missionKeywordName: "", + missionKeywordAbbreviation: "", + missionKeywordCategory: "", + + ...defaultFormValues, + } */); + return; + } + for (const key in missionFormValues) { + form.setValue( + key as keyof MissionOptionalDefaults, + missionFormValues[key as keyof MissionOptionalDefaults], + ); + } } }, [missionFormValues, form, defaultFormValues]); @@ -73,12 +94,6 @@ export const MissionForm = () => { }); }, []); - console.log( - form.watch("missionKeywordName"), - form.watch("missionKeywordAbbreviation"), - ); - console.log(form.formState.errors); - return (
{/* Koorinaten Section */} @@ -98,6 +113,12 @@ export const MissionForm = () => { disabled />
+ {(form.formState.errors.addressLat || + form.formState.errors.addressLng) && ( +

+ Bitte wähle eine Postion übder das Context-Menu über der Karte aus. +

+ )}
{/* Adresse Section */} @@ -177,9 +198,9 @@ export const MissionForm = () => { form.setValue("missionKeywordAbbreviation", ""); form.setValue("hpgMissionString", ""); }} - defaultValue="default" + defaultValue="" > - {Object.keys(KEYWORD_CATEGORY).map((use) => ( @@ -288,7 +309,23 @@ export const MissionForm = () => { > Alarmieren -
diff --git a/apps/dispatch/app/dispatch/_components/pannel/Pannel.tsx b/apps/dispatch/app/dispatch/_components/pannel/Pannel.tsx index 4347bec8..b90b143b 100644 --- a/apps/dispatch/app/dispatch/_components/pannel/Pannel.tsx +++ b/apps/dispatch/app/dispatch/_components/pannel/Pannel.tsx @@ -1,10 +1,11 @@ import { usePannelStore } from "_store/pannelStore"; import { cn } from "helpers/cn"; import { MissionForm } from "./MissionForm"; -import { Rss } from "lucide-react"; +import { Rss, Trash2Icon } from "lucide-react"; export const Pannel = () => { - const { isOpen, setOpen } = usePannelStore(); + const { setOpen, setMissionFormValues } = usePannelStore(); + return (
@@ -12,9 +13,17 @@ export const Pannel = () => {

Neuer Einsatz

- +
+ + +
diff --git a/apps/dispatch/app/helpers/axios.ts b/apps/dispatch/app/helpers/axios.ts new file mode 100644 index 00000000..07caa512 --- /dev/null +++ b/apps/dispatch/app/helpers/axios.ts @@ -0,0 +1,9 @@ +import axios from "axios"; + +export const serverApi = axios.create({ + baseURL: process.env.NEXT_PUBLIC_DISPATCH_SERVER_URL, + timeout: 10000, + headers: { + "Content-Type": "application/json", + }, +}); diff --git a/apps/dispatch/package.json b/apps/dispatch/package.json index 0b0e6824..fa61cc31 100644 --- a/apps/dispatch/package.json +++ b/apps/dispatch/package.json @@ -18,6 +18,7 @@ "@repo/ui": "*", "@tailwindcss/postcss": "^4.0.14", "@tanstack/react-query": "^5.69.0", + "axios": "^1.9.0", "leaflet": "^1.9.4", "livekit-client": "^2.9.7", "livekit-server-sdk": "^2.10.2", diff --git a/grafana/grafana.db b/grafana/grafana.db index 9c2042c9..4139ebd5 100644 Binary files a/grafana/grafana.db and b/grafana/grafana.db differ diff --git a/package-lock.json b/package-lock.json index 7d4e8333..d8c1419a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "@repo/ui": "*", "@tailwindcss/postcss": "^4.0.14", "@tanstack/react-query": "^5.69.0", + "axios": "^1.9.0", "leaflet": "^1.9.4", "livekit-client": "^2.9.7", "livekit-server-sdk": "^2.10.2", @@ -4337,9 +4338,9 @@ } }, "node_modules/axios": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", - "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", diff --git a/packages/database/prisma/json/MissionVehicleLog.ts b/packages/database/prisma/json/MissionVehicleLog.ts index 60c3ebeb..8524bf79 100644 --- a/packages/database/prisma/json/MissionVehicleLog.ts +++ b/packages/database/prisma/json/MissionVehicleLog.ts @@ -1,8 +1,10 @@ import { Station, User } from "../../generated/client"; +import { PublicUser } from "./User"; export interface MissionStationLog { type: "station-log"; auto: true; + timeStamp: string; data: { stationId: string; oldFMSstatus: string; @@ -15,9 +17,11 @@ export interface MissionStationLog { export interface MissionMessageLog { type: "message-log"; auto: false; + timeStamp: string; + data: { message: string; - user: User; + user: PublicUser; }; } diff --git a/packages/database/prisma/json/User.ts b/packages/database/prisma/json/User.ts new file mode 100644 index 00000000..dbb12bcb --- /dev/null +++ b/packages/database/prisma/json/User.ts @@ -0,0 +1,20 @@ +import { User } from "../../generated/client"; + +export interface PublicUser { + firstname: string; + lastname: string; + publicId: string; + badges: string[]; +} + +export const getPublicUser = (user: User): PublicUser => { + return { + firstname: user.firstname, + lastname: user.lastname + .split(" ") + .map((part) => `${part[0]}.`) + .join(" "), // Only take the first part of the name + publicId: user.publicId, + badges: user.badges, + }; +}; diff --git a/packages/database/prisma/json/index.ts b/packages/database/prisma/json/index.ts index dcdbeb9a..2f99fcc5 100644 --- a/packages/database/prisma/json/index.ts +++ b/packages/database/prisma/json/index.ts @@ -1 +1,3 @@ -export type { ParticipantLog } from "./ParticipantLog"; +export * from "./ParticipantLog"; +export * from "./MissionVehicleLog"; +export * from "./User"; diff --git a/packages/ui/src/components/Input.tsx b/packages/ui/src/components/Input.tsx new file mode 100644 index 00000000..f104a49b --- /dev/null +++ b/packages/ui/src/components/Input.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { + FieldValues, + Path, + RegisterOptions, + UseFormReturn, +} from "react-hook-form"; +import { cn } from "../helper/cn"; + +interface InputProps + extends Omit, "form"> { + name: Path; + form: UseFormReturn; + formOptions?: RegisterOptions; + label?: string; + placeholder?: string; +} + +export const Input = ({ + name, + label = name, + placeholder = label, + form, + formOptions, + className, + ...inputProps +}: InputProps) => { + return ( + + ); +}; diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts new file mode 100644 index 00000000..be66d766 --- /dev/null +++ b/packages/ui/src/components/index.ts @@ -0,0 +1 @@ +export * from "./Input"; diff --git a/packages/ui/src/helper/cn.ts b/packages/ui/src/helper/cn.ts new file mode 100644 index 00000000..737e5944 --- /dev/null +++ b/packages/ui/src/helper/cn.ts @@ -0,0 +1,6 @@ +import clsx, { ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export const cn = (...inputs: ClassValue[]) => { + return twMerge(clsx(inputs)); +}; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 00924967..5b89e88a 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1 +1,2 @@ export * from "./helper/event"; +export * from "./components";