Improve image editing, allow null times
This commit is contained in:
@@ -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}
|
||||
|
@@ -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
|
||||
|
75
web/app/ui/form/image-list-input.tsx
Normal file
75
web/app/ui/form/image-list-input.tsx
Normal 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>
|
||||
);
|
||||
}
|
65
web/app/ui/input/image-list-input.tsx
Normal file
65
web/app/ui/input/image-list-input.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user