proje-react-panel 1.0.9 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +2 -0
- package/.idea/vcs.xml +0 -1
- package/.idea/watcherTasks.xml +4 -0
- package/dist/api/crudApi.d.ts +12 -4
- package/dist/components/ErrorBoundary.d.ts +16 -0
- package/dist/components/ErrorComponent.d.ts +4 -0
- package/dist/components/Form.d.ts +6 -0
- package/dist/components/FormField.d.ts +13 -0
- package/dist/components/Label.d.ts +8 -0
- package/dist/components/Panel.d.ts +3 -1
- package/dist/components/layout/Layout.d.ts +9 -2
- package/dist/components/layout/SideBar.d.ts +13 -2
- package/dist/components/list/List.d.ts +5 -5
- package/dist/components/screens/ControllerCreate.d.ts +5 -0
- package/dist/components/screens/ControllerDetails.d.ts +5 -0
- package/dist/components/screens/ControllerEdit.d.ts +5 -0
- package/dist/components/screens/ControllerList.d.ts +5 -0
- package/dist/components/screens/Login.d.ts +4 -0
- package/dist/decorators/Cell.d.ts +9 -0
- package/dist/decorators/Input.d.ts +13 -0
- package/dist/hooks/useScreens.d.ts +2 -0
- package/dist/index.cjs.js +1 -12
- package/dist/index.d.ts +6 -1
- package/dist/index.esm.js +1 -12
- package/dist/initPanel.d.ts +2 -0
- package/dist/initPanelOptions.d.ts +8 -0
- package/dist/screens/ControllerDetails.d.ts +2 -2
- package/dist/screens/ControllerEdit.d.ts +2 -2
- package/dist/screens/ControllerList.d.ts +2 -2
- package/dist/screens/Form.d.ts +2 -2
- package/dist/src/api/crudApi.d.ts +6 -0
- package/dist/src/components/Panel.d.ts +9 -0
- package/dist/src/components/layout/Layout.d.ts +11 -0
- package/dist/src/components/layout/SideBar.d.ts +10 -0
- package/dist/src/components/list/List.d.ts +10 -0
- package/dist/src/decorators/Crud.d.ts +6 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/screens/ControllerCreate.d.ts +5 -0
- package/dist/src/screens/ControllerDetails.d.ts +5 -0
- package/dist/src/screens/ControllerEdit.d.ts +5 -0
- package/dist/src/screens/ControllerList.d.ts +5 -0
- package/dist/src/screens/Form.d.ts +6 -0
- package/dist/src/store/store.d.ts +19 -0
- package/dist/src/types/Screen.d.ts +4 -0
- package/dist/src/types/ScreenCreatorData.d.ts +8 -0
- package/dist/src/utils/createScreens.d.ts +1 -0
- package/dist/src/utils/getFields.d.ts +2 -0
- package/dist/src/utils/getScreens.d.ts +2 -0
- package/dist/store/store.d.ts +19 -0
- package/dist/types/ScreenCreatorData.d.ts +9 -6
- package/dist/types/initPanelOptions.d.ts +8 -0
- package/dist/utils/crudScreens.d.ts +2 -0
- package/dist/utils/getFields.d.ts +1 -1
- package/dist/utils/getScreens.d.ts +2 -0
- package/package.json +14 -7
- package/src/api/crudApi.ts +30 -28
- package/src/components/ErrorBoundary.tsx +64 -0
- package/src/components/ErrorComponent.tsx +15 -0
- package/src/components/Form.tsx +70 -0
- package/src/components/FormField.tsx +60 -0
- package/src/components/Label.tsx +15 -0
- package/src/components/Panel.tsx +12 -7
- package/src/components/layout/Layout.tsx +16 -12
- package/src/components/layout/SideBar.tsx +38 -12
- package/src/components/list/List.tsx +73 -67
- package/src/components/screens/ControllerCreate.tsx +7 -0
- package/src/components/screens/ControllerDetails.tsx +40 -0
- package/src/components/screens/ControllerEdit.tsx +35 -0
- package/src/components/screens/ControllerList.tsx +44 -0
- package/src/components/screens/Login.tsx +64 -0
- package/src/decorators/Cell.ts +34 -0
- package/src/decorators/Input.ts +50 -0
- package/src/hooks/useScreens.tsx +37 -0
- package/src/index.ts +7 -2
- package/src/initPanel.ts +14 -0
- package/src/store/store.ts +18 -0
- package/src/styles/error-boundary.scss +89 -0
- package/src/styles/form.scss +38 -6
- package/src/styles/index.scss +22 -4
- package/src/styles/layout.scss +49 -0
- package/src/styles/list.scss +8 -6
- package/src/styles/login.scss +90 -0
- package/src/styles/sidebar.scss +17 -22
- package/src/types/ScreenCreatorData.ts +10 -7
- package/src/types/initPanelOptions.ts +8 -0
- package/src/utils/createScreens.ts +2 -2
- package/src/utils/getFields.ts +8 -9
- package/dist/utils/getScreenForRoutes.d.ts +0 -2
- package/dist/utils/storeData.d.ts +0 -4
- package/src/declerations/Cell.ts +0 -37
- package/src/screens/ControllerCreate.tsx +0 -13
- package/src/screens/ControllerDetails.tsx +0 -34
- package/src/screens/ControllerEdit.tsx +0 -31
- package/src/screens/ControllerList.tsx +0 -40
- package/src/screens/Form.tsx +0 -67
- package/src/utils/getScreenForRoutes.tsx +0 -44
- package/src/utils/storeData.ts +0 -7
- /package/dist/{declerations → decorators}/Crud.d.ts +0 -0
- /package/dist/{declerations → src/decorators}/Cell.d.ts +0 -0
- /package/src/{declerations → decorators}/Crud.ts +0 -0
package/package.json
CHANGED
@@ -1,15 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "proje-react-panel",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.10",
|
4
4
|
"description": "",
|
5
5
|
"author": "SEFA DEMİR",
|
6
6
|
"license": "ISC",
|
7
7
|
"main": "dist/index.cjs.js",
|
8
8
|
"module": "dist/index.esm.js",
|
9
|
-
"source":
|
10
|
-
"src/index.ts",
|
11
|
-
"src/dom/index.ts"
|
12
|
-
],
|
9
|
+
"source": "src/index.ts",
|
13
10
|
"types": "dist/index.d.ts",
|
14
11
|
"scripts": {
|
15
12
|
"test": "echo \"Error: no test specified\" && exit 1",
|
@@ -31,12 +28,13 @@
|
|
31
28
|
"@rollup/plugin-commonjs": "^28.0.3",
|
32
29
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
33
30
|
"@types/react": "^19.0.10",
|
34
|
-
"@types/react-dom": "^19.0.4",
|
35
31
|
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
36
32
|
"@typescript-eslint/parser": "^8.26.1",
|
37
33
|
"class-transformer": "^0.5.1",
|
38
34
|
"class-validator": "^0.14.1",
|
39
35
|
"eslint": "^8.57.1",
|
36
|
+
"eslint-plugin-react": "^7.37.4",
|
37
|
+
"prettier": "^3.5.3",
|
40
38
|
"react": "^19.0.0",
|
41
39
|
"react-hook-form": "^7.54.2",
|
42
40
|
"react-router": "^7.3.0",
|
@@ -46,6 +44,15 @@
|
|
46
44
|
"rollup-plugin-serve": "^3.0.0",
|
47
45
|
"rollup-plugin-terser": "^7.0.2",
|
48
46
|
"rollup-plugin-typescript2": "^0.36.0",
|
49
|
-
"typescript": "^5.8.2"
|
47
|
+
"typescript": "^5.8.2",
|
48
|
+
"use-sync-external-store": "^1.4.0",
|
49
|
+
"zustand": "^5.0.3"
|
50
|
+
},
|
51
|
+
"peerDependencies": {
|
52
|
+
"react": ">=19.0.0",
|
53
|
+
"react-hook-form": ">=7.54.2",
|
54
|
+
"react-router": "7.3.0",
|
55
|
+
"use-sync-external-store": ">=1.4.0",
|
56
|
+
"zustand": ">=5.0.3"
|
50
57
|
}
|
51
58
|
}
|
package/src/api/crudApi.ts
CHANGED
@@ -1,30 +1,32 @@
|
|
1
|
-
|
2
1
|
export const CrudApi = {
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
2
|
+
getList: (fetchSettings: { baseUrl: string }, api: string) => {
|
3
|
+
return fetch(`${fetchSettings.baseUrl}/${api}`, {
|
4
|
+
method: "GET",
|
5
|
+
headers: { "Content-Type": "application/json" },
|
6
|
+
}).then((res) => {
|
7
|
+
return res.json();
|
8
|
+
});
|
9
|
+
},
|
10
|
+
create: (fetchSettings: { baseUrl: string }, api: string, data: any) => {
|
11
|
+
return fetch(`${fetchSettings?.baseUrl ?? ""}/${api}`, {
|
12
|
+
method: "POST",
|
13
|
+
headers: { "Content-Type": "application/json" },
|
14
|
+
body: JSON.stringify(data),
|
15
|
+
}).then((res) => res.json());
|
16
|
+
},
|
17
|
+
details: (fetchSettings: { baseUrl: string }, api: string, id: any) => {
|
18
|
+
return fetch(`${fetchSettings?.baseUrl ?? ""}/${api}/${id}`, {
|
19
|
+
method: "GET",
|
20
|
+
headers: { "Content-Type": "application/json" },
|
21
|
+
}).then((res) => {
|
22
|
+
return res.json();
|
23
|
+
});
|
24
|
+
},
|
25
|
+
edit: (fetchSettings: { baseUrl: string }, api: string, data: any) => {
|
26
|
+
return fetch(`${fetchSettings?.baseUrl ?? ""}/${api}/${data.id}`, {
|
27
|
+
method: "PUT",
|
28
|
+
headers: { "Content-Type": "application/json" },
|
29
|
+
body: JSON.stringify(data),
|
30
|
+
}).then((res) => res.json());
|
31
|
+
},
|
30
32
|
};
|
@@ -0,0 +1,64 @@
|
|
1
|
+
import React, { Component, ErrorInfo, ReactNode } from "react";
|
2
|
+
|
3
|
+
interface Props {
|
4
|
+
children: ReactNode;
|
5
|
+
}
|
6
|
+
|
7
|
+
interface State {
|
8
|
+
hasError: boolean;
|
9
|
+
error: Error | null;
|
10
|
+
errorInfo: ErrorInfo | null;
|
11
|
+
}
|
12
|
+
|
13
|
+
export class ErrorBoundary extends Component<Props, State> {
|
14
|
+
public state: State = {
|
15
|
+
hasError: false,
|
16
|
+
error: null,
|
17
|
+
errorInfo: null,
|
18
|
+
};
|
19
|
+
|
20
|
+
public static getDerivedStateFromError(error: Error): State {
|
21
|
+
return { hasError: true, error, errorInfo: null };
|
22
|
+
}
|
23
|
+
|
24
|
+
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
25
|
+
this.setState({
|
26
|
+
error: error,
|
27
|
+
errorInfo: errorInfo,
|
28
|
+
});
|
29
|
+
}
|
30
|
+
|
31
|
+
public render() {
|
32
|
+
if (this.state.hasError) {
|
33
|
+
return (
|
34
|
+
<div className="error-boundary">
|
35
|
+
<div className="error-boundary__content">
|
36
|
+
<div className="error-boundary__icon">
|
37
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
38
|
+
<circle cx="12" cy="12" r="10" />
|
39
|
+
<line x1="12" y1="8" x2="12" y2="12" />
|
40
|
+
<line x1="12" y1="16" x2="12" y2="16" />
|
41
|
+
</svg>
|
42
|
+
</div>
|
43
|
+
<h1>Oops! Something went wrong</h1>
|
44
|
+
<p className="error-boundary__message">
|
45
|
+
{this.state.error?.message || "An unexpected error occurred"}
|
46
|
+
</p>
|
47
|
+
<button className="error-boundary__button" onClick={() => window.location.reload()}>
|
48
|
+
Refresh Page
|
49
|
+
</button>
|
50
|
+
{process.env.NODE_ENV === "development" && (
|
51
|
+
<details className="error-boundary__details">
|
52
|
+
<summary>Error Details</summary>
|
53
|
+
<pre>{this.state.error?.toString()}</pre>
|
54
|
+
<pre>{this.state.errorInfo?.componentStack}</pre>
|
55
|
+
</details>
|
56
|
+
)}
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
);
|
60
|
+
}
|
61
|
+
|
62
|
+
return this.props.children;
|
63
|
+
}
|
64
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
export function ErrorComponent({ error }: { error: unknown }) {
|
4
|
+
return (
|
5
|
+
<div className="error-container">
|
6
|
+
<div className="error-icon">
|
7
|
+
<i className="fa fa-exclamation-circle" />
|
8
|
+
</div>
|
9
|
+
<div className="error-content">
|
10
|
+
<h3>Error Occurred</h3>
|
11
|
+
<p>{(error as { message?: string })?.message || "Something went wrong. Please try again later."}</p>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
);
|
15
|
+
}
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import React, { useEffect } from "react";
|
2
|
+
import { Screen } from "../types/Screen";
|
3
|
+
import { useForm } from "react-hook-form";
|
4
|
+
import { useNavigate } from "react-router";
|
5
|
+
import { CrudApi } from "../api/crudApi";
|
6
|
+
import { useAppStore } from "../store/store";
|
7
|
+
import { InputOptions } from "../decorators/Input";
|
8
|
+
import { FormField } from "./FormField";
|
9
|
+
|
10
|
+
export function Form({ data, screen }: { data?: any; screen: Screen }) {
|
11
|
+
const { screens, fetchSettings } = useAppStore((s) => ({
|
12
|
+
screens: s.screens ?? {},
|
13
|
+
fetchSettings: s.fetchSettings,
|
14
|
+
}));
|
15
|
+
const isEditForm = !!data;
|
16
|
+
const {
|
17
|
+
register,
|
18
|
+
handleSubmit,
|
19
|
+
reset,
|
20
|
+
formState: { errors },
|
21
|
+
} = useForm<any>({
|
22
|
+
resolver: screens[screen.controller].resolver,
|
23
|
+
//TODO: remove __formEdit from api
|
24
|
+
defaultValues: { ...data, __formEdit: isEditForm },
|
25
|
+
});
|
26
|
+
const navigate = useNavigate();
|
27
|
+
//const fields = screens[screen.controller].fields;
|
28
|
+
const inputs = screens[screen.controller].inputs;
|
29
|
+
useEffect(() => {
|
30
|
+
reset({ ...data, __formEdit: isEditForm });
|
31
|
+
}, [isEditForm, data, reset]);
|
32
|
+
|
33
|
+
return (
|
34
|
+
<div className="form-wrapper">
|
35
|
+
<form
|
36
|
+
onSubmit={handleSubmit((dataForm) => {
|
37
|
+
if (!fetchSettings) {
|
38
|
+
return;
|
39
|
+
}
|
40
|
+
delete dataForm.__formEdit;
|
41
|
+
if (isEditForm) {
|
42
|
+
CrudApi.edit(fetchSettings, screen.controller, dataForm).then(() => {
|
43
|
+
navigate("/" + screen.controller, {
|
44
|
+
replace: true,
|
45
|
+
});
|
46
|
+
});
|
47
|
+
} else {
|
48
|
+
CrudApi.create(fetchSettings, screen.controller, dataForm).then(() => {
|
49
|
+
navigate("/" + screen.controller, {
|
50
|
+
replace: true,
|
51
|
+
});
|
52
|
+
});
|
53
|
+
}
|
54
|
+
})}>
|
55
|
+
{inputs.map((input: InputOptions) => (
|
56
|
+
<FormField
|
57
|
+
key={input.name || ""}
|
58
|
+
input={input}
|
59
|
+
register={register}
|
60
|
+
isEditForm={isEditForm}
|
61
|
+
error={errors[input.name || ""]}
|
62
|
+
/>
|
63
|
+
))}
|
64
|
+
<button type="submit" className="submit-button">
|
65
|
+
Submit
|
66
|
+
</button>
|
67
|
+
</form>
|
68
|
+
</div>
|
69
|
+
);
|
70
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import React from "react";
|
2
|
+
import { InputOptions } from "../decorators/Input";
|
3
|
+
import { Label } from "./Label";
|
4
|
+
import { UseFormRegister } from "react-hook-form";
|
5
|
+
|
6
|
+
interface FormFieldProps {
|
7
|
+
input: InputOptions;
|
8
|
+
register: UseFormRegister<any>;
|
9
|
+
isEditForm: boolean;
|
10
|
+
error?: { message?: string };
|
11
|
+
}
|
12
|
+
|
13
|
+
export function FormField({ input, register, isEditForm, error }: FormFieldProps) {
|
14
|
+
const fieldName = input.name || "";
|
15
|
+
|
16
|
+
const renderField = () => {
|
17
|
+
switch (input.type) {
|
18
|
+
case "textarea":
|
19
|
+
return (
|
20
|
+
<textarea
|
21
|
+
{...register(fieldName)}
|
22
|
+
placeholder={input.placeholder}
|
23
|
+
id={fieldName}
|
24
|
+
disabled={isEditForm && input.editable === false}
|
25
|
+
/>
|
26
|
+
);
|
27
|
+
case "select":
|
28
|
+
return (
|
29
|
+
<select {...register(fieldName)} id={fieldName} disabled={isEditForm && input.editable === false}>
|
30
|
+
<option value="">Select {fieldName}</option>
|
31
|
+
{input.selectOptions?.map((option) => (
|
32
|
+
<option key={option} value={option}>
|
33
|
+
{option}
|
34
|
+
</option>
|
35
|
+
))}
|
36
|
+
</select>
|
37
|
+
);
|
38
|
+
case "input":
|
39
|
+
default: {
|
40
|
+
return (
|
41
|
+
<input
|
42
|
+
type={input.inputType}
|
43
|
+
{...register(fieldName)}
|
44
|
+
placeholder={input.placeholder}
|
45
|
+
id={fieldName}
|
46
|
+
disabled={isEditForm && input.editable === false}
|
47
|
+
/>
|
48
|
+
);
|
49
|
+
}
|
50
|
+
}
|
51
|
+
};
|
52
|
+
|
53
|
+
return (
|
54
|
+
<div className="form-field">
|
55
|
+
<Label htmlFor={fieldName} label={input.label} fieldName={fieldName} />
|
56
|
+
{renderField()}
|
57
|
+
{error && <span className="error-message">{error.message}</span>}
|
58
|
+
</div>
|
59
|
+
);
|
60
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
|
3
|
+
interface LabelProps {
|
4
|
+
htmlFor: string;
|
5
|
+
label?: string;
|
6
|
+
fieldName: string;
|
7
|
+
}
|
8
|
+
|
9
|
+
export function Label({ htmlFor, label, fieldName }: LabelProps) {
|
10
|
+
return (
|
11
|
+
<label htmlFor={htmlFor}>
|
12
|
+
{label ?? fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}
|
13
|
+
</label>
|
14
|
+
);
|
15
|
+
}
|
package/src/components/Panel.tsx
CHANGED
@@ -1,13 +1,18 @@
|
|
1
|
-
import React from "react";
|
1
|
+
import React, { useEffect } from "react";
|
2
|
+
import { ErrorBoundary } from "./ErrorBoundary";
|
3
|
+
import { initPanel } from "../initPanel";
|
4
|
+
import { InitPanelOptions } from "../types/initPanelOptions";
|
2
5
|
|
3
6
|
type AppProps = {
|
4
7
|
children: React.ReactNode;
|
8
|
+
init: () => InitPanelOptions;
|
5
9
|
};
|
6
10
|
|
7
|
-
export function Panel({ children }: AppProps) {
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
export function Panel({ children, init }: AppProps) {
|
12
|
+
useEffect(() => {
|
13
|
+
const options = init();
|
14
|
+
initPanel(options);
|
15
|
+
}, [init]);
|
16
|
+
|
17
|
+
return <ErrorBoundary>{children}</ErrorBoundary>;
|
13
18
|
}
|
@@ -1,16 +1,20 @@
|
|
1
|
-
import React from
|
2
|
-
import {
|
3
|
-
import {
|
1
|
+
import React from "react";
|
2
|
+
import { SideBar } from "./SideBar";
|
3
|
+
import { ScreenCreatorData } from "../../types/ScreenCreatorData";
|
4
4
|
|
5
|
-
export function Layout({
|
6
|
-
|
5
|
+
export function Layout<IconType>({
|
6
|
+
children,
|
7
|
+
menu,
|
8
|
+
getIcons,
|
7
9
|
}: {
|
8
|
-
|
10
|
+
children?: React.ReactNode;
|
11
|
+
menu?: (screens: Record<string, ScreenCreatorData>) => { name: string; path: string; iconType: IconType }[];
|
12
|
+
getIcons?: (iconType: IconType) => React.ReactNode;
|
9
13
|
}) {
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
return (
|
15
|
+
<div className="layout">
|
16
|
+
<SideBar menu={menu} getIcons={getIcons} />
|
17
|
+
<main className="content">{children}</main>
|
18
|
+
</div>
|
19
|
+
);
|
16
20
|
}
|
@@ -1,16 +1,42 @@
|
|
1
|
-
import React, { useState } from
|
1
|
+
import React, { useState } from "react";
|
2
|
+
import { Link } from "react-router";
|
3
|
+
import { ScreenCreatorData } from "../../types/ScreenCreatorData";
|
4
|
+
import { useAppStore } from "../../store/store";
|
2
5
|
|
6
|
+
type GetMenuFunction<IconType> = (
|
7
|
+
screens: Record<string, ScreenCreatorData>
|
8
|
+
) => { name: string; path: string; iconType: IconType }[];
|
3
9
|
|
4
|
-
|
5
|
-
const [isOpen, setIsOpen] = useState(true);
|
10
|
+
type GetIconsFunction<IconType> = (iconType: IconType) => React.ReactNode;
|
6
11
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
export function SideBar<IconType>({
|
13
|
+
menu,
|
14
|
+
getIcons,
|
15
|
+
}: {
|
16
|
+
menu?: GetMenuFunction<IconType>;
|
17
|
+
getIcons?: GetIconsFunction<IconType>;
|
18
|
+
}) {
|
19
|
+
const screens = useAppStore((s) => s.screens ?? {});
|
20
|
+
const [isOpen, setIsOpen] = useState(true);
|
21
|
+
|
22
|
+
return (
|
23
|
+
<div className={`sidebar ${isOpen ? "open" : "closed"}`}>
|
24
|
+
<button className="toggle-button" onClick={() => setIsOpen(!isOpen)}>
|
25
|
+
{isOpen ? "<" : ">"}
|
26
|
+
</button>
|
27
|
+
<nav className="nav-links">
|
28
|
+
{menu?.(screens).map((item, index) => (
|
29
|
+
<Link key={index} to={item.path} className="nav-link">
|
30
|
+
<span className={"nav-links-icon"}>{getIcons?.(item.iconType)}</span>
|
31
|
+
{isOpen ? <span>{item.name}</span> : null}
|
32
|
+
</Link>
|
33
|
+
))}
|
34
|
+
{/*{screens.map(([key, screen], index) => (
|
35
|
+
<Link key={`screen-${index}`} to={`/${key}`} className="nav-link">
|
36
|
+
{isOpen ? <span>{key}</span> : null}
|
37
|
+
</Link>
|
38
|
+
))}*/}
|
39
|
+
</nav>
|
40
|
+
</div>
|
41
|
+
);
|
16
42
|
}
|
@@ -1,75 +1,81 @@
|
|
1
|
-
import React from
|
2
|
-
import { CellOptions } from
|
3
|
-
import { Link } from
|
4
|
-
import { Screen } from
|
1
|
+
import React from "react";
|
2
|
+
import { CellOptions } from "../../decorators/Cell";
|
3
|
+
import { Link } from "react-router";
|
4
|
+
import { Screen } from "../../types/Screen";
|
5
5
|
|
6
6
|
interface ListProps<T> {
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
data: T[];
|
8
|
+
cells: CellOptions[];
|
9
|
+
screen: Screen;
|
10
10
|
}
|
11
11
|
|
12
|
-
export function List<T>({ data, cells
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
export function List<T>({ data, cells }: ListProps<T>) {
|
13
|
+
if (!data || data.length === 0) {
|
14
|
+
return <div>No items available</div>;
|
15
|
+
}
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
17
|
+
return (
|
18
|
+
<div className="list-wrapper">
|
19
|
+
<div className="header">List</div>
|
20
|
+
<table className="list-table">
|
21
|
+
<thead>
|
22
|
+
<tr>
|
23
|
+
{cells.map((cellOptions) => (
|
24
|
+
<th key={cellOptions.name}>{cellOptions.title ?? cellOptions.name}</th>
|
25
|
+
))}
|
26
|
+
<th />
|
27
|
+
</tr>
|
28
|
+
</thead>
|
29
|
+
<tbody>
|
30
|
+
{data.map((item, index) => (
|
31
|
+
<tr key={index}>
|
32
|
+
{cells.map((cellOptions) => {
|
33
|
+
// @ts-ignore
|
34
|
+
const value = item[cellOptions.name];
|
35
|
+
let formattedValue = value ?? "-"; // Default value if the field is undefined or null
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
37
|
+
switch (cellOptions.type) {
|
38
|
+
case "date":
|
39
|
+
if (value) {
|
40
|
+
const date = new Date(value);
|
41
|
+
formattedValue = `${date.getDate().toString().padStart(2, "0")}/${(
|
42
|
+
date.getMonth() + 1
|
43
|
+
)
|
44
|
+
.toString()
|
45
|
+
.padStart(
|
46
|
+
2,
|
47
|
+
"0"
|
48
|
+
)}/${date.getFullYear()} ${date.getHours().toString().padStart(2, "0")}:${date
|
49
|
+
.getMinutes()
|
50
|
+
.toString()
|
51
|
+
.padStart(2, "0")}`;
|
52
|
+
}
|
53
|
+
break;
|
49
54
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
55
|
+
case "number":
|
56
|
+
case "string":
|
57
|
+
default:
|
58
|
+
formattedValue = value ? value.toString() : (cellOptions?.placeHolder ?? "-"); // Handles string type or default fallback
|
59
|
+
break;
|
60
|
+
}
|
61
|
+
let render = formattedValue;
|
62
|
+
/*
|
63
|
+
if (cellOptions.linkTo) {
|
64
|
+
render = <Link to={cellOptions.linkTo(item)}>{formattedValue}</Link>;
|
65
|
+
}
|
66
|
+
*/
|
67
|
+
return <td key={cellOptions.name}>{render}</td>;
|
68
|
+
})}
|
69
|
+
<td>
|
70
|
+
{/*@ts-ignore*/}
|
71
|
+
<Link to={"edit/" + (item?.id ?? "-")}>Edit</Link>
|
72
|
+
{/*@ts-ignore*/}
|
73
|
+
<Link to={"details/" + (item?.id ?? "-")}>Details</Link>
|
74
|
+
</td>
|
75
|
+
</tr>
|
76
|
+
))}
|
77
|
+
</tbody>
|
78
|
+
</table>
|
79
|
+
</div>
|
80
|
+
);
|
75
81
|
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { useParams } from "react-router";
|
2
|
+
import React, { useEffect, useState } from "react";
|
3
|
+
import { CrudApi } from "../../api/crudApi";
|
4
|
+
import { Screen } from "../../types/Screen";
|
5
|
+
import { useAppStore } from "../../store/store";
|
6
|
+
import { ErrorComponent } from "../ErrorComponent";
|
7
|
+
|
8
|
+
export function ControllerDetails({ screen }: { screen: Screen }) {
|
9
|
+
const { fetchSettings } = useAppStore((s) => ({
|
10
|
+
fetchSettings: s.fetchSettings,
|
11
|
+
}));
|
12
|
+
const { id } = useParams();
|
13
|
+
const [data, setData] = useState<any>(null);
|
14
|
+
const [error, setError] = useState(null);
|
15
|
+
|
16
|
+
useEffect(() => {
|
17
|
+
if (fetchSettings && screen.controller && id) {
|
18
|
+
CrudApi.details(fetchSettings, screen.controller, id)
|
19
|
+
.then((res) => {
|
20
|
+
setData(res);
|
21
|
+
})
|
22
|
+
.catch((e: any) => {
|
23
|
+
setError(e);
|
24
|
+
console.error(e);
|
25
|
+
});
|
26
|
+
}
|
27
|
+
}, [fetchSettings, id, screen]);
|
28
|
+
|
29
|
+
if (error) {
|
30
|
+
return <ErrorComponent error={error} />;
|
31
|
+
}
|
32
|
+
|
33
|
+
return (
|
34
|
+
<p
|
35
|
+
dangerouslySetInnerHTML={{
|
36
|
+
__html: JSON.stringify(data, null, " " + "<br/>"),
|
37
|
+
}}
|
38
|
+
/>
|
39
|
+
);
|
40
|
+
}
|