Added a basic web UI

This commit is contained in:
Donkie
2023-05-10 21:11:10 +02:00
parent 1c59e0efd5
commit 0269518175
39 changed files with 10950 additions and 1 deletions

145
client/src/App.tsx Normal file
View File

@@ -0,0 +1,145 @@
import { Refine } from "@refinedev/core";
import { RefineKbar, RefineKbarProvider } from "@refinedev/kbar";
import {
ErrorComponent,
notificationProvider,
ThemedLayoutV2,
ThemedSiderV2,
ThemedTitleV2,
} from "@refinedev/antd";
import "@refinedev/antd/dist/reset.css";
import routerBindings, {
NavigateToResource,
UnsavedChangesNotifier,
} from "@refinedev/react-router-v6";
import dataProvider from "@refinedev/simple-rest";
import { useTranslation } from "react-i18next";
import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom";
import { AppIcon } from "./components/app-icon";
import { Header } from "./components/header";
import { ColorModeContextProvider } from "./contexts/color-mode";
import { SpoolCreate, SpoolEdit, SpoolList, SpoolShow } from "./pages/spools";
import {
FilamentCreate,
FilamentEdit,
FilamentList,
FilamentShow,
} from "./pages/filaments";
import {
VendorCreate,
VendorEdit,
VendorList,
VendorShow,
} from "./pages/vendors";
function App() {
const { t, i18n } = useTranslation();
const i18nProvider = {
translate: (key: string, params: object) => t(key, params),
changeLocale: (lang: string) => i18n.changeLanguage(lang),
getLocale: () => i18n.language,
};
return (
<BrowserRouter>
<RefineKbarProvider>
<ColorModeContextProvider>
<Refine
dataProvider={dataProvider("/api/v1")}
notificationProvider={notificationProvider}
i18nProvider={i18nProvider}
routerProvider={routerBindings}
resources={[
{
name: "spool",
list: "/spool",
create: "/spool/create",
edit: "/spool/edit/:id",
show: "/spool/show/:id",
meta: {
canDelete: true,
},
},
{
name: "filament",
list: "/filament",
create: "/filament/create",
edit: "/filament/edit/:id",
show: "/filament/show/:id",
meta: {
canDelete: true,
},
},
{
name: "vendor",
list: "/vendor",
create: "/vendor/create",
edit: "/vendor/edit/:id",
show: "/vendor/show/:id",
meta: {
canDelete: true,
},
},
]}
options={{
syncWithLocation: true,
warnWhenUnsavedChanges: true,
}}
>
<Routes>
<Route
element={
<ThemedLayoutV2
Header={() => <Header sticky />}
Sider={() => <ThemedSiderV2 fixed />}
Title={({ collapsed }) => (
<ThemedTitleV2
collapsed={collapsed}
text="Spoolman"
icon={<AppIcon />}
/>
)}
>
<Outlet />
</ThemedLayoutV2>
}
>
<Route
index
element={<NavigateToResource resource="spool" />}
/>
<Route path="/spool">
<Route index element={<SpoolList />} />
<Route path="create" element={<SpoolCreate />} />
<Route path="edit/:id" element={<SpoolEdit />} />
<Route path="show/:id" element={<SpoolShow />} />
</Route>
<Route path="/filament">
<Route index element={<FilamentList />} />
<Route path="create" element={<FilamentCreate />} />
<Route path="edit/:id" element={<FilamentEdit />} />
<Route path="show/:id" element={<FilamentShow />} />
</Route>
<Route path="/vendor">
<Route index element={<VendorList />} />
<Route path="create" element={<VendorCreate />} />
<Route path="edit/:id" element={<VendorEdit />} />
<Route path="show/:id" element={<VendorShow />} />
</Route>
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
<RefineKbar />
<UnsavedChangesNotifier />
</Refine>
</ColorModeContextProvider>
</RefineKbarProvider>
</BrowserRouter>
);
}
export default App;

View File

@@ -0,0 +1,19 @@
import React from "react";
export const AppIcon: React.FC = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 16 16"
fill="none"
>
<g fill="currentColor">
<path d="M8 6c4.418 0 8-1.343 8-3s-3.582-3-8-3-8 1.343-8 3 3.582 3 8 3z" />
<path d="M8 8c4.418 0 8-1.343 8-3v3c0 1.657-3.582 3-8 3S0 9.657 0 8V5c0 1.657 3.582 3 8 3z" />
<path d="M8 13c4.418 0 8-1.343 8-3v3c0 1.657-3.582 3-8 3s-8-1.343-8-3v-3c0 1.657 3.582 3 8 3z" />
</g>
</svg>
);
};

