feat: Implement connected user API and integrate chat and report components

- Added API routes for fetching connected users, keywords, missions, and stations.
- Created a new QueryProvider component for managing query states and socket events.
- Introduced connection stores for dispatch and pilot, managing socket connections and states.
- Updated Prisma schema for connected aircraft model.
- Enhanced UI with toast notifications for status updates and chat interactions.
- Implemented query functions for fetching connected users and keywords with error handling.
This commit is contained in:
PxlLoewe
2025-05-07 00:43:45 -07:00
parent 152b3d4689
commit 50f42e99d3
49 changed files with 1040 additions and 701 deletions

View File

@@ -1,5 +1,5 @@
import { create } from "zustand";
import { socket } from "../dispatch/socket";
import { dispatchSocket } from "../../dispatch/socket";
interface ConnectionStore {
status: "connected" | "disconnected" | "connecting" | "error";
@@ -20,11 +20,11 @@ export const useDispatchConnectionStore = create<ConnectionStore>((set) => ({
connect: async (uid, selectedZone, logoffTime) =>
new Promise((resolve) => {
set({ status: "connecting", message: "" });
socket.auth = { uid };
dispatchSocket.auth = { uid };
set({ selectedZone });
socket.connect();
socket.once("connect", () => {
socket.emit("connect-dispatch", {
dispatchSocket.connect();
dispatchSocket.once("connect", () => {
dispatchSocket.emit("connect-dispatch", {
logoffTime,
selectedZone,
});
@@ -32,26 +32,26 @@ export const useDispatchConnectionStore = create<ConnectionStore>((set) => ({
});
}),
disconnect: () => {
socket.disconnect();
dispatchSocket.disconnect();
},
}));
socket.on("connect", () => {
dispatchSocket.on("connect", () => {
useDispatchConnectionStore.setState({ status: "connected", message: "" });
});
socket.on("connect_error", (err) => {
dispatchSocket.on("connect_error", (err) => {
useDispatchConnectionStore.setState({
status: "error",
message: err.message,
});
});
socket.on("disconnect", () => {
dispatchSocket.on("disconnect", () => {
useDispatchConnectionStore.setState({ status: "disconnected", message: "" });
});
socket.on("force-disconnect", (reason: string) => {
dispatchSocket.on("force-disconnect", (reason: string) => {
console.log("force-disconnect", reason);
useDispatchConnectionStore.setState({
status: "disconnected",

View File

@@ -1,6 +1,7 @@
import { create } from "zustand";
import { ChatMessage } from "@repo/db";
import { socket } from "dispatch/socket";
import { dispatchSocket } from "dispatch/socket";
import { pilotSocket } from "pilot/socket";
interface ChatStore {
reportTabOpen: boolean;
@@ -33,7 +34,7 @@ export const useLeftMenuStore = create<ChatStore>((set, get) => ({
chats: {},
sendMessage: (userId: string, message: string) => {
return new Promise((resolve, reject) => {
socket.emit(
dispatchSocket.emit(
"send-message",
{ userId, message },
({ error }: { error?: string }) => {
@@ -96,7 +97,16 @@ export const useLeftMenuStore = create<ChatStore>((set, get) => ({
},
}));
socket.on(
dispatchSocket.on(
"chat-message",
({ userId, message }: { userId: string; message: ChatMessage }) => {
const store = useLeftMenuStore.getState();
console.log("chat-message", userId, message);
// Update the chat store with the new message
store.addMessage(userId, message);
},
);
pilotSocket.on(
"chat-message",
({ userId, message }: { userId: string; message: ChatMessage }) => {
const store = useLeftMenuStore.getState();

View File

@@ -1,73 +0,0 @@
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";
import axios from "axios";
interface MissionStore {
missions: Mission[];
setMissions: (missions: Mission[]) => void;
getMissions: () => Promise<undefined>;
createMission: (mission: MissionOptionalDefaults) => Promise<Mission>;
deleteMission: (id: number) => Promise<void>;
editMission: (id: number, mission: Partial<Mission>) => Promise<void>;
}
export const useMissionsStore = create<MissionStore>((set) => ({
missions: [],
setMissions: (missions) => set({ missions }),
createMission: async (mission) => {
const { data } = await serverApi.put<Mission>("/mission", mission);
set((state) => ({ missions: [...state.missions, data] }));
return data;
},
editMission: async (id, mission) => {
const { data, status } = await serverApi.patch<Mission>(
`/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 { data } = await serverApi.post<Mission[]>("/mission", {
filter: {
OR: [{ state: "draft" }, { state: "running" }],
} as Prisma.MissionWhereInput,
});
set({ missions: data });
return undefined;
},
}));
useMissionsStore
.getState()
.getMissions()
.then(() => {
console.log("Missions loaded");
});

View File

@@ -0,0 +1,63 @@
import { create } from "zustand";
import { dispatchSocket } from "../../dispatch/socket";
import { Station } from "@repo/db";
import { pilotSocket } from "pilot/socket";
interface ConnectionStore {
status: "connected" | "disconnected" | "connecting" | "error";
message: string;
selectedStation: Station | null;
connect: (
uid: string,
stationId: string,
logoffTime: string,
station: Station,
) => Promise<void>;
disconnect: () => void;
}
export const useDispatchConnectionStore = create<ConnectionStore>((set) => ({
status: "disconnected",
message: "",
selectedStation: null,
connect: async (uid, stationId, logoffTime, station) =>
new Promise((resolve) => {
set({ status: "connecting", message: "", selectedStation: station });
dispatchSocket.auth = { uid };
dispatchSocket.connect();
dispatchSocket.once("connect", () => {
dispatchSocket.emit("connect-pilot", {
logoffTime,
stationId,
});
resolve();
});
}),
disconnect: () => {
dispatchSocket.disconnect();
},
}));
dispatchSocket.on("connect", () => {
pilotSocket.disconnect();
useDispatchConnectionStore.setState({ status: "connected", message: "" });
});
dispatchSocket.on("connect_error", (err) => {
useDispatchConnectionStore.setState({
status: "error",
message: err.message,
});
});
dispatchSocket.on("disconnect", () => {
useDispatchConnectionStore.setState({ status: "disconnected", message: "" });
});
dispatchSocket.on("force-disconnect", (reason: string) => {
console.log("force-disconnect", reason);
useDispatchConnectionStore.setState({
status: "disconnected",
message: reason,
});
});

View File

@@ -1,7 +1,7 @@
import { create } from "zustand";
import { socket } from "../dispatch/socket";
export const stationStore = create((set) => {
export const useStationStore = create((set) => {
return {
stations: [],
setStations: (stations: any) => set({ stations }),