Merge pull request #48 from VAR-Virtual-Air-Rescue/eslint
Fix NextJS app ESlint errors
This commit was merged in pull request #48.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { HpgState } from "@repo/db";
|
||||
import { cn } from "@repo/shared-components";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
@@ -6,7 +7,6 @@ import { getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||
import { getStationsAPI } from "_querys/stations";
|
||||
import { Ambulance, FireExtinguisher, Radio, Siren } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FieldValues } from "react-hook-form";
|
||||
|
||||
type MissionStationsSelectProps = {
|
||||
selectedStations?: number[];
|
||||
@@ -54,7 +54,6 @@ export function StationsSelect({
|
||||
...(vehicleStates.hpgFireEngineState !== HpgState.NOT_REQUESTED || undefined ? ["FW"] : []),
|
||||
...(vehicleStates.hpgPoliceState !== HpgState.NOT_REQUESTED || undefined ? ["POL"] : []),
|
||||
]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedStations, vehicleStates]);
|
||||
|
||||
// Helper to check if a station is a vehicle and its state is NOT_REQUESTED
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
"use client";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useDispatchConnectionStore } from "../../../../../_store/dispatch/connectionStore";
|
||||
@@ -22,7 +23,6 @@ export const ConnectionBtn = () => {
|
||||
const [logoffDebounce, setLogoffDebounce] = useState<NodeJS.Timeout | null>(null);
|
||||
const session = useSession();
|
||||
const uid = session.data?.user?.id;
|
||||
if (!uid) return null;
|
||||
|
||||
// useEffect für die Logoff-Zeit
|
||||
const [logoffHours, logoffMinutes] = form.logoffTime?.split(":").map(Number) || [];
|
||||
@@ -58,7 +58,7 @@ export const ConnectionBtn = () => {
|
||||
connection.disconnect();
|
||||
};
|
||||
}, [connection.disconnect]);
|
||||
|
||||
if (!uid) return null;
|
||||
return (
|
||||
<div className="rounded-box bg-base-200 flex justify-center items-center gap-2 p-1">
|
||||
{connection.message.length > 0 && (
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
"use client";
|
||||
import React, { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { BellRing, BookmarkPlus, Radio } from "lucide-react";
|
||||
import { BellRing, BookmarkPlus } from "lucide-react";
|
||||
import { KEYWORD_CATEGORY, Mission, missionType, Prisma } from "@repo/db";
|
||||
import {
|
||||
JsonValueType,
|
||||
|
||||
@@ -26,7 +26,14 @@ export const Pannel = () => {
|
||||
setOpen(false);
|
||||
}
|
||||
}
|
||||
}, [missions, setMissionFormValues, setEditingMission, setOpen, missionFormValues]);
|
||||
}, [
|
||||
missions,
|
||||
setMissionFormValues,
|
||||
setEditingMission,
|
||||
setOpen,
|
||||
missionFormValues,
|
||||
editingMissionId,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={cn("flex-1 max-w-[600px] z-9999999")}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConnectedAircraft, Prisma } from "@repo/db";
|
||||
import { Prisma } from "@repo/db";
|
||||
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
||||
import { useMrtStore } from "_store/pilot/MrtStore";
|
||||
import { pilotSocket } from "(app)/pilot/socket";
|
||||
@@ -21,7 +21,7 @@ export const useButtons = () => {
|
||||
}) => editConnectedAircraftAPI(aircraftId, data),
|
||||
});
|
||||
|
||||
const { page, setPage } = useMrtStore((state) => state);
|
||||
const { setPage } = useMrtStore((state) => state);
|
||||
|
||||
const handleButton =
|
||||
(button: "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0" | "home") => () => {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { editConnectedAircraftAPI, getConnectedAircraftsAPI } from "_querys/airc
|
||||
import { Prisma } from "@repo/db";
|
||||
import { getNextDateWithTime } from "@repo/shared-components";
|
||||
import { Select } from "_components/Select";
|
||||
import { components } from "react-select";
|
||||
import { Radio } from "lucide-react";
|
||||
|
||||
export const ConnectionBtn = () => {
|
||||
@@ -54,7 +53,7 @@ export const ConnectionBtn = () => {
|
||||
return () => {
|
||||
connection.disconnect();
|
||||
};
|
||||
}, [connection.disconnect]);
|
||||
}, [connection, connection.disconnect]);
|
||||
|
||||
const [logoffHours, logoffMinutes] = form.logoffTime?.split(":").map(Number) || [];
|
||||
|
||||
@@ -87,7 +86,7 @@ export const ConnectionBtn = () => {
|
||||
return () => {
|
||||
if (logoffDebounce) clearTimeout(logoffDebounce);
|
||||
};
|
||||
}, [logoffHours, logoffMinutes, connection.connectedAircraft]);
|
||||
}, [logoffHours, logoffMinutes, connection.connectedAircraft, aircraftMutation, logoffDebounce]);
|
||||
|
||||
const session = useSession();
|
||||
const uid = session.data?.user?.id;
|
||||
@@ -143,7 +142,9 @@ export const ConnectionBtn = () => {
|
||||
})
|
||||
}
|
||||
value={form.selectedStationId ?? ""}
|
||||
formatOptionLabel={(option: any) => option.component}
|
||||
formatOptionLabel={(option: unknown) =>
|
||||
(option as { component: React.ReactNode }).component
|
||||
}
|
||||
options={
|
||||
stations?.map((station) => ({
|
||||
value: station.id.toString(),
|
||||
|
||||
@@ -8,7 +8,7 @@ import dynamic from "next/dynamic";
|
||||
import { ConnectedDispatcher } from "tracker/_components/ConnectedDispatcher";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
|
||||
import { getAircraftsAPI, getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||
import { getAircraftsAPI } from "_querys/aircrafts";
|
||||
import { checkSimulatorConnected } from "@repo/shared-components";
|
||||
import { SimConnectionAlert } from "(app)/pilot/_components/SimConnectionAlert";
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { NextPage } from "next";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
const AuthLayout: NextPage<
|
||||
Readonly<{
|
||||
|
||||
@@ -14,7 +14,7 @@ export const Login = () => {
|
||||
if (status === "authenticated") {
|
||||
navigate.push("/");
|
||||
}
|
||||
}, [session, navigate]);
|
||||
}, [session, navigate, status]);
|
||||
|
||||
useEffect(() => {
|
||||
const signInWithCode = async () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import { useDebounce } from "_helpers/useDebounce";
|
||||
import { useDebounce } from "@repo/shared-components";
|
||||
import { useAudioStore } from "_store/audioStore";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
@@ -112,5 +112,6 @@ export const useSounds = ({
|
||||
callToLong.current!.pause();
|
||||
};
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isTransmitting]);
|
||||
};
|
||||
|
||||
@@ -11,10 +11,10 @@ export const CustomErrorBoundary = ({ children }: { children?: React.ReactNode }
|
||||
let errorTest;
|
||||
let errorCode = 500;
|
||||
if ("statusCode" in error) {
|
||||
errorCode = (error as any).statusCode;
|
||||
errorCode = error.statusCode;
|
||||
}
|
||||
if ("message" in error || error instanceof Error) {
|
||||
errorTest = (error as any).message;
|
||||
errorTest = error.message;
|
||||
} else if (typeof error === "string") {
|
||||
errorTest = error;
|
||||
} else {
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
"use client";
|
||||
|
||||
import { toast } from "react-hot-toast";
|
||||
import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { dispatchSocket } from "(app)/dispatch/socket";
|
||||
import { Mission, NotificationPayload } from "@repo/db";
|
||||
import { NotificationPayload } from "@repo/db";
|
||||
import { HPGnotificationToast } from "_components/customToasts/HPGnotification";
|
||||
import { useMapStore } from "_store/mapStore";
|
||||
import { AdminMessageToast } from "_components/customToasts/AdminMessage";
|
||||
@@ -30,7 +30,7 @@ export function QueryProvider({ children }: { children: ReactNode }) {
|
||||
}),
|
||||
);
|
||||
useEffect(() => {
|
||||
const invalidateMission = (mission: Mission) => {
|
||||
const invalidateMission = () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["missions"],
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
"use client";
|
||||
import { FieldValues, Path } from "react-hook-form";
|
||||
import SelectTemplate, { Props as SelectTemplateProps, StylesConfig } from "react-select";
|
||||
import { cn } from "@repo/shared-components";
|
||||
import dynamic from "next/dynamic";
|
||||
@@ -99,7 +99,7 @@ const SelectCom = ({
|
||||
);
|
||||
};
|
||||
|
||||
const SelectWrapper = <T extends FieldValues>(props: SelectProps) => <SelectCom {...props} />;
|
||||
const SelectWrapper = (props: SelectProps) => <SelectCom {...props} />;
|
||||
|
||||
export const Select = dynamic(() => Promise.resolve(SelectWrapper), {
|
||||
ssr: false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NotificationPayload, ValidationFailed, ValidationSuccess } from "@repo/db";
|
||||
import { ValidationFailed, ValidationSuccess } from "@repo/db";
|
||||
import { BaseNotification } from "_components/customToasts/BaseNotification";
|
||||
import { MapStore, useMapStore } from "_store/mapStore";
|
||||
import { MapStore } from "_store/mapStore";
|
||||
import { Check, Cross } from "lucide-react";
|
||||
import toast, { Toast } from "react-hot-toast";
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { FMS_STATUS_COLORS } from "_helpers/fmsStatusColors";
|
||||
import { editConnectedAircraftAPI, getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||
import { getStationsAPI } from "_querys/stations";
|
||||
import { useMapStore } from "_store/mapStore";
|
||||
import { cpSync } from "fs";
|
||||
import { X } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Toast, toast } from "react-hot-toast";
|
||||
@@ -67,7 +66,7 @@ export const StatusToast = ({ event, t }: { event: StationStatus; t: Toast }) =>
|
||||
} else if (event.status == connectedAircraft?.fmsStatus && !aircraftDataAcurate) {
|
||||
setAircraftDataAccurate(true);
|
||||
}
|
||||
}, [connectedAircraft, station]);
|
||||
}, [aircraftDataAcurate, connectedAircraft, event.status, t.id]);
|
||||
|
||||
useEffect(() => {
|
||||
let soundRef: React.RefObject<HTMLAudioElement | null> | null = null;
|
||||
|
||||
@@ -41,7 +41,7 @@ export const Chat = () => {
|
||||
useEffect(() => {
|
||||
if (!session.data?.user.id) return;
|
||||
setOwnId(session.data?.user.id);
|
||||
}, [session.data?.user.id]);
|
||||
}, [session.data?.user.id, setOwnId]);
|
||||
|
||||
const filteredDispatcher = dispatcher?.filter((d) => d.userId !== session.data?.user.id);
|
||||
const filteredAircrafts = aircrafts?.filter((a) => a.userId !== session.data?.user.id);
|
||||
@@ -118,7 +118,7 @@ export const Chat = () => {
|
||||
const dispatcherUser = dispatcher?.find((d) => d.userId === addTabValue);
|
||||
const user = aircraftUser || dispatcherUser;
|
||||
if (!user) return;
|
||||
let role = "Station" in user ? user.Station.bosCallsignShort : user.zone;
|
||||
const role = "Station" in user ? user.Station.bosCallsignShort : user.zone;
|
||||
addChat(addTabValue, `${asPublicUser(user.publicUser).fullName} (${role})`);
|
||||
setSelectedChat(addTabValue);
|
||||
}}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { cn } from "@repo/shared-components";
|
||||
import { ListCollapse, Plane } from "lucide-react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getMissionsAPI } from "_querys/missions";
|
||||
import { Station } from "@repo/db";
|
||||
import { Mission, Station } from "@repo/db";
|
||||
import { getConnectedAircraftsAPI } from "_querys/aircrafts";
|
||||
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
|
||||
import { useMapStore } from "_store/mapStore";
|
||||
@@ -20,7 +20,13 @@ export const SituationBoard = () => {
|
||||
const { data: missions } = useQuery({
|
||||
queryKey: ["missions", "missions-on-stations"],
|
||||
queryFn: () =>
|
||||
getMissionsAPI(
|
||||
getMissionsAPI<
|
||||
Mission & {
|
||||
MissionsOnStations: (Station & {
|
||||
Station: Station;
|
||||
})[];
|
||||
}
|
||||
>(
|
||||
{
|
||||
state: {
|
||||
not: "finished",
|
||||
@@ -125,8 +131,8 @@ export const SituationBoard = () => {
|
||||
<td>{mission.missionKeywordAbbreviation}</td>
|
||||
<td>{mission.addressCity}</td>
|
||||
<td>
|
||||
{(mission as any).MissionsOnStations?.map(
|
||||
(mos: { Station: Station }) => mos.Station?.bosCallsignShort,
|
||||
{mission.MissionsOnStations?.map(
|
||||
(mos) => mos.Station?.bosCallsignShort,
|
||||
).join(", ")}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -262,7 +262,7 @@ const AircraftMarker = ({ aircraft }: { aircraft: ConnectedAircraft & { Station:
|
||||
return () => {
|
||||
marker?.off("click", handleClick);
|
||||
};
|
||||
}, [aircraft.id, openAircraftMarker, setOpenAircraftMarker, markerRef.current]);
|
||||
}, [aircraft.id, openAircraftMarker, setOpenAircraftMarker]);
|
||||
|
||||
const [anchor, setAnchor] = useState<"topleft" | "topright" | "bottomleft" | "bottomright">(
|
||||
"topleft",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
import { usePannelStore } from "_store/pannelStore";
|
||||
import { Control, Icon, LatLngExpression } from "leaflet";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
LayerGroup,
|
||||
LayersControl,
|
||||
@@ -15,7 +14,7 @@ import {
|
||||
Marker,
|
||||
Tooltip,
|
||||
} from "react-leaflet";
|
||||
// @ts-ignore
|
||||
// @ts-expect-error geojson hat keine Typen
|
||||
import type { FeatureCollection, Geometry } from "geojson";
|
||||
import L from "leaflet";
|
||||
import LEITSTELLENBERECHE from "./_geojson/Leitstellen.json";
|
||||
@@ -197,7 +196,7 @@ const StationsLayer = ({ attribution }: { attribution: Control.Attribution }) =>
|
||||
);
|
||||
};
|
||||
|
||||
const EsriSatellite = ({ attribution }: { attribution: Control.Attribution }) => {
|
||||
const EsriSatellite = () => {
|
||||
const accessToken = process.env.NEXT_PUBLIC_ESRI_ACCESS;
|
||||
return (
|
||||
<>
|
||||
@@ -221,8 +220,7 @@ const StrassentexteEsri = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const OpenAIP = ({ attribution }: { attribution: Control.Attribution }) => {
|
||||
const accessToken = process.env.NEXT_PUBLIC_OPENAIP_ACCESS;
|
||||
const OpenAIP = () => {
|
||||
const ref = useRef<L.TileLayer | null>(null);
|
||||
|
||||
return (
|
||||
@@ -241,7 +239,7 @@ const OpenAIP = ({ attribution }: { attribution: Control.Attribution }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const NiederschlagOverlay = ({ attribution }: { attribution: Control.Attribution }) => {
|
||||
const NiederschlagOverlay = () => {
|
||||
const tileLayerRef = useRef<L.TileLayer.WMS | null>(null);
|
||||
|
||||
return (
|
||||
@@ -311,7 +309,7 @@ export const BaseMaps = () => {
|
||||
<RadioAreaLayer />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name={"Niederschlag"}>
|
||||
<NiederschlagOverlay attribution={map.attributionControl} />
|
||||
<NiederschlagOverlay />
|
||||
</LayersControl.Overlay>
|
||||
|
||||
<LayersControl.Overlay name={"Windkraftanlagen offshore"}>
|
||||
@@ -322,7 +320,7 @@ export const BaseMaps = () => {
|
||||
<StationsLayer attribution={map.attributionControl} />
|
||||
</LayersControl.Overlay>
|
||||
<LayersControl.Overlay name={"OpenAIP"}>
|
||||
<OpenAIP attribution={map.attributionControl} />
|
||||
<OpenAIP />
|
||||
</LayersControl.Overlay>
|
||||
|
||||
<LayersControl.BaseLayer name="OpenStreetMap Dark" checked>
|
||||
@@ -348,7 +346,7 @@ export const BaseMaps = () => {
|
||||
</LayersControl.BaseLayer>
|
||||
<LayersControl.BaseLayer name="ESRI Satellite">
|
||||
<LayerGroup>
|
||||
<EsriSatellite attribution={map.attributionControl} />
|
||||
<EsriSatellite />
|
||||
<StrassentexteEsri />
|
||||
</LayerGroup>
|
||||
</LayersControl.BaseLayer>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { OSMWay, Prisma } from "@repo/db";
|
||||
import { OSMWay } from "@repo/db";
|
||||
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
|
||||
import { useMapStore } from "_store/mapStore";
|
||||
import { usePannelStore } from "_store/pannelStore";
|
||||
@@ -8,8 +8,6 @@ import { getOsmAddress } from "_querys/osm";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { Popup, useMap } from "react-leaflet";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { editMissionAPI } from "_querys/missions";
|
||||
import { findClosestPolygon } from "_helpers/findClosestPolygon";
|
||||
|
||||
export const ContextMenu = () => {
|
||||
@@ -22,8 +20,9 @@ export const ContextMenu = () => {
|
||||
setSearchPopup,
|
||||
toggleSearchElementSelection,
|
||||
} = useMapStore();
|
||||
const { missionFormValues, setMissionFormValues, setOpen, isOpen, editingMissionId } =
|
||||
usePannelStore((state) => state);
|
||||
const { missionFormValues, setMissionFormValues, setOpen, isOpen } = usePannelStore(
|
||||
(state) => state,
|
||||
);
|
||||
const [showRulerOptions, setShowRulerOptions] = useState(false);
|
||||
const [rulerHover, setRulerHover] = useState(false);
|
||||
const [rulerOptionsHover, setRulerOptionsHover] = useState(false);
|
||||
|
||||
@@ -72,7 +72,7 @@ const MissionPopupContent = ({
|
||||
default:
|
||||
return <span className="text-gray-100">Error</span>;
|
||||
}
|
||||
}, [currentTab, mission]);
|
||||
}, [currentTab, hpgNeedsAttention, mission]);
|
||||
|
||||
const setOpenMissionMarker = useMapStore((state) => state.setOpenMissionMarker);
|
||||
const { anchor } = useSmartPopup();
|
||||
@@ -350,6 +350,10 @@ const MissionMarker = ({ mission }: { mission: Mission }) => {
|
||||
editingMissionId,
|
||||
mission.addressLat,
|
||||
mission.addressLng,
|
||||
mission.hpgLocationLat,
|
||||
mission.hpgLocationLng,
|
||||
mission.hpgValidationState,
|
||||
mission.id,
|
||||
missionFormValues?.addressLat,
|
||||
missionFormValues?.addressLng,
|
||||
]);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useEffect } from "react";
|
||||
|
||||
export const SearchElements = () => {
|
||||
const { searchElements, openMissionMarker, setSearchElements } = useMapStore();
|
||||
const { isOpen: pannelOpen, editingMissionId } = usePannelStore((state) => state);
|
||||
const { isOpen: pannelOpen } = usePannelStore((state) => state);
|
||||
const { data: missions } = useQuery({
|
||||
queryKey: ["missions"],
|
||||
queryFn: () =>
|
||||
@@ -42,7 +42,7 @@ export const SearchElements = () => {
|
||||
);
|
||||
setSearchElements(elements.filter((e) => !!e));
|
||||
}
|
||||
}, [openMissionMarker, pannelOpen, missions]);
|
||||
}, [openMissionMarker, pannelOpen, missions, setSearchElements]);
|
||||
|
||||
const SearchElement = ({ element }: { element: OSMWay }) => {
|
||||
const { toggleSearchElementSelection } = useMapStore();
|
||||
|
||||
@@ -250,7 +250,7 @@ const StationTab = ({ aircraft }: { aircraft: ConnectedAircraft & { Station: Sta
|
||||
const lstName = useMemo(() => {
|
||||
if (!aircraft.posLng || !aircraft.posLat) return station.bosRadioArea;
|
||||
return findLeitstelleForPosition(aircraft.posLng, aircraft.posLat);
|
||||
}, [aircraft.posLng, aircraft.posLat]);
|
||||
}, [aircraft.posLng, aircraft.posLat, station.bosRadioArea]);
|
||||
|
||||
return (
|
||||
<div className="p-4 text-base-content">
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
SmartphoneNfc,
|
||||
CheckCheck,
|
||||
Cross,
|
||||
Radio,
|
||||
Route,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
@@ -26,12 +25,9 @@ import {
|
||||
HpgState,
|
||||
HpgValidationState,
|
||||
Mission,
|
||||
MissionAlertLog,
|
||||
MissionCompletedLog,
|
||||
MissionLog,
|
||||
MissionMessageLog,
|
||||
Prisma,
|
||||
Station,
|
||||
} from "@repo/db";
|
||||
import { usePannelStore } from "_store/pannelStore";
|
||||
import { useSession } from "next-auth/react";
|
||||
@@ -143,13 +139,16 @@ const Einsatzdetails = ({
|
||||
state: "finished",
|
||||
missionLog: {
|
||||
push: {
|
||||
type: "completed-log",
|
||||
auto: false,
|
||||
timeStamp: new Date().toISOString(),
|
||||
data: {
|
||||
user: getPublicUser(session.data?.user, { ignorePrivacy: true }),
|
||||
},
|
||||
} as any,
|
||||
toJSON: () => ({
|
||||
type: "message-log",
|
||||
auto: false,
|
||||
timeStamp: new Date().toISOString(),
|
||||
data: {
|
||||
message: "Einsatz abgeschlossen",
|
||||
user: getPublicUser(session.data.user, { ignorePrivacy: true }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -408,11 +407,6 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
|
||||
refetchMissionStationIds();
|
||||
}, [mission.missionStationIds, refetchMissionStationIds]);
|
||||
|
||||
const { data: allStations } = useQuery({
|
||||
queryKey: ["stations"],
|
||||
queryFn: () => getStationsAPI(),
|
||||
});
|
||||
|
||||
const sendAlertMutation = useMutation({
|
||||
mutationKey: ["missions"],
|
||||
mutationFn: ({
|
||||
@@ -527,6 +521,7 @@ const Rettungsmittel = ({ mission }: { mission: Mission }) => {
|
||||
menuPlacement="top"
|
||||
className="min-w-[320px] flex-1"
|
||||
isMulti={false}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onChange={(v: any) => {
|
||||
setSelectedStation(v);
|
||||
}}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client";
|
||||
import { PublicUser } from "@repo/db";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { cn, PenaltyDropdown } from "@repo/shared-components";
|
||||
import { PenaltyDropdown } from "@repo/shared-components";
|
||||
import { getConnectedAircraftsAPI, kickAircraftAPI } from "_querys/aircrafts";
|
||||
import { getConnectedDispatcherAPI, kickDispatcherAPI } from "_querys/dispatcher";
|
||||
import { getLivekitRooms, kickLivekitParticipant } from "_querys/livekit";
|
||||
import { ParticipantInfo } from "livekit-server-sdk";
|
||||
import {
|
||||
Eye,
|
||||
LockKeyhole,
|
||||
Plane,
|
||||
RedoDot,
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
UserCheck,
|
||||
Workflow,
|
||||
} from "lucide-react";
|
||||
import { ReactNode, useRef, useState } from "react";
|
||||
import { useRef } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
export default function AdminPanel() {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { SettingsIcon, Volume2 } from "lucide-react";
|
||||
import MicVolumeBar from "_components/MicVolumeIndication";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { editUserAPI, getUserAPI } from "_querys/user";
|
||||
import { Prisma } from "@repo/db";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useAudioStore } from "_store/audioStore";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
@@ -36,6 +36,7 @@ export function findClosestPolygon(
|
||||
const polygon = toPolygonFeature(way.nodes);
|
||||
if (!polygon) continue;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const center = centroid(polygon as any).geometry.coordinates; // [lon, lat]
|
||||
|
||||
const newDistance = distance([referencePoint.lon, referencePoint.lat], center);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { point, multiPolygon, booleanPointInPolygon, booleanIntersects, polygon } from "@turf/turf";
|
||||
import { point, booleanPointInPolygon, polygon } from "@turf/turf";
|
||||
import leitstellenGeoJSON from "../_components/map/_geojson/Leitstellen.json"; // Pfad anpassen
|
||||
|
||||
export function findLeitstelleForPosition(lat: number, lng: number) {
|
||||
const heliPoint = point([lat, lng]);
|
||||
|
||||
for (const feature of (leitstellenGeoJSON as any).features) {
|
||||
for (const feature of (leitstellenGeoJSON as GeoJSON.FeatureCollection).features) {
|
||||
const geom = feature.geometry;
|
||||
|
||||
if (geom.type === "Polygon") {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useAudioStore } from "_store/audioStore";
|
||||
import {
|
||||
LocalParticipant,
|
||||
LocalTrackPublication,
|
||||
Participant,
|
||||
RemoteParticipant,
|
||||
RemoteTrack,
|
||||
RemoteTrackPublication,
|
||||
@@ -30,19 +28,12 @@ export const handleTrackSubscribed = (
|
||||
}
|
||||
};
|
||||
|
||||
export const handleTrackUnsubscribed = (
|
||||
track: RemoteTrack,
|
||||
publication: RemoteTrackPublication,
|
||||
participant: RemoteParticipant,
|
||||
) => {
|
||||
export const handleTrackUnsubscribed = (track: RemoteTrack) => {
|
||||
// remove tracks from all attached elements
|
||||
track.detach();
|
||||
};
|
||||
|
||||
export const handleLocalTrackUnpublished = (
|
||||
publication: LocalTrackPublication,
|
||||
participant: LocalParticipant,
|
||||
) => {
|
||||
export const handleLocalTrackUnpublished = (publication: LocalTrackPublication) => {
|
||||
// when local tracks are ended, update UI to remove them from rendering
|
||||
publication.track?.detach();
|
||||
};
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { DependencyList, useEffect } from "react";
|
||||
import useTimeout from "./useTimeout";
|
||||
|
||||
export const useDebounce = (callback: () => void, delay: number, dependencies: DependencyList) => {
|
||||
const { reset, clear } = useTimeout(callback, delay);
|
||||
useEffect(reset, [...dependencies, reset]);
|
||||
useEffect(() => clear, [clear]);
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
|
||||
export default function useTimeout(callback: () => void, delay: number) {
|
||||
const callbackRef = useRef(callback);
|
||||
const timeoutRef = useRef<NodeJS.Timeout>(null);
|
||||
|
||||
useEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
const set = useCallback(() => {
|
||||
timeoutRef.current = setTimeout(() => callbackRef.current(), delay);
|
||||
}, [delay]);
|
||||
|
||||
const clear = useCallback(() => {
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
set();
|
||||
return clear;
|
||||
}, [delay, set, clear]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
clear();
|
||||
set();
|
||||
}, [clear, set]);
|
||||
|
||||
return { reset, clear, set };
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConnectedAircraft, PositionLog, Prisma, PublicUser, Station } from "@repo/db";
|
||||
import { ConnectedAircraft, PositionLog, Prisma, Station } from "@repo/db";
|
||||
import axios from "axios";
|
||||
import { serverApi } from "_helpers/axios";
|
||||
import { checkSimulatorConnected } from "@repo/shared-components";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConnectedAircraft, ConnectedDispatcher, Prisma } from "@repo/db";
|
||||
import { ConnectedDispatcher, Prisma } from "@repo/db";
|
||||
import { serverApi } from "_helpers/axios";
|
||||
import axios from "axios";
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ import { Mission, MissionSdsLog, Prisma } from "@repo/db";
|
||||
import axios from "axios";
|
||||
import { serverApi } from "_helpers/axios";
|
||||
|
||||
export const getMissionsAPI = async (
|
||||
export const getMissionsAPI = async <T = Mission>(
|
||||
filter?: Prisma.MissionWhereInput,
|
||||
include?: Prisma.MissionInclude,
|
||||
orderBy?: Prisma.MissionOrderByWithRelationInput,
|
||||
) => {
|
||||
const res = await axios.get<Mission[]>("/api/missions", {
|
||||
): Promise<T[]> => {
|
||||
const res = await axios.get<T[]>("/api/missions", {
|
||||
params: {
|
||||
filter: JSON.stringify(filter),
|
||||
include: JSON.stringify(include),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { raw } from "../../../../packages/database/generated/client/runtime/library";
|
||||
|
||||
export const getOsmAddress = async (lat: number, lng: number) => {
|
||||
const address = await fetch(
|
||||
`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json`,
|
||||
|
||||
@@ -65,7 +65,7 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
||||
const newSpeaktingParticipants = get().speakingParticipants.filter(
|
||||
(p) => !(p.identity === participant.identity),
|
||||
);
|
||||
set((state) => ({
|
||||
set(() => ({
|
||||
speakingParticipants: newSpeaktingParticipants,
|
||||
}));
|
||||
if (newSpeaktingParticipants.length === 0 && get().transmitBlocked) {
|
||||
@@ -77,8 +77,7 @@ export const useAudioStore = create<TalkState>((set, get) => ({
|
||||
set({ micDeviceId, micVolume });
|
||||
},
|
||||
toggleTalking: () => {
|
||||
const { room, isTalking, micDeviceId, micVolume, speakingParticipants, transmitBlocked } =
|
||||
get();
|
||||
const { room, isTalking, micDeviceId, speakingParticipants, transmitBlocked } = get();
|
||||
if (!room) return;
|
||||
|
||||
if (speakingParticipants.length > 0 && !isTalking && !transmitBlocked) {
|
||||
@@ -187,7 +186,7 @@ interface PTTData {
|
||||
}
|
||||
|
||||
const handlePTT = (data: PTTData) => {
|
||||
const { shouldTransmit, source } = data;
|
||||
const { shouldTransmit } = data;
|
||||
const { room, speakingParticipants } = useAudioStore.getState();
|
||||
if (!room) return;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { prisma, Prisma } from "@repo/db";
|
||||
import { prisma } from "@repo/db";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest): Promise<NextResponse> {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { AuthOptions, getServerSession as getNextAuthServerSession } from "next-auth";
|
||||
import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import { prisma, PrismaClient } from "@repo/db";
|
||||
import { prisma } from "@repo/db";
|
||||
|
||||
export const options: AuthOptions = {
|
||||
providers: [
|
||||
@@ -9,7 +10,7 @@ export const options: AuthOptions = {
|
||||
credentials: {
|
||||
code: { label: "code", type: "code" },
|
||||
},
|
||||
async authorize(credentials, req) {
|
||||
async authorize(credentials) {
|
||||
try {
|
||||
if (!credentials) throw new Error("No credentials provided");
|
||||
const code = await prisma.oAuthToken.findFirstOrThrow({
|
||||
@@ -60,7 +61,7 @@ export const options: AuthOptions = {
|
||||
|
||||
adapter: PrismaAdapter(prisma as any),
|
||||
callbacks: {
|
||||
jwt: async ({ token, user, ...rest }) => {
|
||||
jwt: async ({ token, user }) => {
|
||||
if (user && "firstname" in user) {
|
||||
return {
|
||||
...token,
|
||||
@@ -69,7 +70,7 @@ export const options: AuthOptions = {
|
||||
}
|
||||
return token;
|
||||
},
|
||||
session: async ({ session, user, token }) => {
|
||||
session: async ({ session, token }) => {
|
||||
const dbUser = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: token?.sub,
|
||||
|
||||
@@ -3,15 +3,10 @@ import { RoomManager } from "_helpers/LivekitRoomManager";
|
||||
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export const GET = async (request: NextRequest) => {
|
||||
export const GET = async () => {
|
||||
const session = await getServerSession();
|
||||
|
||||
if (!session) return Response.json({ message: "Unauthorized" }, { status: 401 });
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
const rooms = await RoomManager.listRooms();
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
||||
import { ROOMS } from "_data/livekitRooms";
|
||||
import { AccessToken } from "livekit-server-sdk";
|
||||
import { NextRequest } from "next/server";
|
||||
import { getPublicUser, prisma } from "@repo/db";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PositionLog, Prisma, prisma, User } from "@repo/db";
|
||||
import { PositionLog, prisma, User } from "@repo/db";
|
||||
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
||||
import { verify } from "jsonwebtoken";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { nextJsConfig } from "@repo/eslint-config/next-js";
|
||||
import nextJsConfig from "@repo/eslint-config/next-js";
|
||||
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
export default nextJsConfig;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"check-types": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@hookform/resolvers": "^5.1.1",
|
||||
"@livekit/components-react": "^2.9.12",
|
||||
"@livekit/components-styles": "^1.1.6",
|
||||
@@ -34,6 +35,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"daisyui": "^5.0.43",
|
||||
"date-fns": "^4.1.0",
|
||||
"eslint-config-next": "^15.3.4",
|
||||
"geojson": "^0.5.0",
|
||||
"i": "^0.3.7",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
|
||||
1
apps/dispatch/types/next-auth.d.ts
vendored
1
apps/dispatch/types/next-auth.d.ts
vendored
@@ -1,4 +1,3 @@
|
||||
import NextAuth from "next-auth";
|
||||
import { User as IUser } from "@repo/db";
|
||||
|
||||
declare module "next-auth" {
|
||||
|
||||
@@ -6,8 +6,8 @@ export const addMessage = async (notam: Prisma.ConfigCreateInput) => {
|
||||
await prisma.config.create({
|
||||
data: notam,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error("Failed to add message");
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to add message: ${e instanceof Error ? e.message : "Unknown error"}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ export const disableMessage = async () => {
|
||||
await prisma.config.create({
|
||||
data: {},
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error("Failed to disable message");
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to add message: ${e instanceof Error ? e.message : "Unknown error"}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Check, MessageSquareWarning, Settings } from "lucide-react";
|
||||
import { Check, Settings } from "lucide-react";
|
||||
import { MessageForm } from "./_components/MessageForm";
|
||||
import { PaginatedTable, PaginatedTableRef } from "_components/PaginatedTable";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Event, Participant } from "@repo/db";
|
||||
import { EventAppointmentOptionalDefaults } from "@repo/db/zod";
|
||||
import { EventAppointmentOptionalDefaults, InputJsonValueType } from "@repo/db/zod";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { RefObject, useRef } from "react";
|
||||
@@ -45,7 +45,7 @@ export const AppointmentModal = ({
|
||||
✕
|
||||
</button>
|
||||
</form>
|
||||
<h3 className="font-bold text-lg">Termin {appointmentForm.watch("id")}</h3>
|
||||
|
||||
<form
|
||||
onSubmit={appointmentForm.handleSubmit(async (values) => {
|
||||
if (!event) return;
|
||||
@@ -55,13 +55,13 @@ export const AppointmentModal = ({
|
||||
})}
|
||||
className="flex flex-col"
|
||||
>
|
||||
<DateInput
|
||||
control={appointmentForm.control}
|
||||
name="appointmentDate"
|
||||
showTimeInput
|
||||
timeCaption="Uhrzeit"
|
||||
showTimeCaption
|
||||
/>
|
||||
<div className="flex justify-between mr-7">
|
||||
<h3 className="font-bold text-lg">Termin {appointmentForm.watch("id")}</h3>
|
||||
<DateInput
|
||||
value={new Date(appointmentForm.watch("appointmentDate") || Date.now())}
|
||||
onChange={(date) => appointmentForm.setValue("appointmentDate", date)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<PaginatedTable
|
||||
hide={appointmentForm.watch("id") === undefined}
|
||||
@@ -150,7 +150,7 @@ export const AppointmentModal = ({
|
||||
attended: false,
|
||||
appointmentCancelled: true,
|
||||
statusLog: [
|
||||
...(row.original.statusLog as any),
|
||||
...(row.original.statusLog as InputJsonValueType[]),
|
||||
{
|
||||
event: "Gefehlt an Event",
|
||||
timestamp: new Date().toISOString(),
|
||||
@@ -169,7 +169,7 @@ export const AppointmentModal = ({
|
||||
);
|
||||
},
|
||||
},
|
||||
] as ColumnDef<Participant, any>[]
|
||||
] as ColumnDef<Participant>[]
|
||||
}
|
||||
prismaModel={"participant"}
|
||||
filter={{
|
||||
|
||||
@@ -59,9 +59,7 @@ export const ParticipantModal = ({ participantForm, ref }: ParticipantModalProps
|
||||
if (!participantForm.watch("id")) return;
|
||||
|
||||
const participant = participantForm.getValues();
|
||||
await handleParticipantFinished(participant.id.toString()).catch((e) => {
|
||||
const error = e as AxiosError;
|
||||
});
|
||||
await handleParticipantFinished(participant.id.toString()).catch(() => {});
|
||||
|
||||
toast.success("Workflow erfolgreich ausgeführt");
|
||||
router.refresh();
|
||||
@@ -119,10 +117,10 @@ export const ParticipantModal = ({ participantForm, ref }: ParticipantModalProps
|
||||
<div className="flex flex-col">
|
||||
<h3 className="text-xl">Verlauf</h3>
|
||||
{(participantForm.watch("statusLog") as unknown as ParticipantLog[])?.map((s) => (
|
||||
<div className="flex justify-between" key={(s as any).timestamp}>
|
||||
<div className="flex justify-between" key={s.timestamp.toString()}>
|
||||
<p>{s.event}</p>
|
||||
<p>{s.user}</p>
|
||||
<p>{new Date((s as any).timestamp).toLocaleString()}</p>
|
||||
<p>{new Date(s.timestamp).toLocaleString()}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
"use server";
|
||||
|
||||
import { prisma, Prisma, Event, Participant, EventAppointment } from "@repo/db";
|
||||
import { prisma, Prisma, Event, Participant } from "@repo/db";
|
||||
|
||||
export const upsertEvent = async (
|
||||
event: Prisma.EventCreateInput,
|
||||
id?: Event["id"],
|
||||
) => {
|
||||
export const upsertEvent = async (event: Prisma.EventCreateInput, id?: Event["id"]) => {
|
||||
const newEvent = id
|
||||
? await prisma.event.update({
|
||||
where: { id: id },
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { prisma } from "@repo/db";
|
||||
import { Form } from "../_components/Form";
|
||||
|
||||
export default async () => {
|
||||
|
||||
@@ -1,23 +1,36 @@
|
||||
import { PartyPopperIcon } from "lucide-react";
|
||||
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
||||
import Link from "next/link";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { Event } from "@repo/db";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<PaginatedTable
|
||||
showEditButton
|
||||
prismaModel="event"
|
||||
columns={[
|
||||
{
|
||||
header: "Name",
|
||||
accessorKey: "name",
|
||||
},
|
||||
{
|
||||
header: "Versteckt",
|
||||
accessorKey: "hidden",
|
||||
},
|
||||
]}
|
||||
columns={
|
||||
[
|
||||
{
|
||||
header: "Name",
|
||||
accessorKey: "name",
|
||||
},
|
||||
{
|
||||
header: "Versteckt",
|
||||
accessorKey: "hidden",
|
||||
},
|
||||
{
|
||||
header: "Aktionen",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-1">
|
||||
<Link href={`/admin/event/${row.original.id}`}>
|
||||
<button className="btn btn-sm">Edit</button>
|
||||
</Link>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<Event>[]
|
||||
}
|
||||
leftOfSearch={
|
||||
<span className="flex items-center gap-2">
|
||||
<PartyPopperIcon className="w-5 h-5" /> Events
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { KeywordOptionalDefaultsSchema } from "@repo/db/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { KEYWORD_CATEGORY, Keyword } from "@repo/db";
|
||||
import { FileText } from "lucide-react";
|
||||
import { Input } from "../../../../_components/ui/Input";
|
||||
@@ -24,7 +23,7 @@ export const KeywordForm = ({ keyword }: { keyword?: Keyword }) => {
|
||||
<form
|
||||
onSubmit={form.handleSubmit(async (values) => {
|
||||
setLoading(true);
|
||||
const createdKeyword = await upsertKeyword(values, keyword?.id);
|
||||
await upsertKeyword(values, keyword?.id);
|
||||
setLoading(false);
|
||||
if (!keyword) redirect(`/admin/keyword`);
|
||||
})}
|
||||
|
||||
@@ -9,7 +9,6 @@ export default () => {
|
||||
<>
|
||||
<PaginatedTable
|
||||
initialOrderBy={[{ id: "category", desc: true }]}
|
||||
showEditButton
|
||||
prismaModel="keyword"
|
||||
searchFields={["name", "abreviation", "description"]}
|
||||
columns={
|
||||
@@ -26,6 +25,16 @@ export default () => {
|
||||
header: "Name",
|
||||
accessorKey: "name",
|
||||
},
|
||||
{
|
||||
header: "Aktionen",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-1">
|
||||
<Link href={`/admin/keyword/${row.original.id}`}>
|
||||
<button className="btn btn-sm">bearbeiten</button>
|
||||
</Link>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<Keyword>[]
|
||||
}
|
||||
leftOfSearch={
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
"use client";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { StationOptionalDefaultsSchema } from "@repo/db/zod";
|
||||
import { set, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { BosUse, Country, Station } from "@repo/db";
|
||||
import { FileText, LocateIcon, PlaneIcon } from "lucide-react";
|
||||
import { Input } from "../../../../_components/ui/Input";
|
||||
|
||||
@@ -1,32 +1,46 @@
|
||||
"use client";
|
||||
import { DatabaseBackupIcon } from "lucide-react";
|
||||
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
||||
import Link from "next/link";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { Station } from "@repo/db";
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<>
|
||||
<PaginatedTable
|
||||
showEditButton
|
||||
prismaModel="station"
|
||||
searchFields={["bosCallsign", "bosUse", "country", "operator"]}
|
||||
columns={[
|
||||
{
|
||||
header: "BOS Name",
|
||||
accessorKey: "bosCallsign",
|
||||
},
|
||||
{
|
||||
header: "Bos Use",
|
||||
accessorKey: "bosUse",
|
||||
},
|
||||
{
|
||||
header: "Country",
|
||||
accessorKey: "country",
|
||||
},
|
||||
{
|
||||
header: "operator",
|
||||
accessorKey: "operator",
|
||||
},
|
||||
]}
|
||||
columns={
|
||||
[
|
||||
{
|
||||
header: "BOS Name",
|
||||
accessorKey: "bosCallsign",
|
||||
},
|
||||
{
|
||||
header: "Bos Use",
|
||||
accessorKey: "bosUse",
|
||||
},
|
||||
{
|
||||
header: "Country",
|
||||
accessorKey: "country",
|
||||
},
|
||||
{
|
||||
header: "operator",
|
||||
accessorKey: "operator",
|
||||
},
|
||||
{
|
||||
header: "Aktionen",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-1">
|
||||
<Link href={`/admin/event/${row.original.id}`}>
|
||||
<button className="btn btn-sm">Edit</button>
|
||||
</Link>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<Station>[]
|
||||
}
|
||||
leftOfSearch={
|
||||
<span className="flex items-center gap-2">
|
||||
<DatabaseBackupIcon className="w-5 h-5" /> Stationen
|
||||
@@ -35,9 +49,7 @@ const page = () => {
|
||||
rightOfSearch={
|
||||
<p className="text-2xl font-semibold text-left flex items-center gap-2 justify-between">
|
||||
<Link href={"/admin/station/new"}>
|
||||
<button className="btn btn-sm btn-outline btn-primary">
|
||||
Erstellen
|
||||
</button>
|
||||
<button className="btn btn-sm btn-outline btn-primary">Erstellen</button>
|
||||
</Link>
|
||||
</p>
|
||||
}
|
||||
|
||||
@@ -39,26 +39,23 @@ import { PaginatedTable, PaginatedTableRef } from "_components/PaginatedTable";
|
||||
import { cn } from "@repo/shared-components";
|
||||
import {
|
||||
ChartBarBigIcon,
|
||||
Check,
|
||||
Eye,
|
||||
LockKeyhole,
|
||||
PlaneIcon,
|
||||
RedoDot,
|
||||
ShieldUser,
|
||||
Timer,
|
||||
Trash2,
|
||||
Users,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { Error } from "_components/Error";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { setStandardName } from "../../../../../../helper/discord";
|
||||
import { setStandardName } from "(app)/../../helper/discord";
|
||||
import { penaltyColumns } from "(app)/admin/penalty/columns";
|
||||
import { addPenalty, editPenaltys } from "(app)/admin/penalty/actions";
|
||||
import { reportColumns } from "(app)/admin/report/columns";
|
||||
import { sendMail, sendMailByTemplate } from "../../../../../../helper/mail";
|
||||
import { sendMailByTemplate } from "(app)/../../helper/mail";
|
||||
|
||||
interface ProfileFormProps {
|
||||
user: User;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
"use client";
|
||||
import { User2 } from "lucide-react";
|
||||
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
||||
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
||||
import Link from "next/link";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { User } from "@repo/db";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
const AdminUserPage = async () => {
|
||||
const session = await getServerSession();
|
||||
const AdminUserPage = () => {
|
||||
const { data: session } = useSession();
|
||||
return (
|
||||
<>
|
||||
<PaginatedTable
|
||||
showEditButton
|
||||
prismaModel="user"
|
||||
searchFields={["publicId", "firstname", "lastname", "email"]}
|
||||
initialOrderBy={[
|
||||
@@ -16,28 +19,40 @@ const AdminUserPage = async () => {
|
||||
desc: false,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
header: "ID",
|
||||
accessorKey: "publicId",
|
||||
},
|
||||
{
|
||||
header: "Vorname",
|
||||
accessorKey: "firstname",
|
||||
},
|
||||
{
|
||||
header: "Nachname",
|
||||
accessorKey: "lastname",
|
||||
},
|
||||
...(session?.user.permissions.includes("ADMIN_USER_ADVANCED")
|
||||
? [
|
||||
{
|
||||
header: "Email",
|
||||
accessorKey: "email",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
columns={
|
||||
[
|
||||
{
|
||||
header: "ID",
|
||||
accessorKey: "publicId",
|
||||
},
|
||||
{
|
||||
header: "Vorname",
|
||||
accessorKey: "firstname",
|
||||
},
|
||||
{
|
||||
header: "Nachname",
|
||||
accessorKey: "lastname",
|
||||
},
|
||||
...(session?.user.permissions.includes("ADMIN_USER_ADVANCED")
|
||||
? [
|
||||
{
|
||||
header: "Email",
|
||||
accessorKey: "email",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
header: "Aktionen",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-1">
|
||||
<Link href={`/admin/event/${row.original.id}`}>
|
||||
<button className="btn btn-sm">Anzeigen</button>
|
||||
</Link>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<User>[]
|
||||
} // Define the columns for the user table
|
||||
leftOfSearch={
|
||||
<p className="text-2xl font-semibold text-left flex items-center gap-2">
|
||||
<User2 className="w-5 h-5" /> Benutzer
|
||||
|
||||
@@ -17,7 +17,9 @@ export const EventCard = ({
|
||||
Participants: Participant[];
|
||||
};
|
||||
selectedAppointments: EventAppointment[];
|
||||
appointments: EventAppointment[];
|
||||
appointments: (EventAppointment & {
|
||||
Participants: { userId: string }[];
|
||||
})[];
|
||||
}) => {
|
||||
return (
|
||||
<div className="col-span-full">
|
||||
|
||||
@@ -16,7 +16,11 @@ import {
|
||||
TriangleAlert,
|
||||
} from "lucide-react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { ParticipantOptionalDefaults, ParticipantOptionalDefaultsSchema } from "@repo/db/zod";
|
||||
import {
|
||||
InputJsonValueType,
|
||||
ParticipantOptionalDefaults,
|
||||
ParticipantOptionalDefaultsSchema,
|
||||
} from "@repo/db/zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Select } from "../../../_components/ui/Select";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -24,11 +28,14 @@ import { handleParticipantEnrolled } from "../../../../helper/events";
|
||||
import { eventCompleted } from "@repo/shared-components";
|
||||
import MDEditor from "@uiw/react-md-editor";
|
||||
import toast from "react-hot-toast";
|
||||
import { formatDate } from "date-fns";
|
||||
|
||||
interface ModalBtnProps {
|
||||
title: string;
|
||||
event: Event;
|
||||
dates: EventAppointment[];
|
||||
dates: (EventAppointment & {
|
||||
Participants: { userId: string }[];
|
||||
})[];
|
||||
selectedAppointments: EventAppointment[];
|
||||
participant?: Participant;
|
||||
user: User;
|
||||
@@ -88,14 +95,16 @@ const ModalBtn = ({
|
||||
(date) =>
|
||||
date.id === selectAppointmentForm.watch("eventAppointmentId") || selectedAppointment?.id,
|
||||
);
|
||||
const ownIndexInParticipantList = (selectedDate as any)?.Participants?.findIndex(
|
||||
(p: Participant) => p.userId === user.id,
|
||||
const ownIndexInParticipantList = selectedDate?.Participants?.findIndex(
|
||||
(p) => p.userId === user.id,
|
||||
);
|
||||
|
||||
const ownPlaceInParticipantList =
|
||||
ownIndexInParticipantList === -1
|
||||
? (selectedDate as any)?.Participants?.length + 1
|
||||
: ownIndexInParticipantList + 1;
|
||||
typeof ownIndexInParticipantList === "number"
|
||||
? ownIndexInParticipantList === -1
|
||||
? (selectedDate?.Participants?.length ?? 0) + 1
|
||||
: ownIndexInParticipantList + 1
|
||||
: undefined;
|
||||
|
||||
const missingRequirements =
|
||||
event.requiredBadges?.length > 0 &&
|
||||
@@ -167,13 +176,7 @@ const ModalBtn = ({
|
||||
<Select
|
||||
form={selectAppointmentForm}
|
||||
options={dates.map((date) => ({
|
||||
label: `${new Date(date.appointmentDate).toLocaleString("de-DE", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})} - (${(date as any).Participants.length}/${event.maxParticipants})`,
|
||||
label: `${formatDate(date.appointmentDate, "dd.MM.yyyy HH:mm")} - (${date.Participants.length}/${event.maxParticipants})`,
|
||||
value: date.id,
|
||||
}))}
|
||||
name="eventAppointmentId"
|
||||
@@ -296,7 +299,7 @@ const ModalBtn = ({
|
||||
userId: participant!.userId,
|
||||
appointmentCancelled: true,
|
||||
statusLog: [
|
||||
...(participant?.statusLog as any),
|
||||
...(participant?.statusLog as unknown as InputJsonValueType[]),
|
||||
{
|
||||
data: {
|
||||
appointmentId: selectedAppointment.id,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Download } from "lucide-react";
|
||||
import Image, { StaticImageData } from "next/image";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Service } from "../page";
|
||||
import { generateToken } from "./action";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useErrorBoundary } from "react-error-boundary";
|
||||
import { se } from "date-fns/locale";
|
||||
import { PERMISSION } from "@repo/db";
|
||||
|
||||
export const Authorize = ({ service }: { service: Service }) => {
|
||||
|
||||
@@ -11,10 +11,10 @@ export const CustomErrorBoundary = ({ children }: { children?: React.ReactNode }
|
||||
let errorTest;
|
||||
let errorCode = 500;
|
||||
if ("statusCode" in error) {
|
||||
errorCode = (error as any).statusCode;
|
||||
errorCode = error.statusCode;
|
||||
}
|
||||
if ("message" in error || error instanceof Error) {
|
||||
errorTest = (error as any).message;
|
||||
errorTest = error.message;
|
||||
} else if (typeof error === "string") {
|
||||
errorTest = error;
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
import { useEffect, useState, useCallback, Ref, useImperativeHandle } from "react";
|
||||
import { useState, Ref, useImperativeHandle } from "react";
|
||||
import SortableTable, { Pagination, SortableTableProps } from "./Table";
|
||||
import { PrismaClient } from "@repo/db";
|
||||
import { getData } from "./pagiantedTableActions";
|
||||
import { useDebounce } from "@repo/shared-components";
|
||||
|
||||
export interface PaginatedTableRef {
|
||||
refresh: () => void;
|
||||
@@ -10,9 +11,8 @@ export interface PaginatedTableRef {
|
||||
|
||||
interface PaginatedTableProps<TData> extends Omit<SortableTableProps<TData>, "data"> {
|
||||
prismaModel: keyof PrismaClient;
|
||||
filter?: Record<string, any>;
|
||||
filter?: Record<string, unknown>;
|
||||
rowsPerPage?: number;
|
||||
showEditButton?: boolean;
|
||||
searchFields?: string[];
|
||||
include?: Record<string, boolean>;
|
||||
strictQuery?: boolean;
|
||||
@@ -26,7 +26,6 @@ interface PaginatedTableProps<TData> extends Omit<SortableTableProps<TData>, "da
|
||||
export function PaginatedTable<TData>({
|
||||
prismaModel,
|
||||
rowsPerPage = 10,
|
||||
showEditButton = false,
|
||||
searchFields = [],
|
||||
filter,
|
||||
include,
|
||||
@@ -42,7 +41,6 @@ export function PaginatedTable<TData>({
|
||||
const [page, setPage] = useState(0);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm);
|
||||
const [orderBy, setOrderBy] = useState<Record<string, "asc" | "desc">>(
|
||||
restProps.initialOrderBy
|
||||
? restProps.initialOrderBy.reduce(
|
||||
@@ -60,16 +58,19 @@ export function PaginatedTable<TData>({
|
||||
prismaModel,
|
||||
rowsPerPage,
|
||||
page * rowsPerPage,
|
||||
debouncedSearchTerm,
|
||||
searchTerm,
|
||||
searchFields,
|
||||
filter,
|
||||
include,
|
||||
orderBy,
|
||||
strictQuery
|
||||
? restProps.columns
|
||||
.filter((col: any) => "accessorKey" in col)
|
||||
.map((col: any) => col.accessorKey)
|
||||
.reduce((acc: Record<string, any>, key: string) => {
|
||||
.filter(
|
||||
(col): col is { accessorKey: string } =>
|
||||
typeof (col as { accessorKey?: unknown }).accessorKey === "string",
|
||||
)
|
||||
.map((col) => col.accessorKey)
|
||||
.reduce<Record<string, boolean>>((acc, key) => {
|
||||
acc[key] = true;
|
||||
return acc;
|
||||
}, {})
|
||||
@@ -82,35 +83,20 @@ export function PaginatedTable<TData>({
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
RefreshTableData();
|
||||
}, [filter, orderBy]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
refresh: () => {
|
||||
RefreshTableData();
|
||||
},
|
||||
}));
|
||||
|
||||
const debounce = (func: Function, delay: number) => {
|
||||
let timer: NodeJS.Timeout;
|
||||
return (...args: any[]) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => func(...args), delay);
|
||||
};
|
||||
};
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
debounce((value: string) => {
|
||||
setDebouncedSearchTerm(value);
|
||||
}, 500),
|
||||
[],
|
||||
useDebounce(
|
||||
() => {
|
||||
RefreshTableData();
|
||||
},
|
||||
500,
|
||||
[searchTerm, page, rowsPerPage, orderBy, filter],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
RefreshTableData();
|
||||
}, [page, debouncedSearchTerm]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 m-4">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -122,7 +108,6 @@ export function PaginatedTable<TData>({
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
handleSearchChange(e.target.value);
|
||||
setPage(0); // Reset to first page on search
|
||||
}}
|
||||
className="input input-bordered w-full max-w-xs justify-end"
|
||||
@@ -134,7 +119,6 @@ export function PaginatedTable<TData>({
|
||||
<SortableTable
|
||||
data={data}
|
||||
prismaModel={prismaModel}
|
||||
showEditButton={showEditButton}
|
||||
setOrderBy={setOrderBy}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { toast } from "react-hot-toast";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { ReactNode, useState } from "react";
|
||||
|
||||
export function QueryProvider({ children }: { children: ReactNode }) {
|
||||
const [queryClient] = useState(
|
||||
|
||||
@@ -9,13 +9,11 @@ import {
|
||||
flexRender,
|
||||
} from "@tanstack/react-table";
|
||||
import { ArrowLeft, ArrowRight, ChevronDown, ChevronUp } from "lucide-react"; // Icons for sorting
|
||||
import Link from "next/link";
|
||||
import { PrismaClient } from "@repo/db";
|
||||
|
||||
export interface SortableTableProps<TData> {
|
||||
data: TData[];
|
||||
columns: ColumnDef<TData>[];
|
||||
showEditButton?: boolean;
|
||||
prismaModel?: keyof PrismaClient;
|
||||
setOrderBy?: (orderBy: Record<string, "asc" | "desc">) => void;
|
||||
initialOrderBy?: SortingState;
|
||||
@@ -26,28 +24,13 @@ export default function SortableTable<TData>({
|
||||
columns,
|
||||
initialOrderBy = [],
|
||||
prismaModel,
|
||||
showEditButton,
|
||||
setOrderBy,
|
||||
}: SortableTableProps<TData>) {
|
||||
const [sorting, setSorting] = useState<SortingState>(initialOrderBy);
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns: showEditButton
|
||||
? [
|
||||
...columns,
|
||||
{
|
||||
header: "Actions",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-1">
|
||||
<Link href={`/admin/${prismaModel as string}/${(row.original as any).id}`}>
|
||||
<button className="btn btn-sm">Edit</button>
|
||||
</Link>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
: columns,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
onSortingChange: setSorting,
|
||||
|
||||
@@ -15,7 +15,7 @@ export const Button = ({
|
||||
|
||||
return (
|
||||
<button
|
||||
{...(props as any)}
|
||||
{...props}
|
||||
className={cn("btn", props.className)}
|
||||
disabled={isLoadingState || props.disabled}
|
||||
onClick={async (e) => {
|
||||
@@ -27,7 +27,7 @@ export const Button = ({
|
||||
}}
|
||||
>
|
||||
{isLoadingState && <span className="loading loading-spinner loading-sm"></span>}
|
||||
{props.children as any}
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,32 +1,24 @@
|
||||
import DatePicker, { DatePickerProps, registerLocale } from "react-datepicker";
|
||||
import { Control, Controller, FieldValues, Path } from "react-hook-form";
|
||||
import { de } from "date-fns/locale";
|
||||
registerLocale("de", de);
|
||||
import { formatDate } from "date-fns";
|
||||
|
||||
interface DateInputProps<T extends FieldValues>
|
||||
extends Omit<DatePickerProps, "onChange" | "selected"> {
|
||||
control: Control<T>;
|
||||
name: Path<T>;
|
||||
}
|
||||
|
||||
export const DateInput = <T extends FieldValues>({
|
||||
control,
|
||||
name,
|
||||
export const DateInput = ({
|
||||
value,
|
||||
onChange,
|
||||
...props
|
||||
}: DateInputProps<T>) => {
|
||||
}: Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "onChange"> & {
|
||||
value?: Date | null;
|
||||
onChange?: (date: Date) => void;
|
||||
}) => {
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<DatePicker
|
||||
className="input input-bordered mt-2"
|
||||
locale={"de"}
|
||||
onChange={(date) => field.onChange(date)}
|
||||
selected={field.value}
|
||||
{...(props as any)}
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
type="datetime-local"
|
||||
className="input"
|
||||
value={formatDate(value || new Date(), "yyyy-MM-dd hh:mm")}
|
||||
onChange={(e) => {
|
||||
const date = e.target.value ? new Date(e.target.value) : null;
|
||||
if (!date) return;
|
||||
onChange?.(date);
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
import { DetailedHTMLProps, InputHTMLAttributes, ReactNode } from 'react';
|
||||
import { InputHTMLAttributes, ReactNode } from "react";
|
||||
|
||||
interface FormTextInputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
error: any;
|
||||
Svg: ReactNode;
|
||||
children?: ReactNode;
|
||||
error: ReactNode;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const FormTextInput = ({
|
||||
error,
|
||||
Svg,
|
||||
children,
|
||||
...props
|
||||
}: FormTextInputProps) => {
|
||||
return (
|
||||
<>
|
||||
<label className="input input-bordered flex items-center gap-2">
|
||||
{children}
|
||||
<input {...props} />
|
||||
</label>
|
||||
<p className="text-error">{error}</p>
|
||||
</>
|
||||
);
|
||||
export const FormTextInput = ({ error, children, ...props }: FormTextInputProps) => {
|
||||
return (
|
||||
<>
|
||||
<label className="input input-bordered flex items-center gap-2">
|
||||
{children}
|
||||
<input {...props} />
|
||||
</label>
|
||||
<p className="text-error">{error}</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import DatePicker, { DatePickerProps, registerLocale } from "react-datepicker";
|
||||
import { Control, Controller, FieldValues, Path, PathValue } from "react-hook-form";
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { DatePickerProps, registerLocale } from "react-datepicker";
|
||||
import { Control, Controller, FieldValues, Path } from "react-hook-form";
|
||||
import { de } from "date-fns/locale";
|
||||
import { useState } from "react";
|
||||
import { cn } from "@repo/shared-components";
|
||||
@@ -44,7 +45,7 @@ export const ListInput = <T extends FieldValues>({
|
||||
setValue("");
|
||||
}}
|
||||
type="button"
|
||||
onSubmit={(e) => false}
|
||||
onSubmit={() => false}
|
||||
>
|
||||
Hinzufügen
|
||||
</button>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
"use client";
|
||||
import MDEditor from "@uiw/react-md-editor";
|
||||
import { FieldValues, Path, RegisterOptions, UseFormReturn } from "react-hook-form";
|
||||
@@ -5,7 +6,7 @@ import { cn } from "@repo/shared-components";
|
||||
|
||||
interface MarkdownEditorProps<T extends FieldValues> {
|
||||
name: Path<T>;
|
||||
form: UseFormReturn<T>;
|
||||
form: UseFormReturn<any>;
|
||||
formOptions?: RegisterOptions<T>;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
"use client";
|
||||
import { FieldValues, Path, RegisterOptions, UseFormReturn } from "react-hook-form";
|
||||
import SelectTemplate, { Props as SelectTemplateProps, StylesConfig } from "react-select";
|
||||
import { cn } from "@repo/shared-components";
|
||||
import dynamic from "next/dynamic";
|
||||
import { CSSProperties } from "react";
|
||||
|
||||
interface SelectProps<T extends FieldValues> extends Omit<SelectTemplateProps, "form"> {
|
||||
label?: any;
|
||||
label?: React.ReactNode;
|
||||
name: Path<T>;
|
||||
form: UseFormReturn<T> | any;
|
||||
form: UseFormReturn<any>;
|
||||
formOptions?: RegisterOptions<T>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
|
||||
const customStyles: StylesConfig<any, false> = {
|
||||
type OptionType = { label: string; value: string };
|
||||
|
||||
const customStyles: StylesConfig<OptionType, false> = {
|
||||
control: (provided) => ({
|
||||
...provided,
|
||||
backgroundColor: "var(--color-base-100)",
|
||||
@@ -55,7 +56,6 @@ const SelectCom = <T extends FieldValues>({
|
||||
label = name,
|
||||
placeholder = label,
|
||||
form,
|
||||
formOptions,
|
||||
className,
|
||||
...inputProps
|
||||
}: SelectProps<T>) => {
|
||||
@@ -74,7 +74,6 @@ const SelectCom = <T extends FieldValues>({
|
||||
});
|
||||
}
|
||||
form.trigger(name);
|
||||
form.Dirty;
|
||||
}}
|
||||
value={
|
||||
(inputProps as any)?.isMulti
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { FieldValues, Path, RegisterOptions, UseFormReturn } from "react-hook-form";
|
||||
import { cn } from "@repo/shared-components";
|
||||
|
||||
interface InputProps<T extends FieldValues>
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "form"> {
|
||||
name: Path<T>;
|
||||
form: UseFormReturn<T>;
|
||||
form: UseFormReturn<any>;
|
||||
formOptions?: RegisterOptions<T>;
|
||||
label?: string;
|
||||
}
|
||||
@@ -13,7 +14,6 @@ export const Switch = <T extends FieldValues>({
|
||||
name,
|
||||
label = name,
|
||||
form,
|
||||
formOptions,
|
||||
className,
|
||||
...inputProps
|
||||
}: InputProps<T>) => {
|
||||
@@ -21,7 +21,12 @@ export const Switch = <T extends FieldValues>({
|
||||
<div className="form-control ">
|
||||
<label className="label cursor-pointer w-full">
|
||||
<span className={cn("label-text text-left w-full", className)}>{label}</span>
|
||||
<input type="checkbox" className={cn("toggle", className)} {...form.register(name)} />
|
||||
<input
|
||||
type="checkbox"
|
||||
className={cn("toggle", className)}
|
||||
{...form.register(name)}
|
||||
{...inputProps}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AuthOptions, getServerSession as getNextAuthServerSession } from "next-auth";
|
||||
import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import { DiscordAccount, prisma, User } from "@repo/db";
|
||||
import { prisma } from "@repo/db";
|
||||
import bcrypt from "bcryptjs";
|
||||
import oldUser from "./var.User.json";
|
||||
import { createNewUserFromOld, OldUser } from "../../../../types/oldUser";
|
||||
@@ -70,7 +70,7 @@ export const options: AuthOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
adapter: PrismaAdapter(prisma as any),
|
||||
adapter: PrismaAdapter(prisma),
|
||||
callbacks: {
|
||||
jwt: async ({ token, user }) => {
|
||||
if (user && "firstname" in user) {
|
||||
@@ -88,6 +88,7 @@ export const options: AuthOptions = {
|
||||
},
|
||||
});
|
||||
if (!dbUser) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return null as any;
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { prisma } from "@repo/db";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET(request: Request): Promise<NextResponse> {
|
||||
export async function GET(): Promise<NextResponse> {
|
||||
try {
|
||||
const config = await prisma.config.findFirst({
|
||||
orderBy: {
|
||||
|
||||
@@ -7,7 +7,6 @@ import "./globals.css";
|
||||
import { QueryProvider } from "_components/QueryClient";
|
||||
import { prisma } from "@repo/db";
|
||||
import React from "react";
|
||||
import { Error as ErrorComp } from "_components/Error";
|
||||
import { Maintenance } from "@repo/shared-components";
|
||||
|
||||
const geistSans = Geist({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { nextJsConfig } from "@repo/eslint-config/next-js";
|
||||
import nextJsConfig from "@repo/eslint-config/next-js";
|
||||
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
export default nextJsConfig;
|
||||
|
||||
@@ -19,7 +19,7 @@ export const sendMailByTemplate = async (
|
||||
| "email-verification"
|
||||
| "ban-notice"
|
||||
| "timeban-notice",
|
||||
data: any,
|
||||
data: unknown,
|
||||
) => {
|
||||
try {
|
||||
await fetch(`${process.env.NEXT_PUBLIC_HUB_SERVER_URL}/mail/template/${template}`, {
|
||||
|
||||
@@ -25,7 +25,7 @@ export const enrollUserInCourse = async (courseid: number | string, userid: numb
|
||||
);
|
||||
return enrollmentResponse;
|
||||
} catch (error) {
|
||||
return new Error("Failed to enroll user in course");
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const removeImports = require("next-remove-imports")();
|
||||
/* const removeImports = require("next-remove-imports")(); */
|
||||
/* const nextConfig = removeImports({}); */
|
||||
const nextConfig = {};
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
"clsx": "^2.1.1",
|
||||
"daisyui": "^5.0.43",
|
||||
"date-fns": "^4.1.0",
|
||||
"eslint": "^9.30.0",
|
||||
"eslint-config-next": "^15.3.4",
|
||||
"i": "^0.3.7",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
@@ -52,8 +51,13 @@
|
||||
"react-select": "^5.10.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"typescript": "^5.8.3",
|
||||
"zod": "^3.25.67",
|
||||
"zustand": "^5.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.30.0",
|
||||
"eslint": "^9.30.0",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.33.1"
|
||||
}
|
||||
}
|
||||
|
||||
1
apps/hub/types/next-auth.d.ts
vendored
1
apps/hub/types/next-auth.d.ts
vendored
@@ -1,4 +1,3 @@
|
||||
import NextAuth from "next-auth";
|
||||
import { User as IUser } from "@repo/db";
|
||||
|
||||
declare module "next-auth" {
|
||||
|
||||
12
apps/hub/types/prisma.d.ts
vendored
12
apps/hub/types/prisma.d.ts
vendored
@@ -1,14 +1,8 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { JsonArray, JsonObject } from "@prisma/client/runtime/library";
|
||||
/* import { JsonArray, JsonObject } from "@prisma/client/runtime/library";
|
||||
|
||||
declare module "@prisma/client" {
|
||||
export type InputJsonValue =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| JsonObject
|
||||
| JsonArray;
|
||||
export type InputJsonValue = string | number | boolean | null | JsonObject | JsonArray;
|
||||
|
||||
export type JsonValue = any; // Erzwingt Flexibilität
|
||||
}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user