proje-react-panel 1.0.17 → 1.1.1

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.
Files changed (119) hide show
  1. package/dist/components/Counter.d.ts +9 -0
  2. package/dist/components/DetailsPage.d.ts +7 -0
  3. package/dist/components/ErrorBoundary.d.ts +16 -0
  4. package/dist/components/ErrorComponent.d.ts +4 -0
  5. package/dist/components/LoadingScreen.d.ts +2 -0
  6. package/dist/components/Login.d.ts +13 -0
  7. package/dist/components/Panel.d.ts +1 -3
  8. package/dist/components/components/Checkbox.d.ts +3 -2
  9. package/dist/components/components/FormField.d.ts +5 -1
  10. package/dist/components/components/InnerForm.d.ts +8 -3
  11. package/dist/components/components/Label.d.ts +3 -2
  12. package/dist/components/components/Uploader.d.ts +8 -0
  13. package/dist/components/components/index.d.ts +1 -1
  14. package/dist/components/components/list/ListPage.d.ts +1 -1
  15. package/dist/components/form/Checkbox.d.ts +7 -0
  16. package/dist/components/form/FormField.d.ts +17 -0
  17. package/dist/components/form/FormPage.d.ts +6 -0
  18. package/dist/components/form/InnerForm.d.ts +10 -0
  19. package/dist/components/form/Label.d.ts +9 -0
  20. package/dist/components/form/Select.d.ts +8 -0
  21. package/dist/components/form/SelectStyles.d.ts +7 -0
  22. package/dist/components/form/Uploader.d.ts +8 -0
  23. package/dist/components/layout/Layout.d.ts +3 -4
  24. package/dist/components/layout/SideBar.d.ts +2 -3
  25. package/dist/components/list/CellField.d.ts +9 -0
  26. package/dist/components/list/Datagrid.d.ts +6 -8
  27. package/dist/components/list/FilterPopup.d.ts +7 -5
  28. package/dist/components/list/ListHeader.d.ts +11 -0
  29. package/dist/components/list/ListPage.d.ts +6 -0
  30. package/dist/components/pages/FormPage.d.ts +8 -2
  31. package/dist/decorators/details/Details.d.ts +11 -0
  32. package/dist/decorators/details/DetailsItem.d.ts +11 -0
  33. package/dist/decorators/details/getDetailsPageMeta.d.ts +8 -0
  34. package/dist/decorators/form/Form.d.ts +16 -5
  35. package/dist/decorators/form/Input.d.ts +14 -9
  36. package/dist/decorators/form/getFormPageMeta.d.ts +10 -0
  37. package/dist/decorators/form/inputs/SelectInput.d.ts +23 -0
  38. package/dist/decorators/list/Cell.d.ts +13 -1
  39. package/dist/decorators/list/List.d.ts +18 -1
  40. package/dist/decorators/list/cells/ImageCell.d.ts +9 -0
  41. package/dist/decorators/list/getListPageMeta.d.ts +8 -0
  42. package/dist/index.cjs.js +1 -1
  43. package/dist/index.d.ts +13 -17
  44. package/dist/index.esm.js +1 -1
  45. package/dist/initPanel.d.ts +1 -1
  46. package/dist/store/store.d.ts +0 -3
  47. package/dist/types/AnyClass.d.ts +2 -1
  48. package/dist/types/getDetailsData.d.ts +1 -0
  49. package/dist/types/initPanelOptions.d.ts +0 -1
  50. package/package.json +1 -1
  51. package/src/assets/icons/svg/check.svg +4 -0
  52. package/src/assets/icons/svg/cross.svg +4 -0
  53. package/src/components/DetailsPage.tsx +55 -0
  54. package/src/components/{components/ErrorComponent.tsx → ErrorComponent.tsx} +1 -1
  55. package/src/components/{pages/Login.tsx → Login.tsx} +2 -2
  56. package/src/components/Panel.tsx +4 -5
  57. package/src/components/form/Checkbox.tsx +21 -0
  58. package/src/components/{components → form}/FormField.tsx +22 -30
  59. package/src/components/form/FormPage.tsx +32 -0
  60. package/src/components/form/InnerForm.tsx +84 -0
  61. package/src/components/form/Label.tsx +21 -0
  62. package/src/components/form/Select.tsx +51 -0
  63. package/src/components/form/SelectStyles.ts +73 -0
  64. package/src/components/form/Uploader.tsx +66 -0
  65. package/src/components/layout/Layout.tsx +29 -32
  66. package/src/components/layout/SideBar.tsx +4 -13
  67. package/src/components/list/CellField.tsx +63 -0
  68. package/src/components/list/Datagrid.tsx +106 -0
  69. package/src/components/{components/list → list}/FilterPopup.tsx +13 -9
  70. package/src/components/list/ListHeader.tsx +47 -0
  71. package/src/components/{components/list → list}/ListPage.tsx +20 -82
  72. package/src/decorators/details/Details.ts +31 -0
  73. package/src/decorators/details/DetailsItem.ts +40 -0
  74. package/src/decorators/details/getDetailsPageMeta.ts +15 -0
  75. package/src/decorators/form/Form.ts +38 -12
  76. package/src/decorators/form/Input.ts +31 -9
  77. package/src/decorators/form/getFormPageMeta.ts +21 -0
  78. package/src/decorators/form/inputs/SelectInput.ts +19 -0
  79. package/src/decorators/list/Cell.ts +41 -1
  80. package/src/decorators/list/List.ts +30 -6
  81. package/src/decorators/list/cells/ImageCell.ts +17 -0
  82. package/src/decorators/list/getListPageMeta.ts +16 -0
  83. package/src/index.ts +33 -24
  84. package/src/initPanel.ts +1 -4
  85. package/src/store/store.ts +0 -5
  86. package/src/styles/components/checkbox.scss +42 -0
  87. package/src/styles/components/uploader.scss +86 -0
  88. package/src/styles/details.scss +62 -0
  89. package/src/styles/form.scss +9 -11
  90. package/src/styles/index.scss +26 -12
  91. package/src/styles/list.scss +3 -1
  92. package/src/types/AnyClass.ts +2 -1
  93. package/src/types/initPanelOptions.ts +1 -3
  94. package/src/components/components/Checkbox.tsx +0 -9
  95. package/src/components/components/ImageUploader.tsx +0 -301
  96. package/src/components/components/InnerForm.tsx +0 -74
  97. package/src/components/components/Label.tsx +0 -15
  98. package/src/components/components/index.ts +0 -8
  99. package/src/components/components/list/Datagrid.tsx +0 -127
  100. package/src/components/pages/ControllerDetails.tsx +0 -37
  101. package/src/components/pages/FormPage.tsx +0 -34
  102. package/src/decorators/Crud.ts +0 -20
  103. package/src/decorators/form/FormOptions.ts +0 -8
  104. package/src/decorators/form/getFormFields.ts +0 -13
  105. package/src/decorators/list/GetCellFields.ts +0 -13
  106. package/src/decorators/list/ImageCell.ts +0 -13
  107. package/src/decorators/list/ListData.ts +0 -7
  108. package/src/decorators/list/getListFields.ts +0 -10
  109. package/src/styles/image-uploader.scss +0 -94
  110. package/src/types/Screen.ts +0 -4
  111. package/src/types/ScreenCreatorData.ts +0 -14
  112. package/src/utils/createScreens.ts +0 -5
  113. package/src/utils/getFields.ts +0 -22
  114. /package/src/components/{components/Counter.tsx → Counter.tsx} +0 -0
  115. /package/src/components/{components/ErrorBoundary.tsx → ErrorBoundary.tsx} +0 -0
  116. /package/src/components/{components/LoadingScreen.tsx → LoadingScreen.tsx} +0 -0
  117. /package/src/components/{components/list → list}/EmptyList.tsx +0 -0
  118. /package/src/components/{components/list → list}/Pagination.tsx +0 -0
  119. /package/src/components/{components/list → list}/index.ts +0 -0
