Group flight list by date

This commit is contained in:
april 2024-01-05 11:13:21 -06:00
parent 7fa749a5de
commit 05fefd40b1
8 changed files with 379 additions and 294 deletions

View File

@ -1,13 +1,12 @@
import { LogItem, VerticalLogItem } from "@/ui/display/log-item"; import { VerticalLogItem } from "@/ui/display/log-item";
import ErrorDisplay from "@/ui/error-display"; import ErrorDisplay from "@/ui/error-display";
import { client } from "@/util/api"; import { client } from "@/util/api";
import { import {
Center, Center,
Container, Container,
Divider,
Grid, Grid,
Loader, Loader,
ScrollAreaAutosize, ScrollArea,
Stack, Stack,
Title, Title,
} from "@mantine/core"; } from "@mantine/core";
@ -26,238 +25,238 @@ export default function Flight() {
const log = flight.data; const log = flight.data;
return ( return (
<Container> // <Container>
<Stack> <Stack h="calc(100vh-95px)">
{flight.isError ? ( {flight.isError ? (
<Center h="calc(100vh - 95px)"> <Center h="calc(100vh - 95px)">
<ErrorDisplay error="Error Fetching Flight" /> <ErrorDisplay error="Error Fetching Flight" />
</Center> </Center>
) : flight.isPending ? ( ) : flight.isPending ? (
<Center h="calc(100vh - 95px)"> <Center h="calc(100vh - 95px)">
<Loader /> <Loader />
</Center> </Center>
) : flight.data ? ( ) : flight.data ? (
<> <>
<Title order={3} py="lg" style={{ textAlign: "center" }}> <Title order={3} py="lg" style={{ textAlign: "center" }}>
Flight Log Flight Log
</Title> </Title>
<ScrollAreaAutosize mah="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">
<Grid.Col span={6}> <Grid.Col span={6}>
<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 ? (
<> <>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Waypoint From" label="Waypoint From"
content={log.waypoint_from} content={log.waypoint_from}
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Waypoint To" label="Waypoint To"
content={log.waypoint_to} content={log.waypoint_to}
/> />
</Grid.Col> </Grid.Col>
</> </>
) : null} ) : null}
{log.route ? ( {log.route ? (
<> <>
<Grid.Col span={12}> <Grid.Col span={12}>
<VerticalLogItem label="Route" content={log.route} /> <VerticalLogItem label="Route" content={log.route} />
</Grid.Col> </Grid.Col>
</> </>
) : null} ) : null}
{log.hobbs_start || log.hobbs_end ? ( {log.hobbs_start || log.hobbs_end ? (
<> <>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Hobbs Start" label="Hobbs Start"
content={log.hobbs_start} content={log.hobbs_start}
hours hours
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Hobbs End" label="Hobbs End"
content={log.hobbs_end} content={log.hobbs_end}
hours hours
/> />
</Grid.Col> </Grid.Col>
</> </>
) : null} ) : null}
{log.tach_start || log.tach_end ? ( {log.tach_start || log.tach_end ? (
<> <>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Tach Start" label="Tach Start"
content={log.tach_start} content={log.tach_start}
hours hours
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Tach End" label="Tach End"
content={log.tach_end} content={log.tach_end}
hours hours
/> />
</Grid.Col> </Grid.Col>
</> </>
) : null} ) : null}
{log.time_start || log.time_off ? ( {log.time_start || log.time_off ? (
<> <>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Time Start" label="Time Start"
content={log.time_start} content={log.time_start}
time time
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Time Off" label="Time Off"
content={log.time_off} content={log.time_off}
time time
/> />
</Grid.Col> </Grid.Col>
</> </>
) : null} ) : null}
{log.time_down || log.time_stop ? ( {log.time_down || log.time_stop ? (
<> <>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Time Down" label="Time Down"
content={log.time_down} content={log.time_down}
time time
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Time Stop" label="Time Stop"
content={log.time_stop} content={log.time_stop}
time time
/> />
</Grid.Col> </Grid.Col>
</> </>
) : null} ) : null}
<Grid.Col span={4}> <Grid.Col span={4}>
<VerticalLogItem <VerticalLogItem
label="Total Time" label="Total Time"
content={log.time_total} content={log.time_total}
hours hours
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={4}> <Grid.Col span={4}>
<VerticalLogItem <VerticalLogItem
label="Time Solo" label="Time Solo"
content={log.time_solo} content={log.time_solo}
hours hours
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={4}> <Grid.Col span={4}>
<VerticalLogItem <VerticalLogItem
label="Time Night" label="Time Night"
content={log.time_night} content={log.time_night}
hours hours
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Time PIC" label="Time PIC"
content={log.time_pic} content={log.time_pic}
hours hours
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Time SIC" label="Time SIC"
content={log.time_sic} content={log.time_sic}
hours hours
/> />
</Grid.Col> </Grid.Col>
{log.time_xc && log.dist_xc ? ( {log.time_xc && log.dist_xc ? (
<> <>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Time Cross-Country" label="Time Cross-Country"
content={log.time_xc} content={log.time_xc}
hours hours
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Cross-Country Distance" label="Cross-Country Distance"
content={log.dist_xc} content={log.dist_xc}
decimal={2} decimal={2}
/> />
</Grid.Col> </Grid.Col>
</> </>
) : null} ) : null}
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Takeoffs (Day)" label="Takeoffs (Day)"
content={log.takeoffs_day} content={log.takeoffs_day}
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Landings (Day)" label="Landings (Day)"
content={log.landings_day} content={log.landings_day}
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Takeoffs (Night)" label="Takeoffs (Night)"
content={log.takeoffs_night} content={log.takeoffs_night}
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<VerticalLogItem <VerticalLogItem
label="Landings (Night)" label="Landings (Night)"
content={log.landings_night} content={log.landings_night}
/> />
</Grid.Col> </Grid.Col>
{log.time_instrument || {log.time_instrument ||
log.time_sim_instrument || log.time_sim_instrument ||
log.holds_instrument ? ( log.holds_instrument ? (
<> <>
<Grid.Col span={4}> <Grid.Col span={4}>
<VerticalLogItem <VerticalLogItem
label="Instrument Time" label="Instrument Time"
content={log.time_instrument} content={log.time_instrument}
hours hours
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={4}> <Grid.Col span={4}>
<VerticalLogItem <VerticalLogItem
label="Simulated Instrument Time" label="Simulated Instrument Time"
content={log.time_sim_instrument} content={log.time_sim_instrument}
hours hours
/> />
</Grid.Col> </Grid.Col>
<Grid.Col span={4}> <Grid.Col span={4}>
<VerticalLogItem <VerticalLogItem
label="Instrument Holds" label="Instrument Holds"
content={log.holds_instrument} content={log.holds_instrument}
/> />
</Grid.Col> </Grid.Col>
</> </>
) : null} ) : null}
</Grid> </Grid>
</Container> </Container>
</ScrollAreaAutosize> </ScrollArea>
</> </>
) : ( ) : (
<Center h="calc(100vh - 95px)"> <Center h="calc(100vh - 95px)">
<ErrorDisplay error="Unknown Error" /> <ErrorDisplay error="Unknown Error" />
</Center> </Center>
)} )}
</Stack> </Stack>
</Container> // </Container>
); );
} }

