diff --git a/apps/dispatch-server/routes/aircraft.ts b/apps/dispatch-server/routes/aircraft.ts
index c3ff5d44..fa665438 100644
--- a/apps/dispatch-server/routes/aircraft.ts
+++ b/apps/dispatch-server/routes/aircraft.ts
@@ -2,6 +2,7 @@ import {
AdminMessage,
getPublicUser,
MissionLog,
+ MissionSdsStatusLog,
NotificationPayload,
Prisma,
prisma,
@@ -130,6 +131,44 @@ router.patch("/:id", async (req, res) => {
}
});
+router.post("/:id/send-sds-message", async (req, res) => {
+ const { id } = req.params;
+ const { sdsMessage } = req.body as { sdsMessage: MissionSdsStatusLog };
+
+ if (!sdsMessage.data.stationId || !id) {
+ res.status(400).json({ error: "Missing aircraftId or stationId" });
+ return;
+ }
+
+ await prisma.mission.updateMany({
+ where: {
+ state: "running",
+ missionStationIds: {
+ has: sdsMessage.data.stationId,
+ },
+ },
+ data: {
+ missionLog: {
+ push: sdsMessage as unknown as Prisma.InputJsonValue,
+ },
+ },
+ });
+
+ io.to(
+ sdsMessage.data.direction === "to-lst" ? "dispatchers" : `station:${sdsMessage.data.stationId}`,
+ ).emit(sdsMessage.data.direction === "to-lst" ? "notification" : "sds-status", {
+ type: "station-status",
+ status: sdsMessage.data.status,
+ message: "SDS Status Message",
+ data: {
+ aircraftId: parseInt(id),
+ stationId: sdsMessage.data.stationId,
+ },
+ } as NotificationPayload);
+
+ res.sendStatus(204);
+});
+
// Kick a connectedAircraft by ID
router.delete("/:id", async (req, res) => {
const { id } = req.params;
diff --git a/apps/dispatch/Dockerfile b/apps/dispatch/Dockerfile
index 315d8d76..1236c9d6 100644
--- a/apps/dispatch/Dockerfile
+++ b/apps/dispatch/Dockerfile
@@ -1,12 +1,12 @@
FROM node:22-alpine AS base
-ARG NEXT_PUBLIC_DISPATCH_URL
-ARG NEXT_PUBLIC_DISPATCH_SERVER_URL
-ARG NEXT_PUBLIC_HUB_URL
-ARG NEXT_PUBLIC_DISPATCH_SERVICE_ID
-ARG NEXT_PUBLIC_LIVEKIT_URL
-ARG NEXT_PUBLIC_DISCORD_URL
-ARG NEXT_PUBLIC_OPENAIP_ACCESS
+ARG NEXT_PUBLIC_DISPATCH_URL="http://localhost:3001"
+ARG NEXT_PUBLIC_DISPATCH_SERVER_URL="http://localhost:4001"
+ARG NEXT_PUBLIC_HUB_URL="http://localhost:3002"
+ARG NEXT_PUBLIC_DISPATCH_SERVICE_ID="1"
+ARG NEXT_PUBLIC_LIVEKIT_URL="http://localhost:7880"
+ARG NEXT_PUBLIC_DISCORD_URL="https://discord.com"
+ARG NEXT_PUBLIC_OPENAIP_ACCESS=""
ENV NEXT_PUBLIC_DISPATCH_SERVER_URL=$NEXT_PUBLIC_DISPATCH_SERVER_URL
ENV NEXT_PUBLIC_DISPATCH_URL=$NEXT_PUBLIC_DISPATCH_URL
@@ -16,13 +16,13 @@ ENV NEXT_PUBLIC_LIVEKIT_URL=$NEXT_PUBLIC_LIVEKIT_URL
ENV NEXT_PUBLIC_OPENAIP_ACCESS=$NEXT_PUBLIC_OPENAIP_ACCESS
ENV NEXT_PUBLIC_DISCORD_URL=$NEXT_PUBLIC_DISCORD_URL
+FROM base AS builder
+
ENV PNPM_HOME="/usr/local/pnpm"
ENV PATH="${PNPM_HOME}:${PATH}"
RUN corepack enable && corepack prepare pnpm@latest --activate
RUN pnpm add -g turbo@^2.5
-
-FROM base AS builder
RUN apk update
RUN apk add --no-cache libc6-compat
@@ -31,12 +31,20 @@ WORKDIR /usr/app
RUN echo "NEXT_PUBLIC_HUB_URL is: $NEXT_PUBLIC_HUB_URL"
RUN echo "NEXT_PUBLIC_DISPATCH_SERVICE_ID is: $NEXT_PUBLIC_DISPATCH_SERVICE_ID"
RUN echo "NEXT_PUBLIC_DISPATCH_SERVER_URL is: $NEXT_PUBLIC_DISPATCH_SERVER_URL"
+RUN echo "NEXT_PUBLIC_LIVEKIT_URL is: $NEXT_PUBLIC_LIVEKIT_URL"
COPY . .
RUN turbo prune dispatch --docker
FROM base AS installer
+
+ENV PNPM_HOME="/usr/local/pnpm"
+ENV PATH="${PNPM_HOME}:${PATH}"
+RUN corepack enable && corepack prepare pnpm@latest --activate
+
+RUN pnpm add -g turbo@^2.5
+
RUN apk update
RUN apk add --no-cache libc6-compat
@@ -50,19 +58,22 @@ COPY --from=builder /usr/app/out/full/ .
RUN turbo run build
-FROM base AS runner
+FROM node:22-alpine AS runner
WORKDIR /usr/app
# Don't run production as root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
-USER nextjs
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
-COPY --from=installer --chown=nextjs:nodejs /usr/app/ ./
+COPY --from=installer --chown=nextjs:nodejs /usr/app/apps/dispatch/.next/standalone ./
+COPY --from=installer --chown=nextjs:nodejs /usr/app/apps/dispatch/.next/static ./apps/dispatch/.next/static
+COPY --from=installer --chown=nextjs:nodejs /usr/app/apps/dispatch/public ./apps/dispatch/public
+
+USER nextjs
# Expose the application port
-EXPOSE 3001
+EXPOSE 3000
-CMD ["pnpm", "--dir", "apps/dispatch", "run", "start"]
\ No newline at end of file
+CMD ["node", "apps/dispatch/server.js"]
\ No newline at end of file
diff --git a/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Connection.tsx b/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Connection.tsx
index f1450f19..039d7ac2 100644
--- a/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Connection.tsx
+++ b/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Connection.tsx
@@ -14,7 +14,7 @@ export const ConnectionBtn = () => {
const connection = useDispatchConnectionStore((state) => state);
const [form, setForm] = useState({
logoffTime: "",
- selectedZone: "LST_01",
+ selectedZone: "VAR_LST_RD_01",
ghostMode: false,
});
const changeDispatcherMutation = useMutation({
diff --git a/apps/dispatch/app/(app)/pilot/_components/mrt/Base.tsx b/apps/dispatch/app/(app)/pilot/_components/mrt/Base.tsx
new file mode 100644
index 00000000..169b7d24
--- /dev/null
+++ b/apps/dispatch/app/(app)/pilot/_components/mrt/Base.tsx
@@ -0,0 +1,29 @@
+import { useEffect } from "react"; // ...existing code...
+import { useMrtStore } from "_store/pilot/MrtStore";
+import Image from "next/image";
+import DAY_BASE_IMG from "./images/Base_NoScreen_Day.png";
+import NIGHT_BASE_IMG from "./images/Base_NoScreen_Night.png";
+
+export const MrtBase = () => {
+ const { nightMode, setNightMode, page } = useMrtStore((state) => state);
+
+ useEffect(() => {
+ const checkNightMode = () => {
+ const currentHour = new Date().getHours();
+ setNightMode(currentHour >= 22 || currentHour < 8);
+ };
+
+ checkNightMode(); // Initial check
+ const intervalId = setInterval(checkNightMode, 60000); // Check every minute
+
+ return () => clearInterval(intervalId); // Cleanup on unmount
+ }, [setNightMode]); // ...existing code...
+
+ return (
+
+ );
+};
diff --git a/apps/dispatch/app/(app)/pilot/_components/mrt/MRT.png b/apps/dispatch/app/(app)/pilot/_components/mrt/MRT.png
deleted file mode 100644
index a9c552b1..00000000
Binary files a/apps/dispatch/app/(app)/pilot/_components/mrt/MRT.png and /dev/null differ
diff --git a/apps/dispatch/app/(app)/pilot/_components/mrt/MRT_MESSAGE.png b/apps/dispatch/app/(app)/pilot/_components/mrt/MRT_MESSAGE.png
deleted file mode 100644
index a0e80ae6..00000000
Binary files a/apps/dispatch/app/(app)/pilot/_components/mrt/MRT_MESSAGE.png and /dev/null differ
diff --git a/apps/dispatch/app/(app)/pilot/_components/mrt/Mrt.tsx b/apps/dispatch/app/(app)/pilot/_components/mrt/Mrt.tsx
index 5d271921..0fda6281 100644
--- a/apps/dispatch/app/(app)/pilot/_components/mrt/Mrt.tsx
+++ b/apps/dispatch/app/(app)/pilot/_components/mrt/Mrt.tsx
@@ -1,22 +1,9 @@
import { CSSProperties } from "react";
-import MrtImage from "./MRT.png";
-import MrtMessageImage from "./MRT_MESSAGE.png";
-import { useButtons } from "./useButtons";
-import { useSounds } from "./useSounds";
import "./mrt.css";
-import Image from "next/image";
-import { useMrtStore } from "_store/pilot/MrtStore";
-
-const MRT_BUTTON_STYLES: CSSProperties = {
- cursor: "pointer",
- zIndex: "9999",
- backgroundColor: "transparent",
- border: "none",
-};
-const MRT_DISPLAYLINE_STYLES: CSSProperties = {
- color: "white",
- zIndex: 1,
-};
+import { MrtBase } from "./Base";
+import { MrtDisplay } from "./MrtDisplay";
+import { MrtButtons } from "./MrtButtons";
+import { MrtPopups } from "./MrtPopups";
export interface DisplayLineProps {
lineStyle?: CSSProperties;
@@ -27,45 +14,7 @@ export interface DisplayLineProps {
textSize: "1" | "2" | "3" | "4";
}
-const DisplayLine = ({
- style = {},
- textLeft,
- textMid,
- textRight,
- textSize,
- lineStyle,
-}: DisplayLineProps) => {
- const INNER_TEXT_PARTS: CSSProperties = {
- fontFamily: "Melder",
- flex: "1",
- flexBasis: "auto",
- overflowWrap: "break-word",
- ...lineStyle,
- };
-
- return (
-
- {textLeft}
- {textMid}
- {textRight}
-
- );
-};
-
export const Mrt = () => {
- useSounds();
- const { handleButton } = useButtons();
- const { lines, page } = useMrtStore((state) => state);
-
return (
{
maxHeight: "100%",
maxWidth: "100%",
color: "white",
- gridTemplateColumns: "21.83% 4.43% 24.42% 18.08% 5.93% 1.98% 6.00% 1.69% 6.00% 9.35%",
- gridTemplateRows: "21.58% 11.87% 3.55% 5.00% 6.84% 0.53% 3.03% 11.84% 3.55% 11.84% 20.39%",
+ gridTemplateColumns:
+ "9.75% 4.23% 8.59% 7.30% 1.16% 7.30% 1.23% 7.16% 1.09% 7.30% 3.68% 4.23% 5.59% 6.07% 1.91% 6.07% 1.84% 6.21% 9.28%",
+ gridTemplateRows:
+ "21.55% 11.83% 3.55% 2.50% 9.46% 2.76% 0.66% 4.99% 6.83% 3.55% 1.97% 9.99% 4.20% 11.04% 5.12%",
}}
>
- {page !== "sds" && (
-
- )}
- {page === "sds" && (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {lines[0] && (
-
- )}
- {lines[1] && (
-
- )}
- {lines[2] && (
-
- )}
- {lines[3] && (
-
- )}
+
+
+
+
);
};
diff --git a/apps/dispatch/app/(app)/pilot/_components/mrt/MrtButtons.tsx b/apps/dispatch/app/(app)/pilot/_components/mrt/MrtButtons.tsx
new file mode 100644
index 00000000..a7942466
--- /dev/null
+++ b/apps/dispatch/app/(app)/pilot/_components/mrt/MrtButtons.tsx
@@ -0,0 +1,150 @@
+import { CSSProperties, useRef } from "react";
+import { useButtons } from "./useButtons";
+
+const MRT_BUTTON_STYLES: CSSProperties = {
+ cursor: "pointer",
+ zIndex: "9999",
+ backgroundColor: "transparent",
+ border: "none",
+};
+
+interface MrtButtonProps {
+ onClick: () => void;
+ onHold?: () => void;
+ style: CSSProperties;
+}
+
+const MrtButton = ({ onClick, onHold, style }: MrtButtonProps) => {
+ const timeoutRef = useRef(null);
+
+ const handleMouseDown = () => {
+ if (!onHold) return;
+ timeoutRef.current = setTimeout(handleTimeoutExpired, 500);
+ };
+
+ const handleTimeoutExpired = () => {
+ timeoutRef.current = null;
+ if (onHold) {
+ onHold();
+ }
+ };
+
+ const handleMouseUp = () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ onClick();
+ }
+ };
+
+ return (
+