Added a basic web UI
This commit is contained in:
@@ -1,2 +1,10 @@
|
|||||||
.git
|
.git
|
||||||
Dockerfile
|
.github
|
||||||
|
.venv
|
||||||
|
.vscode
|
||||||
|
build
|
||||||
|
Dockerfile
|
||||||
|
tests
|
||||||
|
|
||||||
|
client
|
||||||
|
!client/dist
|
||||||
|
|||||||
16
client/.eslintrc.cjs
Normal file
16
client/.eslintrc.cjs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:react-hooks/recommended",
|
||||||
|
],
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
|
||||||
|
plugins: ["react-refresh"],
|
||||||
|
rules: {
|
||||||
|
"react-refresh/only-export-components": "warn",
|
||||||
|
},
|
||||||
|
};
|
||||||
23
client/.gitignore
vendored
Normal file
23
client/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
2
client/.npmrc
Normal file
2
client/.npmrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
legacy-peer-deps=true
|
||||||
|
strict-peer-dependencies=false
|
||||||
3
client/README.MD
Normal file
3
client/README.MD
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Spoolman UI
|
||||||
|
|
||||||
|
A simple [refine](https://github.com/refinedev/refine) based UI for manipulating the data in the Spoolman database.
|
||||||
22
client/index.html
Normal file
22
client/index.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta name="description" content="Spoolman UI" />
|
||||||
|
<meta name="robots" content="noindex,nofollow" />
|
||||||
|
<title>
|
||||||
|
Spoolman
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
9244
client/package-lock.json
generated
Normal file
9244
client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
55
client/package.json
Normal file
55
client/package.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "spoolman-ui",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@refinedev/cli": "^2.5.1",
|
||||||
|
"@refinedev/core": "^4.5.8",
|
||||||
|
"@refinedev/inferencer": "^3.1.4",
|
||||||
|
"@refinedev/kbar": "^1.1.0",
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0",
|
||||||
|
"react-router-dom": "^6.8.1",
|
||||||
|
"@refinedev/simple-rest": "^4.0.0",
|
||||||
|
"@refinedev/antd": "^5.7.0",
|
||||||
|
"antd": "^5.0.5",
|
||||||
|
"@ant-design/icons": "^5.0.1",
|
||||||
|
"i18next": "^20.1.0",
|
||||||
|
"react-i18next": "^11.8.11",
|
||||||
|
"i18next-browser-languagedetector": "^6.1.1",
|
||||||
|
"i18next-xhr-backend": "^3.2.2",
|
||||||
|
"@refinedev/react-router-v6": "^4.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^18.16.2",
|
||||||
|
"@types/react": "^18.0.0",
|
||||||
|
"@types/react-dom": "^18.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||||
|
"@typescript-eslint/parser": "^5.57.1",
|
||||||
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
|
"eslint": "^8.38.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.3.4",
|
||||||
|
"typescript": "^4.7.4",
|
||||||
|
"vite": "^4.3.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "refine dev",
|
||||||
|
"build": "tsc && refine build",
|
||||||
|
"preview": "refine start",
|
||||||
|
"refine": "refine"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
client/public/favicon.ico
Normal file
BIN
client/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
1
client/public/images/flags/en.svg
Normal file
1
client/public/images/flags/en.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480" viewBox="0 0 640 480"><defs><clipPath id="a"><path fill-opacity=".67" d="M-85.333 0h682.67v512h-682.67z"/></clipPath></defs><g clip-path="url(#a)" transform="translate(80) scale(.94)"><g stroke-width="1pt"><path fill="#006" d="M-256 0H768.02v512.01H-256z"/><path fill="#fff" d="M-256 0v57.244l909.535 454.768H768.02V454.77L-141.515 0H-256zM768.02 0v57.243L-141.515 512.01H-256v-57.243L653.535 0H768.02z"/><path fill="#fff" d="M170.675 0v512.01h170.67V0h-170.67zM-256 170.67v170.67H768.02V170.67H-256z"/><path fill="#c00" d="M-256 204.804v102.402H768.02V204.804H-256zM204.81 0v512.01h102.4V0h-102.4zM-256 512.01L85.34 341.34h76.324l-341.34 170.67H-256zM-256 0L85.34 170.67H9.016L-256 38.164V0zm606.356 170.67L691.696 0h76.324L426.68 170.67h-76.324zM768.02 512.01L426.68 341.34h76.324L768.02 473.848v38.162z"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 887 B |
145
client/public/locales/en/common.json
Normal file
145
client/public/locales/en/common.json
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
{
|
||||||
|
"pages": {
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in to your account",
|
||||||
|
"signin": "Sign in",
|
||||||
|
"signup": "Sign up",
|
||||||
|
"divider": "or",
|
||||||
|
"fields": {
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"validEmail": "Invalid email address"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Login",
|
||||||
|
"forgotPassword": "Forgot password?",
|
||||||
|
"noAccount": "Don’t have an account?",
|
||||||
|
"rememberMe": "Remember me"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"forgotPassword": {
|
||||||
|
"title": "Forgot your password?",
|
||||||
|
"fields": {
|
||||||
|
"email": "Email"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"validEmail": "Invalid email address"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Send reset instructions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Sign up for your account",
|
||||||
|
"fields": {
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"validEmail": "Invalid email address"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Register",
|
||||||
|
"haveAccount": "Have an account?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updatePassword": {
|
||||||
|
"title": "Update password",
|
||||||
|
"fields": {
|
||||||
|
"password": "New Password",
|
||||||
|
"confirmPassword": "Confirm new password"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"confirmPasswordNotMatch": "Passwords do not match"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"submit": "Update"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"info": "You may have forgotten to add the {{action}} component to {{resource}} resource.",
|
||||||
|
"404": "Sorry, the page you visited does not exist.",
|
||||||
|
"resource404": "Are you sure you have created the {{resource}} resource.",
|
||||||
|
"backHome": "Back Home"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"list": "List",
|
||||||
|
"create": "Create",
|
||||||
|
"edit": "Edit",
|
||||||
|
"show": "Show"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"create": "Create",
|
||||||
|
"save": "Save",
|
||||||
|
"logout": "Logout",
|
||||||
|
"delete": "Delete",
|
||||||
|
"edit": "Edit",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Are you sure?",
|
||||||
|
"filter": "Filter",
|
||||||
|
"clear": "Clear",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"show": "Show",
|
||||||
|
"undo": "Undo",
|
||||||
|
"import": "Import",
|
||||||
|
"clone": "Clone",
|
||||||
|
"notAccessTitle": "You don't have permission to access"
|
||||||
|
},
|
||||||
|
"warnWhenUnsavedChanges": "Are you sure you want to leave? You have unsaved changes.",
|
||||||
|
"notifications": {
|
||||||
|
"success": "Successful",
|
||||||
|
"error": "Error (status code: {{statusCode}})",
|
||||||
|
"undoable": "You have {{seconds}} seconds to undo",
|
||||||
|
"createSuccess": "Successfully created {{resource}}",
|
||||||
|
"createError": "There was an error creating {{resource}} (status code: {{statusCode}})",
|
||||||
|
"deleteSuccess": "Successfully deleted {{resource}}",
|
||||||
|
"deleteError": "Error when deleting {{resource}} (status code: {{statusCode}})",
|
||||||
|
"editSuccess": "Successfully edited {{resource}}",
|
||||||
|
"editError": "Error when editing {{resource}} (status code: {{statusCode}})",
|
||||||
|
"importProgress": "Importing: {{processed}}/{{total}}"
|
||||||
|
},
|
||||||
|
"loading": "Loading",
|
||||||
|
"tags": {
|
||||||
|
"clone": "Clone"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Dashboard"
|
||||||
|
},
|
||||||
|
"blog_posts": {
|
||||||
|
"blog_posts": "Blog Posts",
|
||||||
|
"fields": {
|
||||||
|
"id": "Id",
|
||||||
|
"title": "Title",
|
||||||
|
"content": "Content",
|
||||||
|
"status": "Status",
|
||||||
|
"category": "Category",
|
||||||
|
"createdAt": "Created At"
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"create": "Create Blog Post",
|
||||||
|
"edit": "Edit Blog Post",
|
||||||
|
"list": "Blog Posts",
|
||||||
|
"show": "Show Blog Post"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"categories": "Categories",
|
||||||
|
"fields": {
|
||||||
|
"id": "Id",
|
||||||
|
"title": "Title",
|
||||||
|
"createdAt": "Created At"
|
||||||
|
},
|
||||||
|
"titles": {
|
||||||
|
"create": "Create Category",
|
||||||
|
"edit": "Edit Category",
|
||||||
|
"list": "Categories",
|
||||||
|
"show": "Show Category"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"actions": "Actions"
|
||||||
|
}
|
||||||
|
}
|
||||||
145
client/src/App.tsx
Normal file
145
client/src/App.tsx
Normal 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;
|
||||||
19
client/src/components/app-icon/index.tsx
Normal file
19
client/src/components/app-icon/index.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
98
client/src/components/header/index.tsx
Normal file
98
client/src/components/header/index.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
1
client/src/components/index.ts
Normal file
1
client/src/components/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Header } from "./header";
|
||||||
59
client/src/contexts/color-mode/index.tsx
Normal file
59
client/src/contexts/color-mode/index.tsx
Normal 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
19
client/src/i18n.ts
Normal 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
16
client/src/index.tsx
Normal 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>
|
||||||
|
);
|
||||||
133
client/src/pages/filaments/create.tsx
Normal file
133
client/src/pages/filaments/create.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
160
client/src/pages/filaments/edit.tsx
Normal file
160
client/src/pages/filaments/edit.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
4
client/src/pages/filaments/index.ts
Normal file
4
client/src/pages/filaments/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export { FilamentCreate } from "./create";
|
||||||
|
export { FilamentEdit } from "./edit";
|
||||||
|
export { FilamentList } from "./list";
|
||||||
|
export { FilamentShow } from "./show";
|
||||||
106
client/src/pages/filaments/list.tsx
Normal file
106
client/src/pages/filaments/list.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
80
client/src/pages/filaments/show.tsx
Normal file
80
client/src/pages/filaments/show.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
100
client/src/pages/spools/create.tsx
Normal file
100
client/src/pages/spools/create.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
127
client/src/pages/spools/edit.tsx
Normal file
127
client/src/pages/spools/edit.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
4
client/src/pages/spools/index.ts
Normal file
4
client/src/pages/spools/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export { SpoolCreate } from "./create";
|
||||||
|
export { SpoolEdit } from "./edit";
|
||||||
|
export { SpoolList } from "./list";
|
||||||
|
export { SpoolShow } from "./show";
|
||||||
68
client/src/pages/spools/list.tsx
Normal file
68
client/src/pages/spools/list.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
64
client/src/pages/spools/show.tsx
Normal file
64
client/src/pages/spools/show.tsx
Normal 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
39
client/src/pages/vendors/create.tsx
vendored
Normal 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
66
client/src/pages/vendors/edit.tsx
vendored
Normal 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
4
client/src/pages/vendors/index.ts
vendored
Normal 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
41
client/src/pages/vendors/list.tsx
vendored
Normal 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
32
client/src/pages/vendors/show.tsx
vendored
Normal 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
1
client/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
25
client/tsconfig.json
Normal file
25
client/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": ["src", "vite.config.ts"],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
8
client/tsconfig.node.json
Normal file
8
client/tsconfig.node.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
6
client/vite.config.ts
Normal file
6
client/vite.config.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
});
|
||||||
@@ -10,6 +10,9 @@ dynamic = ["dependencies"]
|
|||||||
[tool.setuptools.dynamic]
|
[tool.setuptools.dynamic]
|
||||||
dependencies = {file = ["requirements.txt"]}
|
dependencies = {file = ["requirements.txt"]}
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["spoolman"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
select = ["ALL"]
|
select = ["ALL"]
|
||||||
ignore = ["A003", "D101", "D104", "D406", "D407", "S104", "TRY201", "TRY003", "EM101", "EM102", "DTZ003"]
|
ignore = ["A003", "D101", "D104", "D406", "D407", "S104", "TRY201", "TRY003", "EM101", "EM102", "DTZ003"]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
from platformdirs import user_data_dir
|
from platformdirs import user_data_dir
|
||||||
from sqlalchemy import URL
|
from sqlalchemy import URL
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ logger = logging.getLogger(__name__)
|
|||||||
# Setup FastAPI
|
# Setup FastAPI
|
||||||
app = FastAPI(debug=True)
|
app = FastAPI(debug=True)
|
||||||
app.mount("/api/v1", v1_app)
|
app.mount("/api/v1", v1_app)
|
||||||
|
app.mount("/", StaticFiles(directory="client/dist", html=True), name="client")
|
||||||
|
|
||||||
|
|
||||||
def get_connection_url() -> URL:
|
def get_connection_url() -> URL:
|
||||||
|
|||||||
Reference in New Issue
Block a user