View File

@ -86,7 +86,6 @@ export default function NewFlight() {
mutationFn: async (values: FlightFormSchema) => { mutationFn: async (values: FlightFormSchema) => {
const newFlight = flightCreateHelper(values); const newFlight = flightCreateHelper(values);
const res = await client.post("/flights", newFlight); const res = await client.post("/flights", newFlight);
console.log(res);
return res.data; return res.data;
}, },
retry: (failureCount, error: AxiosError) => { retry: (failureCount, error: AxiosError) => {

View File

@ -8,30 +8,24 @@ import {
Stack, Stack,
Loader, Loader,
Center, Center,
Divider,
Badge, Badge,
Group,
Divider,
} from "@mantine/core"; } from "@mantine/core";
import { randomId } from "@mantine/hooks";
import { Link, useLocation, useNavigate } from "@remix-run/react"; import { Link, useLocation, useNavigate } from "@remix-run/react";
import { IconPlus } from "@tabler/icons-react"; import {
IconArrowRightTail,
IconPlaneTilt,
IconPlus,
} from "@tabler/icons-react";
import { UseQueryResult, useQuery } from "@tanstack/react-query"; import { UseQueryResult, useQuery } from "@tanstack/react-query";
function useFlights() { function useFlights() {
const flights = useQuery({ const flights = useQuery({
queryKey: ["flights-list"], queryKey: ["flights-list"],
queryFn: async () => { queryFn: async () =>
const res = await client.get(`/flights`); await client.get(`/flights/by-date`).then((res) => res.data),
const groupedFlights: { [date: string]: FlightConciseSchema[] } = {};
res.data.map((log: FlightConciseSchema) => {
const dateStr = log.date;
if (!groupedFlights[dateStr]) {
groupedFlights[dateStr] = [];
}
groupedFlights[dateStr].push(log);
});
return groupedFlights;
},
}); });
return flights; return flights;
@ -41,41 +35,127 @@ function FlightsListDisplay({
flights, flights,
page, page,
}: { }: {
flights: UseQueryResult<{ [date: string]: FlightConciseSchema[] }>; flights: UseQueryResult<{
[year: string]: {
[month: string]: { [day: string]: FlightConciseSchema[] };
};
}>;
page: string; page: string;
}) { }) {
const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
return ( return (
<> <>
{flights.data ? ( {flights.data ? (
Object.entries(flights.data).map(([date, logs], index: number) => ( Object.entries(flights.data).map(([year, months]) => (
<> <>
<Text key={date} mt="md" mb="xs" fw={700}> <NavLink
{date} key={randomId()}
</Text> label={`-- ${year} --`}
<Divider key={date + index} /> fw={700}
{logs.map((flight: FlightConciseSchema) => ( style={{ textAlign: "center" }}
<NavLink defaultOpened
key={flight.id} childrenOffset={0}
component={Link} >
to={`/logbook/flights/${flight.id}`} <>
label={`${ <Divider />
!(flight.waypoint_from || flight.waypoint_to) {Object.entries(months).map(([month, days]) => (
? "No Route" <NavLink
: "" key={randomId()}
}${flight.waypoint_from ? flight.waypoint_from : ""} ${ label={monthNames[Number(month) - 1]}
flight.waypoint_to ? flight.waypoint_to : "" fw={500}
}`} style={{ textAlign: "center" }}
description={`${flight.date}`} defaultOpened
rightSection={ >
flight.aircraft ? ( <Divider />
<Badge key={"aircraft" + index} color="gray"> {Object.entries(days).map(([, logs]) => (
{flight.aircraft} <>
</Badge> {logs.map((flight: FlightConciseSchema) => (
) : null <>
} <NavLink
active={page === flight.id} key={randomId()}
/> component={Link}
))} to={`/logbook/flights/${flight.id}`}
label={
<Group>
<Badge
color="gray"
size="lg"
radius="sm"
px="xs"
>
{flight.date}
</Badge>
<Text fw={500}>
{`${Number(flight.time_total).toFixed(
1
)} hr`}
</Text>
{flight.waypoint_from ||
flight.waypoint_to ? (
<Text>/</Text>
) : null}
<Group gap="xs">
{flight.waypoint_from ? (
<Text>{flight.waypoint_from}</Text>
) : (
""
)}
{flight.waypoint_from &&
flight.waypoint_to ? (
<IconArrowRightTail />
) : null}
{flight.waypoint_to ? (
<Text>{flight.waypoint_to}</Text>
) : (
""
)}
</Group>
</Group>
}
description={
<Text lineClamp={1}>
{flight.comments
? flight.comments
: "(No Comment)"}
</Text>
}
rightSection={
flight.aircraft ? (
<Badge
key={randomId()}
leftSection={<IconPlaneTilt size="1rem" />}
color="gray"
size="lg"
>
{flight.aircraft}
</Badge>
) : null
}
active={page === flight.id}
/>
<Divider />
</>
))}
</>
))}
</NavLink>
))}
</>
</NavLink>
</> </>
)) ))
) : flights.isLoading ? ( ) : flights.isLoading ? (

View File

@ -1,22 +1,22 @@
import { Divider, Grid, Container, ScrollAreaAutosize } from "@mantine/core"; import { Divider, Grid, Container, ScrollArea } from "@mantine/core";
import { Outlet } from "@remix-run/react"; import { Outlet } from "@remix-run/react";
import { FlightsList } from "./flights-list"; import { FlightsList } from "./flights-list";
export default function FlightsLayout() { export default function FlightsLayout() {
return ( return (
<> <>
<Grid h="100%" visibleFrom="md"> <Grid h="100%" visibleFrom="lg">
<Grid.Col span={3}> <Grid.Col span={4}>
<FlightsList /> <FlightsList />
</Grid.Col> </Grid.Col>
<Divider orientation="vertical" m="sm" /> <Divider orientation="vertical" m="sm" />
<Grid.Col span="auto"> <Grid.Col span="auto">
<ScrollAreaAutosize mah="calc(100vh - 95px)"> <ScrollArea.Autosize mah="calc(100vh - 95px)">
<Outlet /> <Outlet />
</ScrollAreaAutosize> </ScrollArea.Autosize>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
<Container hiddenFrom="md"> <Container hiddenFrom="lg" style={{ paddingLeft: 0, paddingRight: 0 }}>
<Outlet /> <Outlet />
</Container> </Container>
</> </>

View File

@ -40,7 +40,7 @@ export function VerticalLogItem({
return ( return (
<Card> <Card>
<Stack gap="xs" align="center"> <Stack gap="xs" align="center" h="100%">
<Text c="dimmed" style={{ textalign: "center" }}> <Text c="dimmed" style={{ textalign: "center" }}>
{label} {label}
</Text> </Text>

View File

@ -19,7 +19,7 @@ export function TailfinAppShell({ children }: { children: React.ReactNode }) {
return ( return (
<AppShell <AppShell
header={{ height: 60 }} header={{ height: 60 }}
navbar={{ width: 300, breakpoint: "sm", collapsed: { mobile: !opened } }} navbar={{ width: 300, breakpoint: "xl", collapsed: { mobile: !opened } }}
padding="md" padding="md"
> >
<AppShell.Header> <AppShell.Header>
@ -28,7 +28,7 @@ export function TailfinAppShell({ children }: { children: React.ReactNode }) {
<Burger <Burger
opened={opened} opened={opened}
onClick={toggle} onClick={toggle}
hiddenFrom="sm" hiddenFrom="xl"
size="sm" size="sm"
/> />
</Group> </Group>
@ -44,7 +44,7 @@ export function TailfinAppShell({ children }: { children: React.ReactNode }) {
</Group> </Group>
</AppShell.Header> </AppShell.Header>
<AppShell.Navbar> <AppShell.Navbar>
<Navbar /> <Navbar opened={opened} toggle={toggle} />
</AppShell.Navbar> </AppShell.Navbar>
<AppShell.Main>{children}</AppShell.Main> <AppShell.Main>{children}</AppShell.Main>
</AppShell> </AppShell>

View File

@ -8,7 +8,13 @@ import {
IconUser, IconUser,
} from "@tabler/icons-react"; } from "@tabler/icons-react";
export default function Navbar() { export default function Navbar({
opened,
toggle,
}: {
opened: boolean;
toggle: () => void;
}) {
const location = useLocation(); const location = useLocation();
const page = location.pathname.split("/")[2]; const page = location.pathname.split("/")[2];
@ -24,6 +30,7 @@ export default function Navbar() {
label="Dashboard" label="Dashboard"
leftSection={<IconPlaneDeparture />} leftSection={<IconPlaneDeparture />}
active={page == null} active={page == null}
onClick={() => (opened ? toggle() : null)}
/> />
<NavLink <NavLink
p="md" p="md"
@ -32,6 +39,7 @@ export default function Navbar() {
label="Flights" label="Flights"
leftSection={<IconBook2 />} leftSection={<IconBook2 />}
active={page === "flights"} active={page === "flights"}
onClick={() => (opened ? toggle() : null)}
/> />
</Stack> </Stack>
<Stack gap="0"> <Stack gap="0">

View File

@ -27,7 +27,6 @@ client.interceptors.request.use(
error.config.headers.Authorization = `Bearer ${newAccessToken}`; error.config.headers.Authorization = `Bearer ${newAccessToken}`;
return client(error.config); return client(error.config);
} catch (err) { } catch (err) {
console.log("ERRORRRRRRRRRRRRRR");
clearUser(); clearUser();
window.location.href = "/login"; window.location.href = "/login";
} }