Merge pull request #172 from clevelandcs/master

#76 spool list, no reload on page change
This commit is contained in:
Donkie
2023-10-29 10:06:01 +01:00
committed by GitHub
7 changed files with 113 additions and 27 deletions

View File

@@ -9,6 +9,7 @@
"buttons": { "buttons": {
"create": "Create", "create": "Create",
"save": "Save", "save": "Save",
"saveAndAdd": "Save and Add",
"logout": "Log out", "logout": "Log out",
"delete": "Delete", "delete": "Delete",
"edit": "Edit", "edit": "Edit",
@@ -190,7 +191,8 @@
"comment": "Comment", "comment": "Comment",
"settings_extruder_temp": "Extruder Temp", "settings_extruder_temp": "Extruder Temp",
"settings_bed_temp": "Bed Temp", "settings_bed_temp": "Bed Temp",
"color_hex": "Color" "color_hex": "Color",
"spools": "Show Spools"
}, },
"fields_help": { "fields_help": {
"name": "Filament name, to distinguish this filament type among others from the same vendor. Should contain the color for example.", "name": "Filament name, to distinguish this filament type among others from the same vendor. Should contain the color for example.",

View File

@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { IResourceComponentsProps, useTranslate } from "@refinedev/core"; import { IResourceComponentsProps, useTranslate } from "@refinedev/core";
import { Create, useForm, useSelect } from "@refinedev/antd"; import { Create, useForm, useSelect } from "@refinedev/antd";
import { Form, Input, Select, InputNumber, ColorPicker } from "antd"; import { Form, Input, Select, InputNumber, ColorPicker, Button } from "antd";
import TextArea from "antd/es/input/TextArea"; import TextArea from "antd/es/input/TextArea";
import { numberFormatter, numberParser } from "../../utils/parsing"; import { numberFormatter, numberParser } from "../../utils/parsing";
import { IVendor } from "../vendors/model"; import { IVendor } from "../vendors/model";
@@ -14,7 +14,7 @@ interface CreateOrCloneProps {
export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps> = (props) => { export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps> = (props) => {
const t = useTranslate(); const t = useTranslate();
const { formProps, saveButtonProps, formLoading } = useForm<IFilament>(); const { form, formProps, saveButtonProps, formLoading, onFinish, redirect } = useForm<IFilament>();
if (props.mode === "clone" && formProps.initialValues) { if (props.mode === "clone" && formProps.initialValues) {
// Fix the vendor_id // Fix the vendor_id
@@ -23,6 +23,12 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
} }
} }
const handleSubmit = async (redirectTo: "list" | "edit" | "create") => {
let values = await form.validateFields();
await onFinish(values);
redirect(redirectTo, (values as IFilament).id);
}
const { selectProps } = useSelect<IVendor>({ const { selectProps } = useSelect<IVendor>({
resource: "vendor", resource: "vendor",
optionLabel: "name", optionLabel: "name",
@@ -32,7 +38,12 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
<Create <Create
title={props.mode === "create" ? t("filament.titles.create") : t("filament.titles.clone")} title={props.mode === "create" ? t("filament.titles.create") : t("filament.titles.clone")}
isLoading={formLoading} isLoading={formLoading}
saveButtonProps={saveButtonProps} footerButtons={() => (
<>
<Button type="primary" onClick={() => handleSubmit("list")}>{t("buttons.save")}</Button>
<Button type="primary" onClick={() => handleSubmit("create")}>{t("buttons.saveAndAdd")}</Button>
</>
)}
> >
<Form {...formProps} layout="vertical"> <Form {...formProps} layout="vertical">
<Form.Item <Form.Item

View File

@@ -8,14 +8,14 @@ import utc from "dayjs/plugin/utc";
import { IFilament } from "./model"; import { IFilament } from "./model";
import { enrichText } from "../../utils/parsing"; import { enrichText } from "../../utils/parsing";
import { IVendor } from "../vendors/model"; import { IVendor } from "../vendors/model";
import { useNavigate } from "react-router-dom";
dayjs.extend(utc); dayjs.extend(utc);
const { Title } = Typography; const { Title } = Typography;
export const FilamentShow: React.FC<IResourceComponentsProps> = () => { export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
const t = useTranslate(); const t = useTranslate();
const navigate = useNavigate();
const { queryResult } = useShow<IFilament>({ const { queryResult } = useShow<IFilament>({
liveMode: "auto", liveMode: "auto",
}); });
@@ -31,17 +31,24 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
return t("filament.titles.show_title", { id: item.id, name: vendorPrefix + item.name }); return t("filament.titles.show_title", { id: item.id, name: vendorPrefix + item.name });
}; };
const formatVendor = (item: IVendor) => { const gotoVendor = (): undefined => {
const URL = `/vendor/show/${item.id}`; const URL = `/vendor/show/${record?.vendor?.id}`;
return <a href={URL}>{item.name}</a>; navigate(URL);
}; }
const gotoSpools = (): undefined => {
const URL = `/spool#filters=[{"field":"filament.id","operator":"in","value":[${record?.id}]}]`
navigate(URL);
}
return ( return (
<Show isLoading={isLoading} title={record ? formatTitle(record) : ""}> <Show isLoading={isLoading} title={record ? formatTitle(record) : ""}>
<Title level={5}>{t("filament.fields.id")}</Title> <Title level={5}>{t("filament.fields.id")}</Title>
<NumberField value={record?.id ?? ""} /> <NumberField value={record?.id ?? ""} />
<Title level={5}>{t("filament.fields.vendor")}</Title> <Title level={5}>{t("filament.fields.vendor")}</Title>
<TextField value={record?.vendor ? formatVendor(record?.vendor) : ""} /> <button onClick={gotoVendor} style={{ background: 'none', border: 'none', color: 'blue', cursor: 'pointer', paddingLeft: 0 }}>
{record ? record.vendor?.name : ""}
</button>
<Title level={5}>{t("filament.fields.registered")}</Title> <Title level={5}>{t("filament.fields.registered")}</Title>
<DateField <DateField
value={dayjs.utc(record?.registered).local()} value={dayjs.utc(record?.registered).local()}
@@ -110,7 +117,11 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
<TextField value={record?.article_number} /> <TextField value={record?.article_number} />
<Title level={5}>{t("filament.fields.comment")}</Title> <Title level={5}>{t("filament.fields.comment")}</Title>
<TextField value={enrichText(record?.comment)} /> <TextField value={enrichText(record?.comment)} />
</Show> <Title level={5}>{t("filament.fields.spools")}</Title>
<button onClick={gotoSpools} style={{ background: 'none', border: 'none', color: 'blue', cursor: 'pointer', paddingLeft: 0 }}>
{record ? formatTitle(record) : ""}
</button>
</Show >
); );
}; };

View File

@@ -1,13 +1,15 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { IResourceComponentsProps, useTranslate } from "@refinedev/core"; import { IResourceComponentsProps, useTranslate } from "@refinedev/core";
import { Create, useForm, useSelect } from "@refinedev/antd"; import { Create, useForm, useSelect } from "@refinedev/antd";
import { Form, Input, DatePicker, Select, InputNumber, Radio, Divider } from "antd"; import { Form, Input, DatePicker, Select, InputNumber, Radio, Divider, Button } from "antd";
import dayjs from "dayjs"; import dayjs from "dayjs";
import TextArea from "antd/es/input/TextArea"; import TextArea from "antd/es/input/TextArea";
import { IFilament } from "../filaments/model"; import { IFilament } from "../filaments/model";
import { ISpool } from "./model"; import { ISpool } from "./model";
import { numberFormatter, numberParser } from "../../utils/parsing"; import { numberFormatter, numberParser } from "../../utils/parsing";
import { useSpoolmanLocations } from "../../components/otherModels"; import { useSpoolmanLocations } from "../../components/otherModels";
import { MinusOutlined, PlusOutlined } from "@ant-design/icons";
import '../../utils/overrides.css'
interface CreateOrCloneProps { interface CreateOrCloneProps {
mode: "create" | "clone"; mode: "create" | "clone";
@@ -16,7 +18,10 @@ interface CreateOrCloneProps {
export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps> = (props) => { export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps> = (props) => {
const t = useTranslate(); const t = useTranslate();
const { form, formProps, saveButtonProps, formLoading } = useForm<ISpool>(); const { form, formProps, formLoading, onFinish, redirect } = useForm<ISpool>({
redirect: false,
warnWhenUnsavedChanges: false
});
if (props.mode === "clone" && formProps.initialValues) { if (props.mode === "clone" && formProps.initialValues) {
// Clear out the values that we don't want to clone // Clear out the values that we don't want to clone
@@ -28,6 +33,18 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
formProps.initialValues.filament_id = formProps.initialValues.filament.id; formProps.initialValues.filament_id = formProps.initialValues.filament.id;
} }
const handleSubmit = async (redirectTo: "list" | "edit" | "create") => {
let values = await form.validateFields();
if (quantity > 1) {
let submit = Array(quantity).fill(values);
// queue multiple creates this way for now Refine doesn't seem to map Arrays to createMany or multiple creates like it says it does
submit.forEach(async r => await onFinish(r));
} else {
await onFinish(values);
}
redirect(redirectTo, (values as ISpool).id);
}
const { queryResult } = useSelect<IFilament>({ const { queryResult } = useSelect<IFilament>({
resource: "filament", resource: "filament",
}); });
@@ -100,11 +117,42 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
allLocations.push(newLocation.trim()); allLocations.push(newLocation.trim());
} }
const [quantity, setQuantity] = useState(1);
const incrementQty = () => {
setQuantity(quantity + 1);
}
const decrementQty = () => {
setQuantity(quantity - 1);
}
return ( return (
<Create <Create
title={props.mode === "create" ? t("spool.titles.create") : t("spool.titles.clone")} title={props.mode === "create" ? t("spool.titles.create") : t("spool.titles.clone")}
saveButtonProps={saveButtonProps}
isLoading={formLoading} isLoading={formLoading}
footerButtons={() => (
<>
<div style={{ display: 'flex', backgroundColor: '#141414', border: '1px solid #424242', borderRadius: '6px' }}>
<Button type="text" style={{ padding: 0, width: 32, height: 32 }} onClick={decrementQty}>
<MinusOutlined />
</Button>
<InputNumber
name="Quantity"
min={1}
id="qty-input"
controls={false}
value={quantity}
>
</InputNumber>
<Button type="text" style={{ padding: 0, width: 32, height: 32 }} onClick={incrementQty}>
<PlusOutlined />
</Button>
</div>
<Button type="primary" onClick={() => handleSubmit("list")}>{t("buttons.save")}</Button>
<Button type="primary" onClick={() => handleSubmit("create")}>{t("buttons.saveAndAdd")}</Button>
</>
)
}
> >
<Form {...formProps} layout="vertical"> <Form {...formProps} layout="vertical">
<Form.Item <Form.Item
@@ -278,7 +326,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
<TextArea maxLength={1024} /> <TextArea maxLength={1024} />
</Form.Item> </Form.Item>
</Form> </Form>
</Create> </Create >
); );
}; };

View File

@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { IResourceComponentsProps, useTranslate } from "@refinedev/core"; import { IResourceComponentsProps, useTranslate } from "@refinedev/core";
import { Create, useForm } from "@refinedev/antd"; import { Create, useForm } from "@refinedev/antd";
import { Form, Input } from "antd"; import { Button, Form, Input } from "antd";
import TextArea from "antd/es/input/TextArea"; import TextArea from "antd/es/input/TextArea";
import { IVendor } from "./model"; import { IVendor } from "./model";
@@ -12,13 +12,24 @@ interface CreateOrCloneProps {
export const VendorCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps> = (props) => { export const VendorCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps> = (props) => {
const t = useTranslate(); const t = useTranslate();
const { formProps, saveButtonProps, formLoading } = useForm<IVendor>(); const { form, formProps, saveButtonProps, formLoading, onFinish, redirect } = useForm<IVendor>();
const handleSubmit = async (redirectTo: "list" | "edit" | "create") => {
let values = await form.validateFields();
await onFinish(values);
redirect(redirectTo, (values as IVendor).id);
}
return ( return (
<Create <Create
title={props.mode === "create" ? t("vendor.titles.create") : t("vendor.titles.clone")} title={props.mode === "create" ? t("vendor.titles.create") : t("vendor.titles.clone")}
isLoading={formLoading} isLoading={formLoading}
saveButtonProps={saveButtonProps} footerButtons={() => (
<>
<Button type="primary" onClick={() => handleSubmit("list")}>{t("buttons.save")}</Button>
<Button type="primary" onClick={() => handleSubmit("create")}>{t("buttons.saveAndAdd")}</Button>
</>
)}
> >
<Form {...formProps} layout="vertical"> <Form {...formProps} layout="vertical">
<Form.Item <Form.Item

View File

@@ -0,0 +1,3 @@
#qty-input {
text-align: center !important;
}

View File

@@ -18,18 +18,18 @@ export function useInitialTableState(tableId: string): TableState {
const savedSorters = hasHashProperty("sorters") const savedSorters = hasHashProperty("sorters")
? getHashProperty("sorters") ? getHashProperty("sorters")
: isLocalStorageAvailable : isLocalStorageAvailable
? localStorage.getItem(`${tableId}-sorters`) ? localStorage.getItem(`${tableId}-sorters`)
: null; : null;
const savedFilters = hasHashProperty("filters") const savedFilters = hasHashProperty("filters")
? getHashProperty("filters") ? getHashProperty("filters")
: isLocalStorageAvailable : isLocalStorageAvailable
? localStorage.getItem(`${tableId}-filters`) ? localStorage.getItem(`${tableId}-filters`)
: null; : null;
const savedPagination = hasHashProperty("pagination") const savedPagination = hasHashProperty("pagination")
? getHashProperty("pagination") ? getHashProperty("pagination")
: isLocalStorageAvailable : isLocalStorageAvailable
? localStorage.getItem(`${tableId}-pagination`) ? localStorage.getItem(`${tableId}-pagination`)
: null; : null;
const savedShowColumns = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-showColumns`) : null; const savedShowColumns = isLocalStorageAvailable ? localStorage.getItem(`${tableId}-showColumns`) : null;
const sorters = savedSorters ? JSON.parse(savedSorters) : [{ field: "id", order: "asc" }]; const sorters = savedSorters ? JSON.parse(savedSorters) : [{ field: "id", order: "asc" }];
@@ -59,7 +59,7 @@ export function useStoreInitialState(tableId: string, state: TableState) {
if (filters.length > 0) { if (filters.length > 0) {
if (isLocalStorageAvailable) { if (isLocalStorageAvailable) {
localStorage.setItem(`${tableId}-filters`, JSON.stringify(filters)); localStorage.setItem(`${tableId}-filters`, JSON.stringify(filters));
setURLHash("filters", JSON.stringify(state.filters)); setURLHash("filters", JSON.stringify(filters));
} }
} else { } else {
localStorage.removeItem(`${tableId}-filters`); localStorage.removeItem(`${tableId}-filters`);