View File

@@ -0,0 +1,98 @@
import { DownOutlined } from "@ant-design/icons";
import type { RefineThemedLayoutV2HeaderProps } from "@refinedev/antd";
import { useGetIdentity, useGetLocale, useSetLocale } from "@refinedev/core";
import {
Avatar,
Button,
Dropdown,
Layout as AntdLayout,
MenuProps,
Space,
Switch,
theme,
Typography,
} from "antd";
import React, { useContext } from "react";
import { useTranslation } from "react-i18next";
import { ColorModeContext } from "../../contexts/color-mode";
const { Text } = Typography;
const { useToken } = theme;
type IUser = {
id: number;
name: string;
avatar: string;
};
export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({
sticky,
}) => {
const { token } = useToken();
const { i18n } = useTranslation();
const locale = useGetLocale();
const changeLanguage = useSetLocale();
const { data: user } = useGetIdentity<IUser>();
const { mode, setMode } = useContext(ColorModeContext);
const currentLocale = locale();
const menuItems: MenuProps["items"] = [...(i18n.languages || [])]
.sort()
.map((lang: string) => ({
key: lang,
onClick: () => changeLanguage(lang),
icon: (
<span style={{ marginRight: 8 }}>
<Avatar size={16} src={`/images/flags/${lang}.svg`} />
</span>
),
label: lang === "en" ? "English" : "German",
}));
const headerStyles: React.CSSProperties = {
backgroundColor: token.colorBgElevated,
display: "flex",
justifyContent: "flex-end",
alignItems: "center",
padding: "0px 24px",
height: "64px",
};
if (sticky) {
headerStyles.position = "sticky";
headerStyles.top = 0;
headerStyles.zIndex = 1;
}
return (
<AntdLayout.Header style={headerStyles}>
<Space>
<Dropdown
menu={{
items: menuItems,
selectedKeys: currentLocale ? [currentLocale] : [],
}}
>
<Button type="text">
<Space>
<Avatar size={16} src={`/images/flags/${currentLocale}.svg`} />
{currentLocale === "en" ? "English" : "German"}
<DownOutlined />
</Space>
</Button>
</Dropdown>
<Switch
checkedChildren="🌛"
unCheckedChildren="🔆"
onChange={() => setMode(mode === "light" ? "dark" : "light")}
defaultChecked={mode === "dark"}
/>
<Space style={{ marginLeft: "8px" }} size="middle">
{user?.name && <Text strong>{user.name}</Text>}
{user?.avatar && <Avatar src={user?.avatar} alt={user?.name} />}
</Space>
</Space>
</AntdLayout.Header>
);
};

View File

@@ -0,0 +1 @@
export { Header } from "./header";

View File

@@ -0,0 +1,59 @@
import { RefineThemes } from "@refinedev/antd";
import { ConfigProvider, theme } from "antd";
import { createContext, PropsWithChildren, useEffect, useState } from "react";
type ColorModeContextType = {
mode: string;
setMode: (mode: string) => void;
};
export const ColorModeContext = createContext<ColorModeContextType>(
{} as ColorModeContextType
);
export const ColorModeContextProvider: React.FC<PropsWithChildren> = ({
children,
}) => {
const colorModeFromLocalStorage = localStorage.getItem("colorMode");
const isSystemPreferenceDark = window?.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
const systemPreference = isSystemPreferenceDark ? "dark" : "light";
const [mode, setMode] = useState(
colorModeFromLocalStorage || systemPreference
);
useEffect(() => {
window.localStorage.setItem("colorMode", mode);
}, [mode]);
const setColorMode = () => {
if (mode === "light") {
setMode("dark");
} else {
setMode("light");
}
};
const { darkAlgorithm, defaultAlgorithm } = theme;
return (
<ColorModeContext.Provider
value={{
setMode: setColorMode,
mode,
}}
>
<ConfigProvider
// you can change the theme colors here. example: ...RefineThemes.Magenta,
theme={{
...RefineThemes.Yellow,
algorithm: mode === "light" ? defaultAlgorithm : darkAlgorithm,
}}
>
{children}
</ConfigProvider>
</ColorModeContext.Provider>
);
};

