Files
spoolman2/client/src/pages/vendors/edit.tsx
tonym 5c29936231
Some checks failed
CI / style (push) Has been cancelled
CI / build-client (push) Has been cancelled
CI / build-amd64 (push) Has been cancelled
CI / build-tester (push) Has been cancelled
CI / test (cockroachdb) (push) Has been cancelled
CI / test (mariadb) (push) Has been cancelled
CI / test (postgres) (push) Has been cancelled
CI / test (sqlite) (push) Has been cancelled
CI / build-arm64 (push) Has been cancelled
CI / build-armv7 (push) Has been cancelled
CI / publish-images (push) Has been cancelled
CI / publish-release (push) Has been cancelled
Add prev/next navigation buttons to edit pages
- Filament, spool, and vendor edit pages now have Prev/Next buttons
- Navigate between items without returning to the list
- Buttons are disabled at the beginning/end of the list

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 00:10:04 -06:00

180 lines
5.7 KiB
TypeScript

import { LeftOutlined, RightOutlined } from "@ant-design/icons";
import { Edit, useForm } from "@refinedev/antd";
import { HttpError, IResourceComponentsProps, useList, useTranslate } from "@refinedev/core";
import { Alert, Button, DatePicker, Form, Input, InputNumber, message, Typography } from "antd";
import TextArea from "antd/es/input/TextArea";
import dayjs from "dayjs";
import React, { useMemo, useState } from "react";
import { useNavigate } from "react-router";
import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields";
import { EntityType, useGetFields } from "../../utils/queryFields";
import { IVendor, IVendorParsedExtras } from "./model";
/*
The API returns the extra fields as JSON values, but we need to parse them into their real types
in order for Ant design's form to work properly. ParsedExtras does this for us.
We also need to stringify them again before sending them back to the API, which is done by overriding
the form's onFinish method. Form.Item's normalize should do this, but it doesn't seem to work.
*/
export const VendorEdit: React.FC<IResourceComponentsProps> = () => {
const t = useTranslate();
const navigate = useNavigate();
const [messageApi, contextHolder] = message.useMessage();
const [hasChanged, setHasChanged] = useState(false);
const extraFields = useGetFields(EntityType.vendor);
// Get all vendor IDs for prev/next navigation
const { data: allVendors } = useList<IVendor>({
resource: "vendor",
pagination: { mode: "off" },
sorters: [{ field: "id", order: "asc" }],
});
const { formProps, saveButtonProps, id } = useForm<IVendor, HttpError, IVendor, IVendor>({
liveMode: "manual",
onLiveEvent() {
// Warn the user if the vendor has been updated since the form was opened
messageApi.warning(t("vendor.form.vendor_updated"));
setHasChanged(true);
},
});
// Calculate prev/next IDs for navigation
const { prevId, nextId } = useMemo(() => {
if (!allVendors?.data || !id) return { prevId: null, nextId: null };
const ids = allVendors.data.map((v) => v.id);
const currentIndex = ids.indexOf(Number(id));
return {
prevId: currentIndex > 0 ? ids[currentIndex - 1] : null,
nextId: currentIndex < ids.length - 1 ? ids[currentIndex + 1] : null,
};
}, [allVendors?.data, id]);
// Parse the extra fields from string values into real types
if (formProps.initialValues) {
formProps.initialValues = ParsedExtras(formProps.initialValues);
}
// Override the form's onFinish method to stringify the extra fields
const originalOnFinish = formProps.onFinish;
formProps.onFinish = (allValues: IVendorParsedExtras) => {
if (allValues !== undefined && allValues !== null) {
// Lot of stupidity here to make types work
const stringifiedAllValues = StringifiedExtras<IVendorParsedExtras>(allValues);
originalOnFinish?.({
extra: {},
...stringifiedAllValues,
});
}
};
return (
<Edit
saveButtonProps={saveButtonProps}
headerButtons={({ defaultButtons }) => (
<>
<Button
icon={<LeftOutlined />}
disabled={!prevId}
onClick={() => prevId && navigate(`/vendor/edit/${prevId}`)}
>
{t("buttons.prev")}
</Button>
<Button
icon={<RightOutlined />}
disabled={!nextId}
onClick={() => nextId && navigate(`/vendor/edit/${nextId}`)}
>
{t("buttons.next")}
</Button>
{defaultButtons}
</>
)}
>
{contextHolder}
<Form {...formProps} layout="vertical">
<Form.Item
label={t("vendor.fields.id")}
name={["id"]}
rules={[
{
required: true,
},
]}
>
<Input readOnly disabled />
</Form.Item>
<Form.Item
label={t("vendor.fields.registered")}
name={["registered"]}
rules={[
{
required: true,
},
]}
getValueProps={(value) => ({
value: value ? dayjs(value) : undefined,
})}
>
<DatePicker disabled showTime format="YYYY-MM-DD HH:mm:ss" />
</Form.Item>
<Form.Item
label={t("vendor.fields.name")}
name={["name"]}
rules={[
{
required: true,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label={t("vendor.fields.comment")}
name={["comment"]}
rules={[
{
required: false,
},
]}
>
<TextArea maxLength={1024} />
</Form.Item>
<Form.Item
label={t("vendor.fields.empty_spool_weight")}
help={t("vendor.fields_help.empty_spool_weight")}
name={["empty_spool_weight"]}
rules={[
{
required: false,
type: "number",
min: 0,
},
]}
>
<InputNumber addonAfter="g" precision={1} />
</Form.Item>
<Form.Item
label={t("vendor.fields.external_id")}
name={["external_id"]}
rules={[
{
required: false,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Typography.Title level={5}>{t("settings.extra_fields.tab")}</Typography.Title>
{extraFields.data?.map((field, index) => (
<ExtraFieldFormItem key={index} field={field} />
))}
</Form>
{hasChanged && <Alert description={t("vendor.form.vendor_updated")} type="warning" showIcon />}
</Edit>
);
};
export default VendorEdit;