Implement flight deletion

This commit is contained in:
april 2024-01-05 14:54:44 -06:00
parent 40108d1070
commit 654393bab8
6 changed files with 356 additions and 275 deletions

View File

@ -37,6 +37,7 @@ import {
Stack, Stack,
Title, Title,
} from "@mantine/core"; } from "@mantine/core";
import { ModalsProvider } from "@mantine/modals";
import { IconRocket } from "@tabler/icons-react"; import { IconRocket } from "@tabler/icons-react";
import { AuthProvider } from "./util/auth"; import { AuthProvider } from "./util/auth";
@ -159,12 +160,14 @@ export default function App() {
<HydrationBoundary state={dehydratedState}> <HydrationBoundary state={dehydratedState}>
<ApiProvider apiUrl={data.ENV.TAILFIN_API_URL}> <ApiProvider apiUrl={data.ENV.TAILFIN_API_URL}>
<MantineProvider theme={{ primaryColor: "violet" }}> <MantineProvider theme={{ primaryColor: "violet" }}>
<ModalsProvider>
<AuthProvider> <AuthProvider>
<Outlet /> <Outlet />
<ScrollRestoration /> <ScrollRestoration />
<Scripts /> <Scripts />
<LiveReload /> <LiveReload />
</AuthProvider> </AuthProvider>
</ModalsProvider>
</MantineProvider> </MantineProvider>
</ApiProvider> </ApiProvider>
<ReactQueryDevtools initialIsOpen={false} /> <ReactQueryDevtools initialIsOpen={false} />

View File

@ -2,21 +2,31 @@ import { VerticalLogItem } from "@/ui/display/log-item";
import ErrorDisplay from "@/ui/error-display"; import ErrorDisplay from "@/ui/error-display";
import { useApi } from "@/util/api"; import { useApi } from "@/util/api";
import { import {
ActionIcon,
Center, Center,
Container, Container,
Grid, Grid,
Group,
Loader, Loader,
ScrollArea, ScrollArea,
Stack, Stack,
Title, Title,
Tooltip,
Text,
Modal,
Button,
} from "@mantine/core"; } from "@mantine/core";
import { useParams } from "@remix-run/react"; import { useDisclosure } from "@mantine/hooks";
import { useQuery } from "@tanstack/react-query"; import { modals } from "@mantine/modals";
import { useNavigate, useParams } from "@remix-run/react";
import { IconPencil, IconTrash } from "@tabler/icons-react";
import { useMutation, useQuery } from "@tanstack/react-query";
export default function Flight() { export default function Flight() {
const params = useParams(); const params = useParams();
const client = useApi(); const client = useApi();
const navigate = useNavigate();
const flight = useQuery({ const flight = useQuery({
queryKey: [params.id], queryKey: [params.id],
@ -24,10 +34,48 @@ export default function Flight() {
await client.get(`/flights/${params.id}`).then((res) => res.data), await client.get(`/flights/${params.id}`).then((res) => res.data),
}); });
const [deleteOpened, { open: openDelete, close: closeDelete }] =
useDisclosure(false);
const deleteFlight = useMutation({
mutationFn: async () =>
await client.delete(`/flights/${params.id}`).then((res) => res.data),
onSuccess: () => {
navigate("/logbook/flights");
},
});
const log = flight.data; const log = flight.data;
return ( return (
// <Container> <>
<Modal
opened={deleteOpened}
onClose={closeDelete}
title="Delete Flight?"
centered
>
<Stack>
<Text>
Are you sure you want to delete this flight? This action cannot be
undone.
</Text>
{deleteFlight.isError ? (
<Text c="red" fw={700}>
{deleteFlight.error.message}
</Text>
) : null}
<Group justify="flex-end">
<Button color="red" onClick={() => deleteFlight.mutate()}>
Delete
</Button>
<Button color="gray" onClick={closeDelete}>
Cancel
</Button>
</Group>
</Stack>
</Modal>
<Container>
<Stack h="calc(100vh-95px)"> <Stack h="calc(100vh-95px)">
{flight.isError ? ( {flight.isError ? (
<Center h="calc(100vh - 95px)"> <Center h="calc(100vh - 95px)">
@ -39,9 +87,28 @@ export default function Flight() {
</Center> </Center>
) : flight.data ? ( ) : flight.data ? (
<> <>
<Title order={3} py="lg" style={{ textAlign: "center" }}> <Group justify="space-between" px="xl">
<Title order={2} py="lg" style={{ textAlign: "center" }}>
Flight Log Flight Log
</Title> </Title>
<Group>
<Tooltip label="Edit Flight">
<ActionIcon variant="subtle" size="lg">
<IconPencil />
</ActionIcon>
</Tooltip>
<Tooltip label="Delete Flight">
<ActionIcon
variant="subtle"
size="lg"
color="red"
onClick={openDelete}
>
<IconTrash />
</ActionIcon>
</Tooltip>
</Group>
</Group>
<ScrollArea h="calc(100vh - 95px - 110px)" m="0" p="0"> <ScrollArea h="calc(100vh - 95px - 110px)" m="0" p="0">
<Container h="100%"> <Container h="100%">
<Grid justify="center"> <Grid justify="center">
@ -49,7 +116,10 @@ export default function Flight() {
<VerticalLogItem label="Date" content={log.date} date /> <VerticalLogItem label="Date" content={log.date} date />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem label="Aircraft" content={log.aircraft} /> <VerticalLogItem
label="Aircraft"
content={log.aircraft}
/>
</Grid.Col> </Grid.Col>
{log.waypoint_from || log.waypoint_to ? ( {log.waypoint_from || log.waypoint_to ? (
<> <>
@ -259,6 +329,7 @@ export default function Flight() {
</Center> </Center>
)} )}
</Stack> </Stack>
// </Container> </Container>
</>
); );
} }

View File

@ -5,8 +5,9 @@ import {
Fieldset, Fieldset,
Group, Group,
NumberInput, NumberInput,
ScrollAreaAutosize, ScrollArea,
Stack, Stack,
Text,
TextInput, TextInput,
Textarea, Textarea,
Title, Title,
@ -23,7 +24,6 @@ import ListInput from "@/ui/form/list-input";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useApi } from "@/util/api"; import { useApi } from "@/util/api";
import { useNavigate } from "@remix-run/react"; import { useNavigate } from "@remix-run/react";
import { useAuth } from "@/util/auth";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
export default function NewFlight() { export default function NewFlight() {
@ -82,24 +82,18 @@ export default function NewFlight() {
const client = useApi(); const client = useApi();
const { clearUser } = useAuth();
const createFlight = useMutation({ const createFlight = useMutation({
mutationFn: async (values: FlightFormSchema) => { mutationFn: async (values: FlightFormSchema) => {
const newFlight = flightCreateHelper(values); const newFlight = flightCreateHelper(values);
if (newFlight) {
const res = await client.post("/flights", newFlight); const res = await client.post("/flights", newFlight);
return res.data; return res.data;
}
throw new Error("Flight creation failed");
}, },
retry: (failureCount, error: AxiosError) => { retry: (failureCount, error: AxiosError) => {
return !error || error.response?.status !== 401; return !error || error.response?.status !== 401;
}, },
onError: (error: AxiosError) => {
console.log(error);
if (error.response?.status === 401) {
clearUser();
navigate("/login");
}
},
onSuccess: async (data: { id: string }) => { onSuccess: async (data: { id: string }) => {
await queryClient.invalidateQueries({ queryKey: ["flights-list"] }); await queryClient.invalidateQueries({ queryKey: ["flights-list"] });
navigate(`/logbook/flights/${data.id}`); navigate(`/logbook/flights/${data.id}`);
@ -112,7 +106,7 @@ export default function NewFlight() {
<Title order={2}>New Flight</Title> <Title order={2}>New Flight</Title>
<form onSubmit={form.onSubmit((values) => createFlight.mutate(values))}> <form onSubmit={form.onSubmit((values) => createFlight.mutate(values))}>
<ScrollAreaAutosize mah="calc(100vh - 95px - 110px)"> <ScrollArea.Autosize mah="calc(100vh - 95px - 110px)">
<Container> <Container>
{/* Date and Aircraft */} {/* Date and Aircraft */}
@ -347,9 +341,14 @@ export default function NewFlight() {
/> />
</Fieldset> </Fieldset>
</Container> </Container>
</ScrollAreaAutosize> </ScrollArea.Autosize>
<Group justify="flex-end" mt="md"> <Group justify="flex-end" mt="md">
{createFlight.isError ? (
<Text c="red" fw={700}>
{createFlight.error.message}
</Text>
) : null}
<Button type="submit" leftSection={<IconPencil />}> <Button type="submit" leftSection={<IconPencil />}>
Create Create
</Button> </Button>

View File

@ -37,7 +37,7 @@ export default function ListInput({
}; };
return ( return (
<PillsInput label={label}> <PillsInput label={label} description="Press enter or comma to add item">
<Pill.Group> <Pill.Group>
{(form.getTransformedValues()[field_key] as string[]).map( {(form.getTransformedValues()[field_key] as string[]).map(
(item: string) => ( (item: string) => (

View File

@ -88,37 +88,40 @@ type FlightConciseSchema = {
comments: string; comments: string;
}; };
const flightCreateHelper = (values: FlightFormSchema): FlightCreateSchema => { const flightCreateHelper = (
console.log(values.date.utc().startOf("date").toISOString()); values: FlightFormSchema
return { ): FlightCreateSchema | void => {
const date = dayjs(values.date);
try {
const newFlight = {
...values, ...values,
date: values.date.utc().startOf("day").toISOString(), date: date.utc().startOf("day").toISOString(),
hobbs_start: Number(values.hobbs_start), hobbs_start: values.hobbs_start ? Number(values.hobbs_start) : null,
hobbs_end: Number(values.hobbs_end), hobbs_end: values.hobbs_end ? Number(values.hobbs_end) : null,
tach_start: Number(values.tach_start), tach_start: values.tach_start ? Number(values.tach_start) : null,
tach_end: Number(values.tach_end), tach_end: values.tach_end ? Number(values.tach_end) : null,
time_start: values.date time_start: date
.utc() .utc()
.hour(Math.floor((values.time_start ?? 0) / 100)) .hour(Math.floor((values.time_start ?? 0) / 100))
.minute(Math.floor((values.time_start ?? 0) % 100)) .minute(Math.floor((values.time_start ?? 0) % 100))
.second(0) .second(0)
.millisecond(0) .millisecond(0)
.toISOString(), .toISOString(),
time_off: values.date time_off: date
.utc() .utc()
.hour(Math.floor((values.time_off ?? 0) / 100)) .hour(Math.floor((values.time_off ?? 0) / 100))
.minute(Math.floor((values.time_off ?? 0) % 100)) .minute(Math.floor((values.time_off ?? 0) % 100))
.second(0) .second(0)
.millisecond(0) .millisecond(0)
.toISOString(), .toISOString(),
time_down: values.date time_down: date
.utc() .utc()
.hour(Math.floor((values.time_down ?? 0) / 100)) .hour(Math.floor((values.time_down ?? 0) / 100))
.minute(Math.floor((values.time_down ?? 0) % 100)) .minute(Math.floor((values.time_down ?? 0) % 100))
.second(0) .second(0)
.millisecond(0) .millisecond(0)
.toISOString(), .toISOString(),
time_stop: values.date time_stop: date
.utc() .utc()
.hour(Math.floor((values.time_stop ?? 0) / 100)) .hour(Math.floor((values.time_stop ?? 0) / 100))
.minute(Math.floor((values.time_stop ?? 0) % 100)) .minute(Math.floor((values.time_stop ?? 0) % 100))
@ -126,6 +129,10 @@ const flightCreateHelper = (values: FlightFormSchema): FlightCreateSchema => {
.millisecond(0) .millisecond(0)
.toISOString(), .toISOString(),
}; };
return newFlight;
} catch (err) {
console.log(err);
}
}; };
export { export {

1
web/package-lock.json generated
View File

@ -4,6 +4,7 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "tailfin-web",
"dependencies": { "dependencies": {
"@mantine/core": "^7.4.0", "@mantine/core": "^7.4.0",
"@mantine/dates": "^7.4.0", "@mantine/dates": "^7.4.0",