19
client/src/i18n.ts Normal file
View File

@@ -0,0 +1,19 @@
import i18n from "i18next";
import detector from "i18next-browser-languagedetector";
import Backend from "i18next-xhr-backend";
import { initReactI18next } from "react-i18next";
i18n
.use(Backend)
.use(detector)
.use(initReactI18next)
.init({
supportedLngs: ["en"],
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json",
},
defaultNS: "common",
fallbackLng: ["en"],
});
export default i18n;

16
client/src/index.tsx Normal file
View File

@@ -0,0 +1,16 @@
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./i18n";
const container = document.getElementById("root") as HTMLElement;
const root = createRoot(container);
root.render(
<React.StrictMode>
<React.Suspense fallback="loading">
<App />
</React.Suspense>
</React.StrictMode>
);

View File

@@ -0,0 +1,133 @@
import React from "react";
import { IResourceComponentsProps } from "@refinedev/core";
import { Create, useForm } from "@refinedev/antd";
import { Form, Input, DatePicker, Select, InputNumber } from "antd";
import dayjs from "dayjs";
import TextArea from "antd/es/input/TextArea";
export const FilamentCreate: React.FC<IResourceComponentsProps> = () => {
const { formProps, saveButtonProps, queryResult } = useForm();
return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item
label="Name"
help="Filament name, to distinguish this filament type among others from the same vendor. Should contain the color for example."
name={["name"]}
rules={[
{
required: false,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label="Vendor"
name={["vendor", "name"]}
rules={[
{
required: false,
},
]}
>
{/* <Select {...vendorSelectProps} /> */}
</Form.Item>
<Form.Item
label="Material"
help="E.g. PLA, ABS, PETG, etc."
name={["material"]}
rules={[
{
required: false,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label="Price"
help="Price of a full spool in the system configured currency."
name={["price"]}
rules={[
{
required: false,
},
]}
>
<InputNumber min={0} />
</Form.Item>
<Form.Item
label="Density"
name={["density"]}
rules={[
{
required: true,
},
]}
>
<InputNumber min={0} addonAfter="g/cm³" />
</Form.Item>
<Form.Item
label="Diameter"
name={["diameter"]}
rules={[
{
required: true,
},
]}
>
<InputNumber min={0} addonAfter="mm" />
</Form.Item>
<Form.Item
label="Weight"
help="The filament weight of a full spool (net weight)."
name={["weight"]}
rules={[
{
required: false,
},
]}
>
<InputNumber min={0} addonAfter="g" />
</Form.Item>
<Form.Item
label="Spool Weight"
help="The weight of an empty spool."
name={["spool_weight"]}
rules={[
{
required: false,
},
]}
>
<InputNumber min={0} addonAfter="g" />
</Form.Item>
<Form.Item
label="Article Number"
name={["article_number"]}
help="E.g. EAN, UPC, etc."
rules={[
{
required: false,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label="Comment"
name={["comment"]}
rules={[
{
required: false,
},
]}
>
<TextArea maxLength={1024} />
</Form.Item>
</Form>
</Create>
);
};

View File

@@ -0,0 +1,160 @@
import React from "react";
import { IResourceComponentsProps } from "@refinedev/core";
import { Edit, useForm } from "@refinedev/antd";
import { Form, Input, DatePicker, Select, InputNumber } from "antd";
import dayjs from "dayjs";
import TextArea from "antd/es/input/TextArea";
export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
const { formProps, saveButtonProps, queryResult } = useForm();
const filamentData = queryResult?.data?.data;
return (
<Edit saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item
label="Id"
name={["id"]}
rules={[
{
required: true,
},
]}
>
<Input readOnly disabled />
</Form.Item>
<Form.Item
label="Registered"
name={["registered"]}
rules={[
{
required: true,
},
]}
getValueProps={(value) => ({
value: value ? dayjs(value) : undefined,
})}
>
<DatePicker disabled format="lll" />
</Form.Item>
<Form.Item
label="Name"
help="Filament name, to distinguish this filament type among others from the same vendor. Should contain the color for example."
name={["name"]}
rules={[
{
required: false,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label="Vendor"
name={["vendor", "name"]}
rules={[
{
required: false,
},
]}
>
{/* <Select {...vendorSelectProps} /> */}
</Form.Item>
<Form.Item
label="Material"
help="E.g. PLA, ABS, PETG, etc."
name={["material"]}
rules={[
{
required: false,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label="Price"
help="Price of a full spool in the system configured currency."
name={["price"]}
rules={[
{
required: false,
},
]}
>
<InputNumber min={0} />
</Form.Item>
<Form.Item
label="Density"
name={["density"]}
rules={[
{
required: true,
},
]}
>
<InputNumber min={0} addonAfter="g/cm³" />
</Form.Item>
<Form.Item
label="Diameter"
name={["diameter"]}
rules={[
{
required: true,
},
]}
>
<InputNumber min={0} addonAfter="mm" />
</Form.Item>
<Form.Item
label="Weight"
help="The filament weight of a full spool (net weight)."
name={["weight"]}
rules={[
{
required: false,
},
]}
>
<InputNumber min={0} addonAfter="g" />
</Form.Item>
<Form.Item
label="Spool Weight"
help="The weight of an empty spool."
name={["spool_weight"]}
rules={[
{
required: false,
},
]}
>
<InputNumber min={0} addonAfter="g" />
</Form.Item>
<Form.Item
label="Article Number"
name={["article_number"]}
help="E.g. EAN, UPC, etc."
rules={[
{
required: false,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label="Comment"
name={["comment"]}
rules={[
{
required: false,
},
]}
>
<TextArea maxLength={1024} />
</Form.Item>
</Form>
</Edit>
);
};

View File

@@ -0,0 +1,4 @@
export { FilamentCreate } from "./create";
export { FilamentEdit } from "./edit";
export { FilamentList } from "./list";
export { FilamentShow } from "./show";

View File

@@ -0,0 +1,106 @@
import React from "react";
import { IResourceComponentsProps, BaseRecord } from "@refinedev/core";
import {
useTable,
List,
EditButton,
ShowButton,
DateField,
NumberField,
} from "@refinedev/antd";
import { Table, Space } from "antd";
export const FilamentList: React.FC<IResourceComponentsProps> = () => {
const { tableProps } = useTable({
syncWithLocation: true,
});
return (
<List>
<Table {...tableProps} rowKey="id">
<Table.Column dataIndex="id" title="Id" />
<Table.Column dataIndex={["vendor", "name"]} title="Vendor" />
<Table.Column dataIndex="name" title="Name" />
<Table.Column dataIndex="material" title="Material" />
<Table.Column dataIndex="price" title="Price" />
<Table.Column
dataIndex="density"
title="Density"
render={(value: any) => (
<NumberField
value={value}
options={{
unitDisplay: "short",
unit: "gram",
style: "unit",
maximumFractionDigits: 2,
}}
/>
)}
/>
<Table.Column
dataIndex="diameter"
title="Diameter"
render={(value: any) => (
<NumberField
value={value}
options={{
unitDisplay: "short",
unit: "millimeter",
style: "unit",
maximumFractionDigits: 2,
}}
/>
)}
/>
<Table.Column
dataIndex="weight"
title="Weight"
render={(value: any) => (
<NumberField
value={value}
options={{
unitDisplay: "short",
unit: "gram",
style: "unit",
maximumFractionDigits: 1,
}}
/>
)}
/>
<Table.Column
dataIndex="spool_weight"
title="Spool Weight"
render={(value: any) => (
<NumberField
value={value}
options={{
unitDisplay: "short",
unit: "gram",
style: "unit",
maximumFractionDigits: 1,
}}
/>
)}
/>
<Table.Column dataIndex="article_number" title="Article Number" />
<Table.Column
dataIndex={["registered"]}
title="Registered"
render={(value: any) => <DateField value={value} />}
/>
<Table.Column dataIndex={["comment"]} title="Comment" />
<Table.Column
title="Actions"
dataIndex="actions"
render={(_, record: BaseRecord) => (
<Space>
<EditButton hideText size="small" recordItemId={record.id} />
<ShowButton hideText size="small" recordItemId={record.id} />
</Space>
)}
/>
</Table>
</List>
);
};

View File

@@ -0,0 +1,80 @@
import React from "react";
import { IResourceComponentsProps, useShow } from "@refinedev/core";
import {
Show,
NumberField,
DateField,
TagField,
TextField,
} from "@refinedev/antd";
import { Typography } from "antd";
const { Title } = Typography;
export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
const { queryResult } = useShow();
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Title level={5}>Id</Title>
<NumberField value={record?.id ?? ""} />
<Title level={5}>Registered</Title>
<DateField value={record?.registered} format="lll" />
<Title level={5}>Name</Title>
<TextField value={record?.name} />
<Title level={5}>Vendor</Title>
{/* {vendorIsLoading ? <>Loading...</> : <>{vendorData?.data?.id}</>} */}
<Title level={5}>Material</Title>
<TextField value={record?.material} />
<Title level={5}>Price</Title>
<NumberField value={record?.price ?? ""} />
<Title level={5}>Density</Title>
<NumberField
value={record?.density ?? ""}
options={{
unitDisplay: "short",
unit: "gram",
style: "unit",
maximumFractionDigits: 2,
}}
/>
<Title level={5}>Diameter</Title>
<NumberField
value={record?.diameter ?? ""}
options={{
unitDisplay: "short",
unit: "millimeter",
style: "unit",
maximumFractionDigits: 2,
}}
/>
<Title level={5}>Weight</Title>
<NumberField
value={record?.weight ?? ""}
options={{
unitDisplay: "short",
unit: "gram",
style: "unit",
maximumFractionDigits: 1,
}}
/>
<Title level={5}>Spool Weight</Title>
<NumberField
value={record?.spool_weight ?? ""}
options={{
unitDisplay: "short",
unit: "gram",
style: "unit",
maximumFractionDigits: 1,
}}
/>
<Title level={5}>Article Number</Title>
<TextField value={record?.article_number} />
<Title level={5}>Comment</Title>
<TextField value={record?.comment} />
</Show>
);
};

View File

@@ -0,0 +1,100 @@
import React from "react";
import { IResourceComponentsProps } from "@refinedev/core";
import { Create, useForm } from "@refinedev/antd";
import { Form, Input, DatePicker, Select, InputNumber } from "antd";
import dayjs from "dayjs";
import TextArea from "antd/es/input/TextArea";
export const SpoolCreate: React.FC<IResourceComponentsProps> = () => {
const { formProps, saveButtonProps, queryResult } = useForm();
return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item
label="First Used"
name={["first_used"]}
rules={[
{
required: false,
},
]}
getValueProps={(value) => ({
value: value ? dayjs(value) : undefined,
})}
>
<DatePicker format="lll" />
</Form.Item>
<Form.Item
label="Last Used"
name={["last_used"]}
rules={[
{
required: false,
},
]}
getValueProps={(value) => ({
value: value ? dayjs(value) : undefined,
})}
>
<DatePicker format="lll" />
</Form.Item>
<Form.Item
label="Filament"
name={["filament", "name"]}
rules={[
{
required: true,
},
]}
>
{/* <Select {...filamentSelectProps} /> */}
</Form.Item>
<Form.Item
label="Used Weight"
name={["used_weight"]}
rules={[
{
required: true,
},
]}
>
<InputNumber min={0} addonAfter="g" />
</Form.Item>
<Form.Item
label="Location"
name={["location"]}
rules={[
{
required: false,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label="Lot Nr"
name={["lot_nr"]}
rules={[
{
required: false,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label="Comment"
name={["comment"]}
rules={[
{
required: false,
},
]}
>
<TextArea maxLength={1024} />
</Form.Item>
</Form>
</Create>
);
};

View File

@@ -0,0 +1,127 @@
import React from "react";
import { IResourceComponentsProps } from "@refinedev/core";
import { Edit, useForm } from "@refinedev/antd";
import { Form, Input, DatePicker, Select, InputNumber } from "antd";
import dayjs from "dayjs";
import TextArea from "antd/es/input/TextArea";
export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
const { formProps, saveButtonProps, queryResult } = useForm();
const spoolData = queryResult?.data?.data;
return (
<Edit saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item
label="Id"
name={["id"]}
rules={[
{
required: true,
},
]}
>
<Input readOnly disabled />
</Form.Item>
<Form.Item
label="Registered"
name={["registered"]}
rules={[
{
required: true,
},
]}
getValueProps={(value) => ({
value: value ? dayjs(value) : undefined,
})}
>
<DatePicker disabled format="lll" />
</Form.Item>
<Form.Item
label="First Used"
name={["first_used"]}
rules={[
{
required: true,
},
]}
getValueProps={(value) => ({
value: value ? dayjs(value) : undefined,
})}
>
<DatePicker format="lll" />
</Form.Item>
<Form.Item
label="Last Used"
name={["last_used"]}
rules={[
{
required: true,
},
]}
getValueProps={(value) => ({
value: value ? dayjs(value) : undefined,
})}
>
<DatePicker format="lll" />
</Form.Item>
<Form.Item
label="Filament"
name={["filament", "name"]}
rules={[
{
required: true,
},
]}
>
{/* <Select {...filamentSelectProps} /> */}
</Form.Item>
<Form.Item
label="Used Weight"
name={["used_weight"]}
rules={[
{
required: true,
},
]}
>
<Input min={0} addonAfter="g" />
</Form.Item>
<Form.Item
label="Location"
name={["location"]}
rules={[
{
required: false,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label="Lot Nr"
name={["lot_nr"]}
rules={[
{
required: false,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label="Comment"
name={["comment"]}
rules={[
{
required: false,
},
]}
>
<TextArea maxLength={1024} />
</Form.Item>
</Form>
</Edit>
);
};

View File

@@ -0,0 +1,4 @@
export { SpoolCreate } from "./create";
export { SpoolEdit } from "./edit";
export { SpoolList } from "./list";
export { SpoolShow } from "./show";

View File

@@ -0,0 +1,68 @@
import React from "react";
import { IResourceComponentsProps, BaseRecord } from "@refinedev/core";
import {
useTable,
List,
EditButton,
ShowButton,
DateField,
NumberField,
} from "@refinedev/antd";
import { Table, Space } from "antd";
export const SpoolList: React.FC<IResourceComponentsProps> = () => {
const { tableProps } = useTable({
syncWithLocation: true,
});
return (
<List>
<Table {...tableProps} rowKey="id">
<Table.Column dataIndex="id" title="Id" />
<Table.Column
dataIndex={["filament", "vendor", "name"]}
title="Vendor"
/>
<Table.Column dataIndex={["filament", "name"]} title="Filament Name" />
<Table.Column
dataIndex="remaining_weight"
title="Estimated Remaining Weight"
render={(value: any) => (
<NumberField
value={value}
options={{
unitDisplay: "short",
unit: "gram",
style: "unit",
maximumFractionDigits: 1,
}}
/>
)}
/>
<Table.Column
dataIndex={["first_used"]}
title="First Used"
render={(value: any) => <DateField value={value} format="lll" />}
/>
<Table.Column
dataIndex={["last_used"]}
title="Last Used"
render={(value: any) => <DateField value={value} format="lll" />}
/>
<Table.Column dataIndex="location" title="Location" />
<Table.Column dataIndex="lot_nr" title="Lot Nr" />
<Table.Column dataIndex={["comment"]} title="Comment" />
<Table.Column
title="Actions"
dataIndex="actions"
render={(_, record: BaseRecord) => (
<Space>
<EditButton hideText size="small" recordItemId={record.id} />
<ShowButton hideText size="small" recordItemId={record.id} />
</Space>
)}
/>
</Table>
</List>
);
};

View File

@@ -0,0 +1,64 @@
import React from "react";
import { IResourceComponentsProps, useShow } from "@refinedev/core";
import {
Show,
NumberField,
DateField,
TagField,
TextField,
} from "@refinedev/antd";
import { Typography } from "antd";
const { Title } = Typography;
export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
const { queryResult } = useShow();
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Title level={5}>Id</Title>
<NumberField value={record?.id ?? ""} />
<Title level={5}>Registered</Title>
<DateField value={record?.registered} format="lll" />
<Title level={5}>First Used</Title>
<DateField value={record?.first_used} format="lll" />
<Title level={5}>Last Used</Title>
<DateField value={record?.last_used} format="lll" />
<Title level={5}>Filament</Title>
{/* {filamentIsLoading ? (
<>Loading...</>
) : (
<>{filamentData?.data?.id}</>
)} */}
<Title level={5}>Remaining Weight</Title>
<NumberField
value={record?.remaining_weight ?? ""}
options={{
unitDisplay: "short",
unit: "gram",
style: "unit",
maximumFractionDigits: 1,
}}
/>
<Title level={5}>Used Weight</Title>
<NumberField
value={record?.used_weight ?? ""}
options={{
unitDisplay: "short",
unit: "gram",
style: "unit",
maximumFractionDigits: 1,
}}
/>
<Title level={5}>Location</Title>
<TextField value={record?.location} />
<Title level={5}>Lot Nr</Title>
<TextField value={record?.lot_nr} />
<Title level={5}>Comment</Title>
<TextField value={record?.comment} />
</Show>
);
};

39
client/src/pages/vendors/create.tsx vendored Normal file
View File

@@ -0,0 +1,39 @@
import React from "react";
import { IResourceComponentsProps } from "@refinedev/core";
import { Create, useForm } from "@refinedev/antd";
import { Form, Input, DatePicker } from "antd";
import dayjs from "dayjs";
import TextArea from "antd/es/input/TextArea";
export const VendorCreate: React.FC<IResourceComponentsProps> = () => {
const { formProps, saveButtonProps, queryResult } = useForm();
return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item
label="Name"
name={["name"]}
rules={[
{
required: true,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label="Comment"
name={["comment"]}
rules={[
{
required: false,
},
]}
>
<TextArea maxLength={1024} />
</Form.Item>
</Form>
</Create>
);
};

66
client/src/pages/vendors/edit.tsx vendored Normal file
View File

@@ -0,0 +1,66 @@
import React from "react";
import { IResourceComponentsProps } from "@refinedev/core";
import { Edit, useForm } from "@refinedev/antd";
import { Form, Input, DatePicker } from "antd";
import dayjs from "dayjs";
import TextArea from "antd/es/input/TextArea";
export const VendorEdit: React.FC<IResourceComponentsProps> = () => {
const { formProps, saveButtonProps, queryResult } = useForm();
const vendorData = queryResult?.data?.data;
return (
<Edit saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item
label="Id"
name={["id"]}
rules={[
{
required: true,
},
]}
>
<Input readOnly disabled />
</Form.Item>
<Form.Item
label="Registered"
name={["registered"]}
rules={[
{
required: true,
},
]}
getValueProps={(value) => ({
value: value ? dayjs(value) : undefined,
})}
>
<DatePicker disabled format="lll" />
</Form.Item>
<Form.Item
label="Name"
name={["name"]}
rules={[
{
required: true,
},
]}
>
<Input maxLength={64} />
</Form.Item>
<Form.Item
label="Comment"
name={["comment"]}
rules={[
{
required: false,
},
]}
>
<TextArea maxLength={1024} />
</Form.Item>
</Form>
</Edit>
);
};

4
client/src/pages/vendors/index.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
export { VendorCreate } from "./create";
export { VendorEdit } from "./edit";
export { VendorList } from "./list";
export { VendorShow } from "./show";

41
client/src/pages/vendors/list.tsx vendored Normal file
View File

@@ -0,0 +1,41 @@
import React from "react";
import { IResourceComponentsProps, BaseRecord } from "@refinedev/core";
import {
useTable,
List,
EditButton,
ShowButton,
DateField,
} from "@refinedev/antd";
import { Table, Space } from "antd";
export const VendorList: React.FC<IResourceComponentsProps> = () => {
const { tableProps } = useTable({
syncWithLocation: true,
});
return (
<List>
<Table {...tableProps} rowKey="id">
<Table.Column dataIndex="id" title="Id" />
<Table.Column dataIndex="name" title="Name" />
<Table.Column
dataIndex={["registered"]}
title="Registered"
render={(value: any) => <DateField value={value} format="lll" />}
/>
<Table.Column dataIndex={["comment"]} title="Comment" />
<Table.Column
title="Actions"
dataIndex="actions"
render={(_, record: BaseRecord) => (
<Space>
<EditButton hideText size="small" recordItemId={record.id} />
<ShowButton hideText size="small" recordItemId={record.id} />
</Space>
)}
/>
</Table>
</List>
);
};

32
client/src/pages/vendors/show.tsx vendored Normal file
View File

@@ -0,0 +1,32 @@
import React from "react";
import { IResourceComponentsProps, useShow } from "@refinedev/core";
import {
Show,
NumberField,
DateField,
TagField,
TextField,
} from "@refinedev/antd";
import { Typography } from "antd";
const { Title } = Typography;
export const VendorShow: React.FC<IResourceComponentsProps> = () => {
const { queryResult } = useShow();
const { data, isLoading } = queryResult;
const record = data?.data;
return (
<Show isLoading={isLoading}>
<Title level={5}>Id</Title>
<NumberField value={record?.id ?? ""} />
<Title level={5}>Registered</Title>
<DateField value={record?.registered} format="lll" />
<Title level={5}>Name</Title>
<TextField value={record?.name} />
<Title level={5}>Comment</Title>
<TextField value={record?.comment} />
</Show>
);
};

1
client/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />