Add image display to flight logs
This commit is contained in:
parent
4b80593aa3
commit
2395bb10bf
@ -31,7 +31,6 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (totals.isFetched && !!totals.data) {
|
if (totals.isFetched && !!totals.data) {
|
||||||
console.log(totals.data);
|
|
||||||
setTotalsData(totals.data);
|
setTotalsData(totals.data);
|
||||||
}
|
}
|
||||||
}, [totals.data]);
|
}, [totals.data]);
|
||||||
|
@ -77,6 +77,7 @@ export default function Flight() {
|
|||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
<Group justify="flex-end">
|
<Group justify="flex-end">
|
||||||
|
{deleteFlight.isPending ? <Loader /> : null}
|
||||||
<Button color="red" onClick={() => deleteFlight.mutate()}>
|
<Button color="red" onClick={() => deleteFlight.mutate()}>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
@ -131,6 +132,7 @@ export default function Flight() {
|
|||||||
{imageIds.length > 0 ? (
|
{imageIds.length > 0 ? (
|
||||||
<CollapsibleFieldset legend="Images" mt="sm" w="100%">
|
<CollapsibleFieldset legend="Images" mt="sm" w="100%">
|
||||||
<Carousel
|
<Carousel
|
||||||
|
style={{ maxHeight: "700px" }}
|
||||||
withIndicators
|
withIndicators
|
||||||
slideGap="sm"
|
slideGap="sm"
|
||||||
slideSize={{ base: "100%", sm: "80%" }}
|
slideSize={{ base: "100%", sm: "80%" }}
|
||||||
@ -140,6 +142,7 @@ export default function Flight() {
|
|||||||
<SecureImage
|
<SecureImage
|
||||||
key={randomId()}
|
key={randomId()}
|
||||||
id={img}
|
id={img}
|
||||||
|
h="700px"
|
||||||
radius="lg"
|
radius="lg"
|
||||||
/>
|
/>
|
||||||
</Carousel.Slide>
|
</Carousel.Slide>
|
||||||
|
@ -16,6 +16,32 @@ export default function NewFlight() {
|
|||||||
const newFlight = flightCreateHelper(values);
|
const newFlight = flightCreateHelper(values);
|
||||||
if (newFlight) {
|
if (newFlight) {
|
||||||
const res = await client.post("/flights", newFlight);
|
const res = await client.post("/flights", newFlight);
|
||||||
|
|
||||||
|
const id = res.data.id;
|
||||||
|
if (!id) throw new Error("Flight creation failed");
|
||||||
|
|
||||||
|
console.log(values.images);
|
||||||
|
|
||||||
|
const imageForm = new FormData();
|
||||||
|
|
||||||
|
// Upload images
|
||||||
|
for (const img of values.images) {
|
||||||
|
imageForm.append("images", img);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(imageForm);
|
||||||
|
|
||||||
|
const img_id = await client.post(
|
||||||
|
`/flights/${id}/add_images`,
|
||||||
|
imageForm,
|
||||||
|
{ headers: { "Content-Type": "multipart/form-data" } }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!img_id) {
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ["flights-list"] });
|
||||||
|
throw new Error("Image upload failed");
|
||||||
|
}
|
||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
throw new Error("Flight creation failed");
|
throw new Error("Flight creation failed");
|
||||||
|
@ -45,10 +45,12 @@ function useFetchImageAsBase64(
|
|||||||
export default function SecureImage({
|
export default function SecureImage({
|
||||||
id,
|
id,
|
||||||
radius = "sm",
|
radius = "sm",
|
||||||
|
h = "",
|
||||||
clickable = true,
|
clickable = true,
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
radius?: string;
|
radius?: string;
|
||||||
|
h?: string;
|
||||||
clickable?: boolean;
|
clickable?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { isLoading, error, data } = useFetchImageAsBase64(id);
|
const { isLoading, error, data } = useFetchImageAsBase64(id);
|
||||||
@ -57,7 +59,7 @@ export default function SecureImage({
|
|||||||
|
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
return (
|
return (
|
||||||
<Center h="100%">
|
<Center h="500px">
|
||||||
<Loader />
|
<Loader />
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
@ -80,6 +82,12 @@ export default function SecureImage({
|
|||||||
<Image
|
<Image
|
||||||
src={`data:${data?.type};base64,${data?.blob}`}
|
src={`data:${data?.type};base64,${data?.blob}`}
|
||||||
radius={radius}
|
radius={radius}
|
||||||
|
w="auto"
|
||||||
|
maw="100%"
|
||||||
|
h="100%"
|
||||||
|
m="auto"
|
||||||
|
fit="contain"
|
||||||
|
style={{ maxHeight: h ?? "" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (clickable) {
|
if (clickable) {
|
||||||
open();
|
open();
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
Container,
|
Container,
|
||||||
Fieldset,
|
Fieldset,
|
||||||
Group,
|
Group,
|
||||||
|
Loader,
|
||||||
Modal,
|
Modal,
|
||||||
NumberInput,
|
NumberInput,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
@ -34,6 +35,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { useApi } from "@/util/api";
|
import { useApi } from "@/util/api";
|
||||||
import { useAircraft } from "@/util/hooks";
|
import { useAircraft } from "@/util/hooks";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import ImageUpload from "./image-upload";
|
||||||
|
|
||||||
export default function FlightForm({
|
export default function FlightForm({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
@ -101,6 +103,8 @@ export default function FlightForm({
|
|||||||
crew: [],
|
crew: [],
|
||||||
|
|
||||||
comments: "",
|
comments: "",
|
||||||
|
|
||||||
|
images: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -492,20 +496,22 @@ export default function FlightForm({
|
|||||||
minRows={4}
|
minRows={4}
|
||||||
{...form.getInputProps("comments")}
|
{...form.getInputProps("comments")}
|
||||||
/>
|
/>
|
||||||
|
<ImageUpload
|
||||||
|
form={form}
|
||||||
|
field="images"
|
||||||
|
label="Images"
|
||||||
|
placeholder="Upload Images"
|
||||||
|
/>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
</Container>
|
</Container>
|
||||||
</ScrollArea.Autosize>
|
</ScrollArea.Autosize>
|
||||||
|
|
||||||
<Group justify="flex-end" mt="md">
|
<Group justify="flex-end" mt="md">
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<Text c="yellow" fw={700}>
|
<Loader />
|
||||||
Loading...
|
|
||||||
</Text>
|
|
||||||
) : isError ? (
|
) : isError ? (
|
||||||
<Text c="red" fw={700}>
|
<Text c="red" fw={700}>
|
||||||
{error instanceof AxiosError
|
{error?.message}
|
||||||
? error.response?.data.detail
|
|
||||||
: error?.message}
|
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
{withCancelButton ? (
|
{withCancelButton ? (
|
||||||
|
51
web/app/ui/form/image-upload.tsx
Normal file
51
web/app/ui/form/image-upload.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { FlightFormSchema } from "@/util/types";
|
||||||
|
import { FileInput, FileInputProps, Pill } from "@mantine/core";
|
||||||
|
import { UseFormReturnType } from "@mantine/form";
|
||||||
|
import { randomId } from "@mantine/hooks";
|
||||||
|
import { IconPhoto } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
export default function ImageUpload({
|
||||||
|
form,
|
||||||
|
field,
|
||||||
|
label = "",
|
||||||
|
placeholder = "",
|
||||||
|
}: {
|
||||||
|
form: UseFormReturnType<
|
||||||
|
FlightFormSchema,
|
||||||
|
(values: FlightFormSchema) => FlightFormSchema
|
||||||
|
>;
|
||||||
|
field: string;
|
||||||
|
label?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
}) {
|
||||||
|
const ValueComponent: FileInputProps["valueComponent"] = ({ value }) => {
|
||||||
|
if (value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return (
|
||||||
|
<Pill.Group>
|
||||||
|
{value.map((file) => (
|
||||||
|
<Pill key={randomId()}>{file.name}</Pill>
|
||||||
|
))}
|
||||||
|
</Pill.Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Pill>{value.name}</Pill>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FileInput
|
||||||
|
label={label}
|
||||||
|
placeholder={placeholder}
|
||||||
|
multiple
|
||||||
|
accept="image/*"
|
||||||
|
valueComponent={ValueComponent}
|
||||||
|
rightSectionPointerEvents="none"
|
||||||
|
rightSection={<IconPhoto />}
|
||||||
|
{...form.getInputProps(field)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -58,3 +58,7 @@ function useProvideApi(apiUrl: string) {
|
|||||||
|
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createClient() {
|
||||||
|
return axios.create({});
|
||||||
|
}
|
||||||
|
@ -50,6 +50,8 @@ type FlightFormSchema = FlightBaseSchema & {
|
|||||||
time_off: number | null;
|
time_off: number | null;
|
||||||
time_down: number | null;
|
time_down: number | null;
|
||||||
time_stop: number | null;
|
time_stop: number | null;
|
||||||
|
|
||||||
|
images: File[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type FlightCreateSchema = FlightBaseSchema & {
|
type FlightCreateSchema = FlightBaseSchema & {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user