diff --git a/web/app/routes/logbook.aircraft/route.tsx b/web/app/routes/logbook.aircraft/route.tsx
new file mode 100644
index 0000000..1aa57b2
--- /dev/null
+++ b/web/app/routes/logbook.aircraft/route.tsx
@@ -0,0 +1,321 @@
+import ErrorDisplay from "@/ui/error-display";
+import { useApi } from "@/util/api";
+import { AircraftFormSchema, AircraftSchema } from "@/util/types";
+import {
+ ActionIcon,
+ Button,
+ Card,
+ Center,
+ Container,
+ Group,
+ Loader,
+ Modal,
+ NumberInput,
+ ScrollArea,
+ Select,
+ Stack,
+ Text,
+ TextInput,
+ Title,
+ Tooltip,
+} from "@mantine/core";
+import { useForm } from "@mantine/form";
+import { randomId, useDisclosure } from "@mantine/hooks";
+import { IconPencil, IconPlus, IconTrash, IconX } from "@tabler/icons-react";
+import {
+ UseQueryResult,
+ useMutation,
+ useQuery,
+ useQueryClient,
+} from "@tanstack/react-query";
+import { AxiosError } from "axios";
+import { useState } from "react";
+
+function useAircraft() {
+ const client = useApi();
+
+ const aircraft = useQuery({
+ queryKey: ["aircraft-list"],
+ queryFn: async () => await client.get(`/aircraft`).then((res) => res.data),
+ });
+
+ return aircraft;
+}
+
+function AircraftCard({ aircraft }: { aircraft: AircraftSchema }) {
+ const [deleteOpened, { open: openDelete, close: closeDelete }] =
+ useDisclosure(false);
+
+ const client = useApi();
+ const queryClient = useQueryClient();
+
+ const deleteAircraft = useMutation({
+ mutationFn: async () =>
+ await client.delete(`/aircraft/${aircraft.id}`).then((res) => res.data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["aircraft-list"] });
+ },
+ });
+
+ return (
+ <>
+
+
+
+ Are you sure you want to delete this aircraft? This action cannot be
+ undone.
+
+ {deleteAircraft.isError ? (
+
+ {deleteAircraft.error.message}
+
+ ) : null}
+
+
+
+
+
+
+
+
+
+ {aircraft.tail_no}
+
+
+
+
+
+
+
+
+
+
+ {aircraft.make}
+ {aircraft.model}
+
+
+ {aircraft.aircraft_category}
+ /
+ {aircraft.aircraft_class}
+
+ {aircraft.hobbs ? Hobbs: {aircraft.hobbs} : null}
+
+
+ >
+ );
+}
+
+function NewAircraftModal({
+ opened,
+ close,
+}: {
+ opened: boolean;
+ close: () => void;
+}) {
+ const newForm = useForm({
+ initialValues: {
+ tail_no: "",
+ make: "",
+ model: "",
+ aircraft_category: "",
+ aircraft_class: "",
+ hobbs: 0.0,
+ },
+ validate: {
+ tail_no: (value) =>
+ value === null || value.trim() === ""
+ ? "Please enter a tail number"
+ : null,
+ make: (value) =>
+ value === null || value.trim() === "" ? "Please enter a make" : null,
+ model: (value) =>
+ value === null || value.trim() === "" ? "Please enter a model" : null,
+ aircraft_category: (value) =>
+ value === null || value.trim() === ""
+ ? "Please select a category"
+ : null,
+ aircraft_class: (value) =>
+ value === null || value.trim() === "" ? "Please select a class" : null,
+ },
+ });
+
+ const client = useApi();
+ const queryClient = useQueryClient();
+
+ const categories = useQuery({
+ queryKey: ["categories"],
+ queryFn: async () =>
+ await client.get(`/aircraft/categories`).then((res) => res.data),
+ });
+
+ const [category, setCategory] = useState("");
+ const [classSelection, setClassSelection] = useState("");
+
+ const classes = useQuery({
+ queryKey: ["classes", category],
+ queryFn: async () =>
+ await client
+ .get(`/aircraft/class?category=${category}`)
+ .then((res) => res.data),
+ enabled: !!category,
+ });
+
+ const addAircraft = useMutation({
+ mutationFn: async (values: AircraftFormSchema) => {
+ const newAircraft = values;
+ if (newAircraft) {
+ const res = await client.post("/aircraft", newAircraft);
+ return res.data;
+ }
+ throw new Error("Aircraft creation failed");
+ },
+ onSuccess: async () => {
+ await queryClient.invalidateQueries({ queryKey: ["aircraft-list"] });
+ close();
+ },
+ });
+
+ return (
+
+
+
+ );
+}
+
+export default function Aircraft() {
+ const aircraft: UseQueryResult = useAircraft();
+
+ const [newOpened, { open: openNew, close: closeNew }] = useDisclosure(false);
+
+ return (
+ <>
+
+
+
+ Aircraft
+
+
+
+
+
+
+
+
+
+ {aircraft.isLoading ? (
+
+
+
+ ) : aircraft.isError ? (
+
+
+
+ ) : aircraft.data && aircraft.data.length === 0 ? (
+
+
+
+ No Aircraft
+
+
+ ) : (
+
+ {aircraft.data?.map((item) => (
+
+ ))}
+
+ )}
+
+
+ >
+ );
+}
diff --git a/web/app/routes/logbook.flights.$id/route.tsx b/web/app/routes/logbook.flights.$id/route.tsx
index d18961c..c589e79 100644
--- a/web/app/routes/logbook.flights.$id/route.tsx
+++ b/web/app/routes/logbook.flights.$id/route.tsx
@@ -88,11 +88,9 @@ export default function Flight() {
) : flight.data ? (
<>
-
-
- Flight Log
-
-
+
+ Flight Log
+
) : null}
- {log.hobbs_start ||
- log.hobbs_end ||
- log.tach_start ||
- log.tach_end ? (
+ {log.hobbs_start || log.hobbs_end ? (
- {log.hobbs_start || log.hobbs_end ? (
-
-
-
-
- ) : null}
- {log.tach_start || log.tach_end ? (
-
-
-
-
- ) : null}
+
+
+
+
) : null}
{log.time_start ||
diff --git a/web/app/routes/logbook.flights.new/route.tsx b/web/app/routes/logbook.flights.new/route.tsx
index 0f38997..c9a0161 100644
--- a/web/app/routes/logbook.flights.new/route.tsx
+++ b/web/app/routes/logbook.flights.new/route.tsx
@@ -3,7 +3,6 @@ import { FlightFormSchema, flightCreateHelper } from "@/util/types";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useApi } from "@/util/api";
import { useNavigate } from "@remix-run/react";
-import { AxiosError } from "axios";
import FlightForm from "@/ui/form/flight-form";
export default function NewFlight() {
@@ -21,9 +20,6 @@ export default function NewFlight() {
}
throw new Error("Flight creation failed");
},
- retry: (failureCount, error: AxiosError) => {
- return !error || error.response?.status !== 401;
- },
onSuccess: async (data: { id: string }) => {
await queryClient.invalidateQueries({ queryKey: ["flights-list"] });
navigate(`/logbook/flights/${data.id}`);
diff --git a/web/app/routes/logbook.me/route.tsx b/web/app/routes/logbook.me/route.tsx
index 1174b7d..ffc7eb4 100644
--- a/web/app/routes/logbook.me/route.tsx
+++ b/web/app/routes/logbook.me/route.tsx
@@ -31,11 +31,16 @@ export default function Me() {
current_psk: string;
new_psk: string;
confirm_new_psk: string;
- }) =>
+ }) => {
+ console.log({
+ current_password: values.current_psk,
+ new_password: values.new_psk,
+ });
await client.put(`/users/me/password`, {
current_password: values.current_psk,
new_password: values.new_psk,
- }),
+ });
+ },
});
const updatePskForm = useForm({
@@ -95,14 +100,16 @@ export default function Me() {
{...updatePskForm.getInputProps("current_psk")}
/>
-
+
{updatePassword.isPending ? (
Updating...
) : updatePassword.isError ? (
diff --git a/web/app/routes/logbook/route.tsx b/web/app/routes/logbook/route.tsx
index d76396d..ea5dd8b 100644
--- a/web/app/routes/logbook/route.tsx
+++ b/web/app/routes/logbook/route.tsx
@@ -1,7 +1,7 @@
import { TailfinAppShell } from "@/ui/nav/app-shell";
import { useAuth } from "@/util/auth";
import type { MetaFunction } from "@remix-run/node";
-import { Outlet, useNavigate } from "@remix-run/react";
+import { Outlet, useLocation, useNavigate } from "@remix-run/react";
import {
QueryCache,
QueryClient,
@@ -22,13 +22,15 @@ export default function Index() {
const { user, loading } = useAuth();
const navigate = useNavigate();
+ const location = useLocation();
+
useEffect(() => {
if (!loading && !user) {
navigate("/login");
- } else {
+ } else if (location.pathname === "/logbook") {
navigate("/logbook/dashboard");
}
- }, [user, loading, navigate]);
+ }, [user, loading, navigate, location]);
const [queryClient] = useState(
() =>
diff --git a/web/app/ui/form/flight-form.tsx b/web/app/ui/form/flight-form.tsx
index fee87a7..1b04880 100644
--- a/web/app/ui/form/flight-form.tsx
+++ b/web/app/ui/form/flight-form.tsx
@@ -50,8 +50,6 @@ export default function FlightForm({
hobbs_start: null,
hobbs_end: null,
- tach_start: null,
- tach_end: null,
time_start: null,
time_off: null,
@@ -124,10 +122,6 @@ export default function FlightForm({
-
-
-
-
{/* Start/Stop */}
diff --git a/web/app/ui/nav/navbar.tsx b/web/app/ui/nav/navbar.tsx
index 4766a9f..b862a59 100644
--- a/web/app/ui/nav/navbar.tsx
+++ b/web/app/ui/nav/navbar.tsx
@@ -3,8 +3,9 @@ import { Stack, NavLink } from "@mantine/core";
import { Link, useLocation } from "@remix-run/react";
import {
IconBook2,
+ IconDashboard,
IconLogout,
- IconPlaneDeparture,
+ IconPlaneTilt,
IconUser,
} from "@tabler/icons-react";
@@ -28,7 +29,7 @@ export default function Navbar({
component={Link}
to="/logbook/dashboard"
label="Dashboard"
- leftSection={}
+ leftSection={}
active={page == "dashboard"}
onClick={() => (opened ? toggle() : null)}
/>
@@ -41,6 +42,15 @@ export default function Navbar({
active={page === "flights"}
onClick={() => (opened ? toggle() : null)}
/>
+ }
+ active={page === "aircraft"}
+ onClick={() => (opened ? toggle() : null)}
+ />