@@ -1,19 +1,23 @@
1
- import React from 'react';
2
- import { InputOptions } from '../../decorators/form/Input';
3
- import { Label } from './Label';
1
+ import React, { useEffect, useState } from 'react';
2
+ import { InputConfiguration, InputOptions } from '../../decorators/form/Input';
4
3
  import { useFormContext, UseFormRegister } from 'react-hook-form';
5
- import { ImageUploader } from './ImageUploader';
4
+ import { Uploader } from './Uploader';
6
5
  import { Checkbox } from './Checkbox';
6
+ import { Label } from './Label';
7
+ import { Select } from './Select';
7
8
 
8
9
  interface FormFieldProps {
9
- input: InputOptions;
10
+ input: InputConfiguration;
10
11
  register: UseFormRegister<any>;
11
12
  error?: { message?: string };
12
13
  baseName?: string;
14
+ onSelectPreloader?: (
15
+ inputOptions: InputConfiguration
16
+ ) => Promise<{ label: string; value: string }[]>;
13
17
  }
14
18
 
15
19
  interface NestedFormFieldsProps {
16
- input: InputOptions;
20
+ input: InputConfiguration;
17
21
  register: UseFormRegister<any>;
18
22
  }
19
23
 
@@ -25,7 +29,7 @@ function NestedFormFields({ input, register }: NestedFormFieldsProps) {
25
29
  <div>
26
30
  {data?.map((value: any, index: number) => (
27
31
  <div key={index}>
28
- {input.nestedFields?.map((nestedInput: InputOptions) => (
32
+ {input.nestedFields?.map((nestedInput: InputConfiguration) => (
29
33
  <FormField
30
34
  key={nestedInput.name?.toString() ?? ''}
31
35
  baseName={input.name + '[' + index + ']'}
@@ -44,39 +48,25 @@ function NestedFormFields({ input, register }: NestedFormFieldsProps) {
44
48
  );
45
49
  }
46
50
 
47
- export function FormField({ input, register, error, baseName }: FormFieldProps) {
51
+ export function FormField({ input, register, error, baseName, onSelectPreloader }: FormFieldProps) {
48
52
  const fieldName = (baseName ? baseName.toString() + '.' : '') + input.name || '';
49
53
  const renderField = () => {
50
54
  switch (input.type) {
51
55
  case 'textarea':
52
- return <textarea {...register(fieldName)} placeholder={input.placeholder} id={fieldName} />;
56
+ return <textarea {...register(fieldName)} placeholder={input.placeholder} />;
53
57
  case 'select':
58
+ return <Select input={input} fieldName={fieldName} />;
59
+ case 'input': {
54
60
  return (
55
- <select {...register(fieldName)} id={fieldName}>
56
- <option value="">Select {fieldName}</option>
57
- {input.options?.map(option => (
58
- <option key={option.value} value={option.value}>
59
- {option.label}
60
- </option>
61
- ))}
62
- </select>
63
- );
64
- case 'input': {
65
- return (
66
- <input
67
- type={input.inputType}
68
- {...register(fieldName)}
69
- placeholder={input.placeholder}
70
- id={fieldName}
71
- />
61
+ <input type={input.inputType} {...register(fieldName)} placeholder={input.placeholder} />
72
62
  );
73
63
  }
74
64
  case 'file-upload':
75
- return <ImageUploader />;
65
+ return <Uploader input={input} />;
76
66
  case 'checkbox':
77
- return <Checkbox {...register(fieldName)} id={fieldName} />;
67
+ return <Checkbox input={input} />;
78
68
  case 'hidden':
79
- return <input type="hidden" {...register(fieldName)} id={fieldName} />;
69
+ return <input type="hidden" {...register(fieldName)} />;
80
70
  case 'nested':
81
71
  return <NestedFormFields input={input} register={register} />;
82
72
  default:
@@ -86,7 +76,9 @@ export function FormField({ input, register, error, baseName }: FormFieldProps)
86
76
 
87
77
  return (
88
78
  <div className="form-field">
89
- <Label htmlFor={fieldName} label={input.label} fieldName={fieldName} />
79
+ {input.type !== 'checkbox' && (
80
+ <Label htmlFor={fieldName} label={input.label} fieldName={fieldName} />
81
+ )}
90
82
  {renderField()}
91
83
  {error && <span className="error-message">{error.message}</span>}
92
84
  </div>
@@ -0,0 +1,32 @@
1
+ import React, { useEffect, useMemo } from 'react';
2
+ import { InnerForm } from './InnerForm';
3
+ import { AnyClass, AnyClassConstructor } from '../../types/AnyClass';
4
+ import { useParams } from 'react-router';
5
+ import { FormProvider, Resolver, useForm } from 'react-hook-form';
6
+ import { getFormPageMeta } from '../../decorators/form/getFormPageMeta';
7
+
8
+ export interface FormPageProps<T extends AnyClass> {
9
+ model: AnyClassConstructor<T>;
10
+ }
11
+
12
+ export function FormPage<T extends AnyClass>({ model }: FormPageProps<T>) {
13
+ const { class: formClass, inputs, resolver } = useMemo(() => getFormPageMeta(model), [model]);
14
+ const form = useForm<T>({
15
+ resolver: resolver as Resolver<T>,
16
+ });
17
+
18
+ const params = useParams();
19
+ useEffect(() => {
20
+ if (formClass.getDetailsData) {
21
+ formClass.getDetailsData(params as Record<string, string>).then(data => {
22
+ form.reset(data as any);
23
+ });
24
+ }
25
+ }, [params, form.reset, formClass.getDetailsData]);
26
+
27
+ return (
28
+ <FormProvider {...form}>
29
+ <InnerForm inputs={inputs} formClass={formClass} />
30
+ </FormProvider>
31
+ );
32
+ }
@@ -0,0 +1,84 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { FormProvider, useForm, useFormContext, UseFormReturn } from 'react-hook-form';
3
+ import { InputConfiguration } from '../../decorators/form/Input';
4
+ import { FormField } from './FormField';
5
+ import { useNavigate } from 'react-router';
6
+ import { FormConfiguration } from '../../decorators/form/Form';
7
+ import { AnyClass } from '../../types/AnyClass';
8
+
9
+ interface InnerFormProps<T extends AnyClass> {
10
+ inputs: InputConfiguration[];
11
+ formClass: FormConfiguration<T>;
12
+ }
13
+
14
+ export function InnerForm<T extends AnyClass>({ inputs, formClass }: InnerFormProps<T>) {
15
+ const [errorMessage, setErrorMessage] = useState<string | null>(null);
16
+ //TODO: any is not a good solution, we need to find a better way to do this
17
+ const formRef = useRef<HTMLFormElement>(null);
18
+ const navigate = useNavigate();
19
+ const form = useFormContext<T>();
20
+
21
+ return (
22
+ <div className="form-wrapper">
23
+ <form
24
+ ref={formRef}
25
+ onSubmit={form.handleSubmit(
26
+ async (dataForm: T) => {
27
+ try {
28
+ const data =
29
+ formClass.type === 'json'
30
+ ? dataForm
31
+ : (() => {
32
+ const formData = new FormData(formRef.current!);
33
+ for (const key in dataForm) {
34
+ if (!formData.get(key)) {
35
+ formData.append(key, dataForm[key]);
36
+ }
37
+ }
38
+ return formData;
39
+ })();
40
+ await formClass.onSubmit(data);
41
+ setErrorMessage(null);
42
+ if (formClass.redirectBackOnSuccess) {
43
+ navigate(-1);
44
+ }
45
+ } catch (error: any) {
46
+ const message =
47
+ error?.response?.data?.message ||
48
+ (error instanceof Error ? error.message : 'An error occurred');
49
+ setErrorMessage(message);
50
+ console.error(error);
51
+ }
52
+ },
53
+ (errors, event) => {
54
+ //TOOD: put error if useer choose global error
55
+ console.log('error creating creation', errors, event);
56
+ }
57
+ )}
58
+ >
59
+ <div>
60
+ {errorMessage && (
61
+ <div className="error-message" style={{ color: 'red', marginBottom: '1rem' }}>
62
+ {errorMessage}
63
+ </div>
64
+ )}
65
+ {inputs?.map((input: InputConfiguration) => (
66
+ <FormField
67
+ key={input.name || ''}
68
+ input={input}
69
+ register={form.register}
70
+ error={
71
+ input.name
72
+ ? { message: (form.formState.errors[input.name as keyof T] as any)?.message }
73
+ : undefined
74
+ }
75
+ />
76
+ ))}
77
+ <button type="submit" className="submit-button">
78
+ Submit
79
+ </button>
80
+ </div>
81
+ </form>
82
+ </div>
83
+ );
84
+ }
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+
3
+ interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
4
+ htmlFor: string;
5
+ label?: string;
6
+ fieldName: string;
7
+ children?: React.ReactNode;
8
+ }
9
+
10
+ export function Label({ label, fieldName, children, ...props }: LabelProps) {
11
+ return (
12
+ <label {...props} className={'label ' + props.className}>
13
+ {
14
+ <span>
15
+ {label ? label + ':' : fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ':'}
16
+ </span>
17
+ }
18
+ {children}
19
+ </label>
20
+ );
21
+ }
@@ -0,0 +1,51 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { InputConfiguration } from '../../decorators/form/Input';
3
+ import { useFormContext, Controller, useWatch } from 'react-hook-form';
4
+ import { SelectInputConfiguration } from '../../decorators/form/inputs/SelectInput';
5
+ import ReactSelect, { SingleValue } from 'react-select';
6
+ import { darkSelectStyles } from './SelectStyles';
7
+
8
+ interface SelectProps {
9
+ input: InputConfiguration;
10
+ fieldName: string;
11
+ }
12
+
13
+ interface OptionType {
14
+ label: string;
15
+ value: string;
16
+ }
17
+
18
+ export function Select({ input, fieldName }: SelectProps) {
19
+ const inputSelect = input as SelectInputConfiguration;
20
+ const { control } = useFormContext();
21
+ const [options, setOptions] = useState<OptionType[]>(inputSelect.defaultOptions || []);
22
+ const [key, setKey] = useState(0);
23
+ useEffect(() => {
24
+ if (inputSelect.onSelectPreloader) {
25
+ inputSelect.onSelectPreloader().then(option => {
26
+ setOptions(option);
27
+ setKey(key + 1);
28
+ });
29
+ }
30
+ }, [input]);
31
+
32
+ return (
33
+ <Controller
34
+ name={fieldName}
35
+ control={control}
36
+ render={({ field }) => {
37
+ return (
38
+ <ReactSelect
39
+ key={key}
40
+ options={options}
41
+ styles={darkSelectStyles}
42
+ value={options.find(option => option.value === field.value) || null}
43
+ onChange={(selectedOption: OptionType | null) => {
44
+ field.onChange(selectedOption?.value);
45
+ }}
46
+ />
47
+ );
48
+ }}
49
+ />
50
+ );
51
+ }
@@ -0,0 +1,73 @@
1
+ import { StylesConfig } from 'react-select';
2
+
3
+ interface OptionType {
4
+ label: string;
5
+ value: string;
6
+ }
7
+
8
+ export const darkSelectStyles: StylesConfig<OptionType, false> = {
9
+ control: (baseStyles, state) => ({
10
+ ...baseStyles,
11
+ backgroundColor: '#1f2937',
12
+ borderColor: state.isFocused ? '#6366f1' : '#374151',
13
+ boxShadow: state.isFocused ? '0 0 0 1px #6366f1' : 'none',
14
+ '&:hover': {
15
+ borderColor: '#6366f1',
16
+ },
17
+ borderRadius: '6px',
18
+ padding: '2px',
19
+ color: 'white',
20
+ }),
21
+ option: (baseStyles, state) => ({
22
+ ...baseStyles,
23
+ backgroundColor: state.isSelected ? '#6366f1' : state.isFocused ? '#374151' : '#1f2937',
24
+ color: 'white',
25
+ '&:active': {
26
+ backgroundColor: '#6366f1',
27
+ },
28
+ '&:hover': {
29
+ backgroundColor: '#374151',
30
+ },
31
+ cursor: 'pointer',
32
+ }),
33
+ input: baseStyles => ({
34
+ ...baseStyles,
35
+ color: 'white',
36
+ }),
37
+ placeholder: baseStyles => ({
38
+ ...baseStyles,
39
+ color: '#9ca3af',
40
+ }),
41
+ singleValue: baseStyles => ({
42
+ ...baseStyles,
43
+ color: 'white',
44
+ }),
45
+ menuPortal: baseStyles => ({
46
+ ...baseStyles,
47
+ zIndex: 9999,
48
+ }),
49
+ menu: baseStyles => ({
50
+ ...baseStyles,
51
+ backgroundColor: '#1f2937',
52
+ border: '1px solid #374151',
53
+ boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
54
+ }),
55
+ menuList: baseStyles => ({
56
+ ...baseStyles,
57
+ padding: '4px',
58
+ }),
59
+ dropdownIndicator: baseStyles => ({
60
+ ...baseStyles,
61
+ color: '#9ca3af',
62
+ '&:hover': {
63
+ color: '#6366f1',
64
+ },
65
+ }),
66
+ clearIndicator: baseStyles => ({
67
+ ...baseStyles,
68
+ color: '#9ca3af',
69
+ '&:hover': {
70
+ color: '#6366f1',
71
+ },
72
+ }),
73
+ };
@@ -0,0 +1,66 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useFormContext } from 'react-hook-form';
3
+ import { InputConfiguration } from '../../decorators/form/Input';
4
+
5
+ interface UploaderProps {
6
+ input: InputConfiguration;
7
+ maxLength?: number;
8
+ }
9
+
10
+ export function Uploader({ input, maxLength = 1 }: UploaderProps) {
11
+ const form = useFormContext();
12
+ const [files, setFiles] = useState<File[]>([]);
13
+ const id = input.name!;
14
+
15
+ useEffect(() => {
16
+ // Update form value whenever files change
17
+ form.setValue(input.name + '_files', files.length > 0);
18
+ }, [files, form, input.name]);
19
+
20
+ const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
21
+ if (maxLength > 1) {
22
+ throw new Error('TODO: Multiple file upload is not implemented yet');
23
+ }
24
+
25
+ const fileList = e.target.files;
26
+ if (fileList) {
27
+ const filesArray = Array.from(fileList);
28
+ setFiles(prevFiles => [...prevFiles, ...filesArray]);
29
+ }
30
+ };
31
+
32
+ const removeFile = (index: number) => {
33
+ setFiles(prevFiles => prevFiles.filter((_, i) => i !== index));
34
+ };
35
+
36
+ return (
37
+ <div className="uploader-container">
38
+ <button
39
+ type="button"
40
+ className="uploader-button"
41
+ onClick={() => document.getElementById(id)?.click()}
42
+ >
43
+ Upload Files
44
+ </button>
45
+ <input id={id} hidden name={input.name} onChange={onChange} type="file" multiple />
46
+ {files.length > 0 && (
47
+ <div className="uploader-files">
48
+ {files.map((file, index) => (
49
+ <div key={`${file.name}-${index}`} className="uploader-file">
50
+ <p>{file.name}</p>
51
+ <p>{(file.size / 1024 / 1024).toFixed(2)} MB</p>
52
+ <p>{file.type || 'Unknown type'}</p>
53
+ <button
54
+ onClick={() => removeFile(index)}
55
+ className="remove-file-button"
56
+ type="button"
57
+ >
58
+ Remove
59
+ </button>
60
+ </div>
61
+ ))}
62
+ </div>
63
+ )}
64
+ </div>
65
+ );
66
+ }
@@ -1,38 +1,35 @@
1
- import React from "react";
2
- import { SideBar } from "./SideBar";
3
- import { ScreenCreatorData } from "../../types/ScreenCreatorData";
4
- import { useAppStore } from "../../store/store";
5
- import { useNavigate } from "react-router";
1
+ import React from 'react';
2
+ import { SideBar } from './SideBar';
3
+ import { useAppStore } from '../../store/store';
4
+ import { useNavigate } from 'react-router';
6
5
 
7
6
  export function Layout<IconType>({
8
- children,
9
- menu,
10
- getIcons,
11
- logout,
7
+ children,
8
+ menu,
9
+ getIcons,
10
+ logout,
12
11
  }: {
13
- children?: React.ReactNode;
14
- menu?: (screens: Record<string, ScreenCreatorData>) => { name: string; path: string; iconType: IconType }[];
15
- getIcons?: (iconType: IconType) => React.ReactNode;
16
- logout?: () => void;
12
+ children?: React.ReactNode;
13
+ menu?: () => { name: string; path: string; iconType: IconType }[];
14
+ getIcons?: (iconType: IconType) => React.ReactNode;
15
+ logout?: (type: 'redirect' | 'logout') => void;
17
16
  }) {
18
- const { user, screenPaths } = useAppStore((s) => ({
19
- user: s.user,
20
- screenPaths: s.screenPaths,
21
- }));
22
- const data = useAppStore();
23
- const navigate = useNavigate();
24
- if (!user) {
25
- navigate(screenPaths.login);
26
- }
17
+ const { user } = useAppStore(s => ({
18
+ user: s.user,
19
+ }));
20
+ const navigate = useNavigate();
21
+ if (!user) {
22
+ logout?.('redirect');
23
+ }
27
24
 
28
- return (
29
- <div className="layout">
30
- <SideBar onLogout={() => {
31
- if (logout) {
32
- logout();
33
- }
34
- }} menu={menu} getIcons={getIcons} />
35
- <main className="content">{children}</main>
36
- </div>
37
- );
25
+ return (
26
+ <div className="layout">
27
+ <SideBar
28
+ onLogout={logout}
29
+ menu={menu}
30
+ getIcons={getIcons}
31
+ />
32
+ <main className="content">{children}</main>
33
+ </div>
34
+ );
38
35
  }
@@ -1,11 +1,7 @@
1
1
  import React, { useState } from 'react';
2
2
  import { Link, useLocation, useNavigate } from 'react-router';
3
- import { ScreenCreatorData } from '../../types/ScreenCreatorData';
4
- import { useAppStore } from '../../store/store';
5
3
 
6
- type GetMenuFunction<IconType> = (
7
- screens: Record<string, ScreenCreatorData>
8
- ) => { name: string; path: string; iconType: IconType }[];
4
+ type GetMenuFunction<IconType> = () => { name: string; path: string; iconType: IconType }[];
9
5
 
10
6
  type GetIconsFunction<IconType> = (iconType: IconType) => React.ReactNode;
11
7
 
@@ -16,12 +12,8 @@ export function SideBar<IconType>({
16
12
  }: {
17
13
  menu?: GetMenuFunction<IconType>;
18
14
  getIcons?: GetIconsFunction<IconType>;
19
- onLogout?: () => void;
15
+ onLogout?: (type: 'redirect' | 'logout') => void;
20
16
  }) {
21
- const { screens, screenPaths } = useAppStore(s => ({
22
- screens: s.screens ?? {},
23
- screenPaths: s.screenPaths ?? {},
24
- }));
25
17
  const [isOpen, setIsOpen] = useState(true);
26
18
  const location = useLocation();
27
19
  const navigate = useNavigate();
@@ -54,7 +46,7 @@ export function SideBar<IconType>({
54
46
  {isOpen ? '<' : '>'}
55
47
  </button>
56
48
  <nav className="nav-links">
57
- {menu?.(screens).map((item, index) => (
49
+ {menu?.().map((item, index) => (
58
50
  <Link
59
51
  key={index}
60
52
  to={item.path}
@@ -72,8 +64,7 @@ export function SideBar<IconType>({
72
64
  className="logout-button"
73
65
  onClick={() => {
74
66
  if (onLogout) {
75
- onLogout();
76
- navigate(screenPaths.login);
67
+ onLogout('logout');
77
68
  }
78
69
  }}
79
70
  aria-label="Logout"
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { ImageCellOptions } from '../../decorators/list/cells/ImageCell';
3
+ import { AnyClass } from '../../types/AnyClass';
4
+ import { ExtendedCellTypes } from '../../decorators/list/Cell';
5
+ import CheckIcon from '../../assets/icons/svg/check.svg';
6
+ import CrossIcon from '../../assets/icons/svg/cross.svg';
7
+
8
+ interface CellFieldProps<T extends AnyClass> {
9
+ cellOptions: any; // TODO: Create proper type for cellOptions
10
+ item: T;
11
+ value: any;
12
+ }
13
+
14
+ export function CellField<T extends AnyClass>({ cellOptions, item, value }: CellFieldProps<T>) {
15
+ let render = value ?? '-'; // Default value if the field is undefined or null
16
+
17
+ switch (cellOptions.type as ExtendedCellTypes) {
18
+ case 'boolean': {
19
+ render = value ? <CheckIcon className="icon icon-true" /> : <CrossIcon className="icon icon-false" />;
20
+ break;
21
+ }
22
+ case 'date':
23
+ if (value) {
24
+ const date = new Date(value);
25
+ render = `${date.getDate().toString().padStart(2, '0')}/${(date.getMonth() + 1)
26
+ .toString()
27
+ .padStart(2, '0')}/${date.getFullYear()} ${date
28
+ .getHours()
29
+ .toString()
30
+ .padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
31
+ }
32
+ break;
33
+
34
+ case 'image': {
35
+ const imageCellOptions = cellOptions as ImageCellOptions;
36
+ render = (
37
+ <img
38
+ width={100}
39
+ height={100}
40
+ src={imageCellOptions.baseUrl + value}
41
+ style={{ objectFit: 'contain' }}
42
+ />
43
+ );
44
+ break;
45
+ }
46
+ case 'uuid':
47
+ if (value && typeof value === 'string' && value.length >= 6) {
48
+ render = `${value.slice(0, 3)}...${value.slice(-3)}`;
49
+ }
50
+ break;
51
+ default:
52
+ render = value ? value.toString() : (cellOptions?.placeHolder ?? '-');
53
+ break;
54
+ }
55
+
56
+ /*
57
+ if (cellOptions.linkTo) {
58
+ render = <Link to={cellOptions.linkTo(item)}>{formattedValue}</Link>;
59
+ }
60
+ */
61
+
62
+ return <td key={cellOptions.name}>{render}</td>;
63
+ }