From 2d5c795a07c412c15b8c730d62b63034a87cd056 Mon Sep 17 00:00:00 2001 From: Stedoss <29103029+Stedoss@users.noreply.github.com> Date: Mon, 27 Jul 2026 23:12:59 +0100 Subject: [PATCH] Add ability to show and create mission launches --- src/mission-control-client/package-lock.json | 24 +++ src/mission-control-client/package.json | 2 + .../src/api/missionLaunches.ts | 34 ++++ .../src/api/missions.ts | 2 + .../components/MissionLaunchStatusBadge.tsx | 18 ++ src/mission-control-client/src/main.tsx | 1 + .../src/routes/missions.tsx | 162 ++++++++++++++++-- 7 files changed, 231 insertions(+), 12 deletions(-) create mode 100644 src/mission-control-client/src/api/missionLaunches.ts create mode 100644 src/mission-control-client/src/components/MissionLaunchStatusBadge.tsx diff --git a/src/mission-control-client/package-lock.json b/src/mission-control-client/package-lock.json index 9c9593a..502b97d 100644 --- a/src/mission-control-client/package-lock.json +++ b/src/mission-control-client/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@mantine/core": "^8.3.18", + "@mantine/dates": "^8.3.18", "@mantine/form": "^8.3.18", "@mantine/hooks": "^8.3.18", "@mantine/notifications": "^8.3.18", @@ -16,6 +17,7 @@ "@tanstack/react-query": "^5.101.3", "@tanstack/react-router": "^1.170.18", "axios": "^1.18.1", + "dayjs": "^1.11.21", "react": "^19.2.7", "react-dom": "^19.2.7" }, @@ -1384,6 +1386,22 @@ "react-dom": "^18.x || ^19.x" } }, + "node_modules/@mantine/dates": { + "version": "8.3.18", + "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-8.3.18.tgz", + "integrity": "sha512-FHx5teJOhupI0gO2o5evtVYQEdqOjayOkLRhEQfB5Nc5DvcysfPfmNILGkc1Nrp9ZQeQWKLT9qr+CkcCXwHOaw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "@mantine/core": "8.3.18", + "@mantine/hooks": "8.3.18", + "dayjs": ">=1.0.0", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, "node_modules/@mantine/form": { "version": "8.3.18", "resolved": "https://registry.npmjs.org/@mantine/form/-/form-8.3.18.tgz", @@ -3199,6 +3217,12 @@ "node": ">=18" } }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", diff --git a/src/mission-control-client/package.json b/src/mission-control-client/package.json index 0594890..29d4eff 100644 --- a/src/mission-control-client/package.json +++ b/src/mission-control-client/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "@mantine/core": "^8.3.18", + "@mantine/dates": "^8.3.18", "@mantine/form": "^8.3.18", "@mantine/hooks": "^8.3.18", "@mantine/notifications": "^8.3.18", @@ -23,6 +24,7 @@ "@tanstack/react-query": "^5.101.3", "@tanstack/react-router": "^1.170.18", "axios": "^1.18.1", + "dayjs": "^1.11.21", "react": "^19.2.7", "react-dom": "^19.2.7" }, diff --git a/src/mission-control-client/src/api/missionLaunches.ts b/src/mission-control-client/src/api/missionLaunches.ts new file mode 100644 index 0000000..ce85e77 --- /dev/null +++ b/src/mission-control-client/src/api/missionLaunches.ts @@ -0,0 +1,34 @@ +import { apiClient } from './client'; + +export const MissionLaunchStatuses = [ + 'Pending', + 'Delayed', + 'Launching', + 'Launched', + 'Cancelled', +] as const; + +export type MissionLaunchStatus = (typeof MissionLaunchStatuses)[number]; + +export type MissionLaunch = { + id: string; + scheduledFor: string; + status: MissionLaunchStatus; + createdAt: string; +}; + +export type CreateMissionLaunchRequest = { + missionId: string; + scheduledFor: string; + status: MissionLaunchStatus; +}; + +export async function createMissionLaunch( + request: CreateMissionLaunchRequest, +): Promise { + const response = await apiClient.post( + `/missions/${request.missionId}/launch`, + request, + ); + return response.data; +} diff --git a/src/mission-control-client/src/api/missions.ts b/src/mission-control-client/src/api/missions.ts index 454b319..542db50 100644 --- a/src/mission-control-client/src/api/missions.ts +++ b/src/mission-control-client/src/api/missions.ts @@ -1,4 +1,5 @@ import { apiClient } from './client'; +import type { MissionLaunch } from './missionLaunches'; export type MissionStatus = 'Planned' | 'Active' | 'Completed'; @@ -8,6 +9,7 @@ export type Mission = { description: string | null; status: MissionStatus; createdAt: string; + missionLaunch: MissionLaunch | null; }; export type CreateMissionRequest = { diff --git a/src/mission-control-client/src/components/MissionLaunchStatusBadge.tsx b/src/mission-control-client/src/components/MissionLaunchStatusBadge.tsx new file mode 100644 index 0000000..2a802d5 --- /dev/null +++ b/src/mission-control-client/src/components/MissionLaunchStatusBadge.tsx @@ -0,0 +1,18 @@ +import { Badge } from '@mantine/core'; +import type { MissionLaunchStatus } from '../api/missionLaunches'; + +const statusColors: Record = { + Pending: 'blue', + Delayed: 'orange', + Launching: 'green', + Launched: 'gray', + Cancelled: 'black', +}; + +export function MissionLaunchStatusBadge({ + status, +}: { + status: MissionLaunchStatus; +}) { + return {status}; +} diff --git a/src/mission-control-client/src/main.tsx b/src/mission-control-client/src/main.tsx index b45934a..53488ac 100644 --- a/src/mission-control-client/src/main.tsx +++ b/src/mission-control-client/src/main.tsx @@ -1,5 +1,6 @@ import '@mantine/core/styles.css'; import '@mantine/notifications/styles.css'; +import '@mantine/dates/styles.css'; import { MantineProvider } from '@mantine/core'; import { Notifications } from '@mantine/notifications'; import { QueryClientProvider } from '@tanstack/react-query'; diff --git a/src/mission-control-client/src/routes/missions.tsx b/src/mission-control-client/src/routes/missions.tsx index 63014ea..21dcbb3 100644 --- a/src/mission-control-client/src/routes/missions.tsx +++ b/src/mission-control-client/src/routes/missions.tsx @@ -4,6 +4,8 @@ import { Group, Loader, Paper, + Popover, + Select, Stack, Table, Text, @@ -11,11 +13,20 @@ import { TextInput, Title, } from '@mantine/core'; +import { DateTimePicker } from '@mantine/dates'; import { useForm } from '@mantine/form'; import { notifications } from '@mantine/notifications'; +import { IconPlus } from '@tabler/icons-react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; -import { createMission, listMissions } from '../api/missions'; +import { useState } from 'react'; +import { + createMissionLaunch, + MissionLaunchStatuses, + type MissionLaunchStatus, +} from '../api/missionLaunches'; +import { createMission, listMissions, type Mission } from '../api/missions'; +import { MissionLaunchStatusBadge } from '../components/MissionLaunchStatusBadge'; import { MissionStatusBadge } from '../components/MissionStatusBadge'; export const Route = createFileRoute('/missions')({ @@ -97,6 +108,81 @@ function CreateMissionForm() { ); } +function CreateMissionLaunchForm({ missionId }: { missionId: string }) { + const queryClient = useQueryClient(); + + const form = useForm({ + mode: 'uncontrolled', + initialValues: { scheduledFor: '', status: '' }, + validate: { + scheduledFor: (value) => + value.length === 0 ? 'Scheduled For is required' : null, + status: (value) => + value.trim().length === 0 ? 'Status is required' : null, + }, + }); + + const createMissionLaunchMutation = useMutation({ + mutationFn: createMissionLaunch, + onSuccess: () => { + notifications.show({ + title: 'Mission launch created', + message: `Mission Launch created!`, + color: 'green', + }); + form.reset(); + return queryClient.invalidateQueries({ queryKey: ['missions'] }); + }, + onError: () => + notifications.show({ + title: 'Something went wrong', + message: 'The mission launch could not be created.', + color: 'red', + }), + }); + + return ( + +
+ createMissionLaunchMutation.mutate({ + missionId, + scheduledFor: new Date(values.scheduledFor).toISOString(), + status: values.status as MissionLaunchStatus, + }), + )} + > + + + + + + + +
+
+ ); +} + function MissionsTable() { const missionsQuery = useQuery({ queryKey: ['missions'], @@ -119,6 +205,7 @@ function MissionsTable() { + Name Description Status @@ -126,19 +213,70 @@ function MissionsTable() { {missionsQuery.data.map((mission) => ( - - {mission.name} - - - {mission.description} - - - - - - + ))}
); } + +function MissionsTableRow({ mission }: { mission: Mission }) { + const [expanded, setExpanded] = useState(false); + + return ( + <> + + + + + {mission.name} + + + {mission.description} + + + + + + + {expanded === true ? ( + <> + + + + + + Scheduled For + Status + + + + {mission.missionLaunch?.scheduledFor} + + + + +
+
+
+ + + + + + + + + + + + + + ) : null} + + ); +}