Improve image editing, allow null times

This commit is contained in:
april 2024-01-16 12:03:38 -06:00
parent c483cf6dc6
commit 5a9f19484b
7 changed files with 221 additions and 62 deletions

View File

@ -1,6 +1,5 @@
import CollapsibleFieldset from "@/ui/display/collapsible-fieldset";
import { VerticalLogItem } from "@/ui/display/log-item";
import SecureImage from "@/ui/display/secure-img";
import ErrorDisplay from "@/ui/error-display";
import { useApi } from "@/util/api";
import {
@ -18,8 +17,7 @@ import {
Modal,
Button,
} from "@mantine/core";
import { Carousel } from "@mantine/carousel";
import { randomId, useDisclosure } from "@mantine/hooks";
import { useDisclosure } from "@mantine/hooks";
import { useNavigate, useParams } from "@remix-run/react";
import { IconPencil, IconTrash } from "@tabler/icons-react";
import { useMutation, useQuery } from "@tanstack/react-query";
@ -142,7 +140,7 @@ export default function Flight() {
<CollapsibleFieldset legend="Images" mt="sm" w="100%">
<ImageLogItem
imageIds={imageIds}
id={params.id}
id={params.id ?? ""}
mah="700px"
/>
</CollapsibleFieldset>

View File

@ -23,19 +23,21 @@ export default function NewFlight() {
const imageForm = new FormData();
// Upload images
for (const img of values.images) {
imageForm.append("images", img);
}
if (values.images.length > 0) {
for (const img of values.images) {
imageForm.append("images", img);
}
const img_id = await client.post(
`/flights/${id}/add_images`,
imageForm,
{ headers: { "Content-Type": "multipart/form-data" } }
);
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");
if (!img_id) {
await queryClient.invalidateQueries({ queryKey: ["flights-list"] });
throw new Error("Image upload failed");
}
}
return res.data;

View File

@ -13,9 +13,9 @@ import {
Text,
} from "@mantine/core";
import { IconPencil } from "@tabler/icons-react";
import ListInput from "@/ui/input/list-input";
import ImageListInput from "@/ui/input/image-list-input";
import ImageUpload from "@/ui/input/image-upload";
import { useState } from "react";
import { useEffect, useState } from "react";
import { useApi } from "@/util/api";
import { useMutation, useQueryClient } from "@tanstack/react-query";
@ -66,18 +66,23 @@ export default function ImageLogItem({
);
if (!img_id) {
await queryClient.invalidateQueries({ queryKey: [id] });
await queryClient.invalidateQueries({ queryKey: ["flights-list"] });
throw new Error("Image upload failed");
}
}
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [id] });
queryClient.invalidateQueries({ queryKey: ["flights-list"] });
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: [id] });
await queryClient.invalidateQueries({ queryKey: ["flights-list"] });
closeEdit();
},
});
useEffect(() => {
setExistingImages(imageIds);
}, [imageIds]);
return (
<>
<Modal
@ -87,12 +92,6 @@ export default function ImageLogItem({
centered
>
<Stack>
<ListInput
label="Existing Images"
value={existingImages}
setValue={setExistingImages}
canAdd={false}
/>
<ImageUpload
value={newImages}
setValue={setNewImages}
@ -100,6 +99,13 @@ export default function ImageLogItem({
mt="md"
placeholder="Images"
/>
<ImageListInput
label="Existing Images"
imageIds={existingImages}
setImageIds={setExistingImages}
collapsible
startCollapsed
/>
<Group justify="flex-end">
{updateValue.isPending ? <Loader /> : null}

View File

@ -35,6 +35,7 @@ import { useApi } from "@/util/api";
import { useAircraft } from "@/util/hooks";
import { useEffect, useState } from "react";
import ImageUpload from "./image-upload";
import ImageListInput from "./image-list-input";
export default function FlightForm({
onSubmit,
@ -59,7 +60,8 @@ export default function FlightForm({
cancelFunc?: () => void;
autofillHobbs?: boolean;
}) {
const validate_time = (value) => {
const validate_time = (value: number | null) => {
if (value === null) return;
if (value > 2359) return "Time must be between 0000 and 2359";
if (value % 100 > 59) return "Minutes must not exceed 59";
};
@ -163,6 +165,7 @@ export default function FlightForm({
getHobbs.data.hobbs ?? form.getTransformedValues()["hobbs_start"]
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getHobbs.data]);
return (
@ -332,7 +335,9 @@ export default function FlightForm({
style={{
display:
["", null].indexOf(
form.getTransformedValues()["hobbs_start"]
form.getTransformedValues()["hobbs_start"] as
| string
| null
) > -1
? "none"
: undefined,
@ -509,12 +514,12 @@ export default function FlightForm({
{...form.getInputProps("comments")}
/>
{initialValues?.existing_images?.length ?? 0 > 0 ? (
<ListInput
<ImageListInput
form={form}
field="existing_images"
mt="md"
label="Existing Images"
canAdd={false}
// canAdd={false}
/>
) : null}
<ImageUpload

View File

@ -0,0 +1,75 @@
import { Button, Card, Group, Text } from "@mantine/core";
import SecureImage from "../display/secure-img";
import { randomId } from "@mantine/hooks";
import { IconTrash } from "@tabler/icons-react";
import { UseFormReturnType } from "@mantine/form";
import { FlightFormSchema } from "@/util/types";
export default function ImageListInput({
label,
form,
field,
mt = "sm",
h = "100px",
}: {
label: string;
form: UseFormReturnType<
FlightFormSchema,
(values: FlightFormSchema) => FlightFormSchema
>;
field: string;
mt?: string;
w?: string;
h?: string;
}) {
const field_key = field as keyof typeof form.getTransformedValues;
return (
<>
{/* <Grid> */}
<Text size="sm" fw={700} mt={mt} mb="xs">
{label}
</Text>
<Group display="flex" gap="xs" style={{ flexWrap: "wrap" }}>
{(form.getTransformedValues()[field_key] as string[]).map((id) => (
// <Grid.Col key={randomId()}>
<Card key={randomId()} padding="md" shadow="md" withBorder>
{/* <Card.Section> */}
<SecureImage id={id} h={h} />
{/* </Card.Section> */}
<Button
mt="md"
leftSection={<IconTrash />}
onClick={() =>
form.setFieldValue(
field,
(form.getTransformedValues()[field_key] as string[]).filter(
(i) => i !== id
)
)
}
>
Remove
</Button>
</Card>
// </Grid.Col>
))}
</Group>
</>
// </Grid>
// <PillsInput label={label}>
// <Pill.Group>
// {imageIds.map((id: string) => (
// <Pill
// radius="sm"
// key={id}
// withRemoveButton
// onRemove={() => setImageIds(imageIds.filter((i) => i !== id))}
// >
// <SecureImage id={id} h="20px" />
// </Pill>
// ))}
// </Pill.Group>
// </PillsInput>
);
}

View File

@ -0,0 +1,65 @@
import {
ActionIcon,
Button,
Card,
Collapse,
Group,
Text,
Tooltip,
} from "@mantine/core";
import { Dispatch, SetStateAction, useState } from "react";
import SecureImage from "../display/secure-img";
import { randomId } from "@mantine/hooks";
import { IconMinus, IconPlus, IconTrash } from "@tabler/icons-react";
export default function ImageListInput({
label,
imageIds,
setImageIds,
collapsible = false,
startCollapsed = true,
}: {
label: string;
imageIds: string[];
setImageIds: Dispatch<SetStateAction<string[]>>;
collapsible?: boolean;
startCollapsed?: boolean;
}) {
const [collapsed, setCollapsed] = useState(startCollapsed);
return (
<>
<Group gap="0">
<Text size="sm" fw={700} span>
{label}
</Text>
{collapsible ? (
<Tooltip label={collapsed ? "Expand" : "Collapse"}>
<ActionIcon
variant="transparent"
onClick={() => setCollapsed(!collapsed)}
>
{collapsed ? <IconPlus size="1rem" /> : <IconMinus size="1rem" />}
</ActionIcon>
</Tooltip>
) : null}
</Group>
<Collapse in={!collapsed}>
<Group variant="column">
{imageIds.map((id) => (
<Card key={randomId()} padding="md" shadow="md" withBorder>
<SecureImage id={id} />
<Button
mt="md"
leftSection={<IconTrash />}
onClick={() => setImageIds(imageIds.filter((i) => i !== id))}
>
Remove
</Button>
</Card>
))}
</Group>
</Collapse>
</>
);
}

View File

@ -58,10 +58,10 @@ type FlightFormSchema = FlightBaseSchema & {
type FlightCreateSchema = FlightBaseSchema & {
date: string;
time_start: string;
time_off: string;
time_down: string;
time_stop: string;
time_start: string | null;
time_off: string | null;
time_down: string | null;
time_stop: string | null;
};
type FlightDisplaySchema = FlightBaseSchema & {
@ -101,34 +101,42 @@ const flightCreateHelper = (
date: date.utc().startOf("day").toISOString(),
hobbs_start: values.hobbs_start ? Number(values.hobbs_start) : null,
hobbs_end: values.hobbs_end ? Number(values.hobbs_end) : null,
time_start: date
.utc()
.hour(Math.floor((values.time_start ?? 0) / 100))
.minute(Math.floor((values.time_start ?? 0) % 100))
.second(0)
.millisecond(0)
.toISOString(),
time_off: date
.utc()
.hour(Math.floor((values.time_off ?? 0) / 100))
.minute(Math.floor((values.time_off ?? 0) % 100))
.second(0)
.millisecond(0)
.toISOString(),
time_down: date
.utc()
.hour(Math.floor((values.time_down ?? 0) / 100))
.minute(Math.floor((values.time_down ?? 0) % 100))
.second(0)
.millisecond(0)
.toISOString(),
time_stop: date
.utc()
.hour(Math.floor((values.time_stop ?? 0) / 100))
.minute(Math.floor((values.time_stop ?? 0) % 100))
.second(0)
.millisecond(0)
.toISOString(),
time_start: values.time_start
? date
.utc()
.hour(Math.floor((values.time_start ?? 0) / 100))
.minute(Math.floor((values.time_start ?? 0) % 100))
.second(0)
.millisecond(0)
.toISOString()
: null,
time_off: values.time_off
? date
.utc()
.hour(Math.floor((values.time_off ?? 0) / 100))
.minute(Math.floor((values.time_off ?? 0) % 100))
.second(0)
.millisecond(0)
.toISOString()
: null,
time_down: values.time_down
? date
.utc()
.hour(Math.floor((values.time_down ?? 0) / 100))
.minute(Math.floor((values.time_down ?? 0) % 100))
.second(0)
.millisecond(0)
.toISOString()
: null,
time_stop: values.time_stop
? date
.utc()
.hour(Math.floor((values.time_stop ?? 0) / 100))
.minute(Math.floor((values.time_stop ?? 0) % 100))
.second(0)
.millisecond(0)
.toISOString()
: null,
};
return newFlight;
} catch (err) {