proje-react-panel 1.0.16 → 1.0.17-test-8

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 (37) hide show
  1. package/dist/components/components/FormField.d.ts +5 -1
  2. package/dist/components/components/InnerForm.d.ts +8 -3
  3. package/dist/components/components/Uploader.d.ts +8 -0
  4. package/dist/components/components/index.d.ts +1 -1
  5. package/dist/components/components/list/Datagrid.d.ts +3 -7
  6. package/dist/components/components/list/FilterPopup.d.ts +3 -3
  7. package/dist/components/components/list/ListPage.d.ts +2 -4
  8. package/dist/components/pages/FormPage.d.ts +8 -2
  9. package/dist/decorators/form/Input.d.ts +1 -0
  10. package/dist/decorators/list/List.d.ts +5 -6
  11. package/dist/decorators/list/ListData.d.ts +4 -4
  12. package/dist/decorators/list/getListFields.d.ts +2 -2
  13. package/dist/index.cjs.js +1 -1
  14. package/dist/index.esm.js +1 -1
  15. package/dist/types/AnyClass.d.ts +1 -1
  16. package/dist/types/ScreenCreatorData.d.ts +5 -5
  17. package/package.json +1 -1
  18. package/src/components/components/FormField.tsx +15 -6
  19. package/src/components/components/InnerForm.tsx +47 -8
  20. package/src/components/components/Uploader.tsx +66 -0
  21. package/src/components/components/index.ts +2 -2
  22. package/src/components/components/list/Datagrid.tsx +91 -77
  23. package/src/components/components/list/FilterPopup.tsx +4 -4
  24. package/src/components/components/list/ListPage.tsx +13 -15
  25. package/src/components/pages/FormPage.tsx +8 -1
  26. package/src/decorators/form/Input.ts +3 -1
  27. package/src/decorators/form/getFormFields.ts +1 -0
  28. package/src/decorators/list/List.ts +8 -7
  29. package/src/decorators/list/ListData.ts +5 -5
  30. package/src/decorators/list/getListFields.ts +8 -8
  31. package/src/styles/form.scss +2 -4
  32. package/src/styles/index.scss +12 -12
  33. package/src/styles/list.scss +3 -1
  34. package/src/styles/uploader.scss +86 -0
  35. package/src/types/AnyClass.ts +3 -1
  36. package/src/types/ScreenCreatorData.ts +12 -12
  37. package/src/components/components/ImageUploader.tsx +0 -301
@@ -0,0 +1,66 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useForm, Controller, useFormContext } from 'react-hook-form';
3
+ import { InputOptions } from '../../decorators/form/Input';
4
+
5
+ interface UploaderProps {
6
+ input: InputOptions;
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
+ }
@@ -2,7 +2,7 @@ export { InnerForm } from './InnerForm';
2
2
  export { FormField } from './FormField';
3
3
  export { LoadingScreen } from './LoadingScreen';
4
4
  export { Counter } from './Counter';
5
- export { ImageUploader } from './ImageUploader';
5
+ export { Uploader } from './Uploader';
6
6
  export { ErrorComponent } from './ErrorComponent';
7
7
  export { Label } from './Label';
8
- export { ErrorBoundary } from './ErrorBoundary';
8
+ export { ErrorBoundary } from './ErrorBoundary';
@@ -9,15 +9,19 @@ import SearchIcon from '../../../assets/icons/svg/search.svg';
9
9
  import PencilIcon from '../../../assets/icons/svg/pencil.svg';
10
10
  import TrashIcon from '../../../assets/icons/svg/trash.svg';
11
11
 
12
- interface DatagridProps<T extends { id: string }> {
12
+ interface DatagridProps<T> {
13
13
  data: T[];
14
- listData: ListData;
14
+ listData: ListData<T>;
15
15
  onRemoveItem?: (item: T) => Promise<void>;
16
16
  }
17
17
 
18
- export function Datagrid<T extends { id: string }>({ data, listData, onRemoveItem }: DatagridProps<T>) {
18
+ export function Datagrid<T>({ data, listData, onRemoveItem }: DatagridProps<T>) {
19
19
  const cells = listData.cells;
20
- const utilCells = listData.list?.utilCells;
20
+ const listGeneralCells = data?.[0]
21
+ ? typeof listData.list?.cells === 'function'
22
+ ? listData.list?.cells?.(data[0])
23
+ : listData.list?.cells
24
+ : null;
21
25
 
22
26
  return (
23
27
  <div className="datagrid">
@@ -30,89 +34,99 @@ export function Datagrid<T extends { id: string }>({ data, listData, onRemoveIte
30
34
  {cells.map(cellOptions => (
31
35
  <th key={cellOptions.name}>{cellOptions.title ?? cellOptions.name}</th>
32
36
  ))}
33
- {utilCells?.details && <th>Details</th>}
34
- {utilCells?.edit && <th>Edit</th>}
35
- {utilCells?.delete && <th>Delete</th>}
37
+ {listGeneralCells?.details && <th>Details</th>}
38
+ {listGeneralCells?.edit && <th>Edit</th>}
39
+ {listGeneralCells?.delete && <th>Delete</th>}
36
40
  </tr>
37
41
  </thead>
38
42
  <tbody>
39
- {data.map((item, index) => (
40
- <tr key={index}>
41
- {cells.map(cellOptions => {
42
- // @ts-ignore
43
- const value = item[cellOptions.name];
44
- let render = value ?? '-'; // Default value if the field is undefined or null
43
+ {data.map((item, index) => {
44
+ const listCells = item
45
+ ? typeof listData.list?.cells === 'function'
46
+ ? listData.list?.cells?.(item)
47
+ : listData.list?.cells
48
+ : null;
49
+ return (
50
+ <tr key={index}>
51
+ {cells.map(cellOptions => {
52
+ // @ts-ignore
53
+ const value = item[cellOptions.name];
54
+ let render = value ?? '-'; // Default value if the field is undefined or null
45
55
 
46
- switch (cellOptions.type) {
47
- case 'date':
48
- if (value) {
49
- const date = new Date(value);
50
- render = `${date.getDate().toString().padStart(2, '0')}/${(
51
- date.getMonth() + 1
52
- )
53
- .toString()
54
- .padStart(
55
- 2,
56
- '0'
57
- )}/${date.getFullYear()} ${date.getHours().toString().padStart(2, '0')}:${date
58
- .getMinutes()
59
- .toString()
60
- .padStart(2, '0')}`;
61
- }
62
- break;
56
+ switch (cellOptions.type) {
57
+ case 'date':
58
+ if (value) {
59
+ const date = new Date(value);
60
+ render = `${date.getDate().toString().padStart(2, '0')}/${(
61
+ date.getMonth() + 1
62
+ )
63
+ .toString()
64
+ .padStart(
65
+ 2,
66
+ '0'
67
+ )}/${date.getFullYear()} ${date.getHours().toString().padStart(2, '0')}:${date
68
+ .getMinutes()
69
+ .toString()
70
+ .padStart(2, '0')}`;
71
+ }
72
+ break;
63
73
 
64
- case 'image': {
65
- const imageCellOptions = cellOptions as ImageCellOptions;
66
- render = (
67
- <img
68
- width={100}
69
- height={100}
70
- src={imageCellOptions.baseUrl + value}
71
- style={{ objectFit: 'contain' }}
72
- />
73
- );
74
- break;
74
+ case 'image': {
75
+ const imageCellOptions = cellOptions as ImageCellOptions;
76
+ render = (
77
+ <img
78
+ width={100}
79
+ height={100}
80
+ src={imageCellOptions.baseUrl + value}
81
+ style={{ objectFit: 'contain' }}
82
+ />
83
+ );
84
+ break;
85
+ }
86
+ case 'string':
87
+ default:
88
+ render = value ? value.toString() : (cellOptions?.placeHolder ?? '-'); // Handles string type or default fallback
89
+ break;
75
90
  }
76
- case 'string':
77
- default:
78
- render = value ? value.toString() : (cellOptions?.placeHolder ?? '-'); // Handles string type or default fallback
79
- break;
80
- }
81
- /*
91
+ /*
82
92
  if (cellOptions.linkTo) {
83
93
  render = <Link to={cellOptions.linkTo(item)}>{formattedValue}</Link>;
84
94
  }
85
95
  */
86
- return <td key={cellOptions.name}>{render}</td>;
87
- })}
88
- {utilCells?.details && (
89
- <td>
90
- <Link to={`${utilCells.details.path}/${item.id}`} className="util-cell-link">
91
- <SearchIcon className="icon icon-search" />
92
- <span className="util-cell-label">{utilCells.details.label}</span>
93
- </Link>
94
- </td>
95
- )}
96
- {utilCells?.edit && (
97
- <td>
98
- <Link to={`${utilCells.edit.path}/${item.id}`} className="util-cell-link">
99
- <PencilIcon className="icon icon-pencil" />
100
- <span className="util-cell-label">{utilCells.edit.label}</span>
101
- </Link>
102
- </td>
103
- )}
104
- {utilCells?.delete && (
105
- <td>
106
- <a onClick={() => {
107
- onRemoveItem?.(item)
108
- }} className="util-cell-link">
109
- <TrashIcon className="icon icon-trash" />
110
- <span className="util-cell-label">{utilCells.delete.label}</span>
111
- </a>
112
- </td>
113
- )}
114
- </tr>
115
- ))}
96
+ return <td key={cellOptions.name}>{render}</td>;
97
+ })}
98
+ {listCells?.details && (
99
+ <td>
100
+ <Link to={listCells.details.path} className="util-cell-link">
101
+ <SearchIcon className="icon icon-search" />
102
+ <span className="util-cell-label">{listCells.details.label}</span>
103
+ </Link>
104
+ </td>
105
+ )}
106
+ {listCells?.edit && (
107
+ <td>
108
+ <Link to={listCells.edit.path} className="util-cell-link">
109
+ <PencilIcon className="icon icon-pencil" />
110
+ <span className="util-cell-label">{listCells.edit.label}</span>
111
+ </Link>
112
+ </td>
113
+ )}
114
+ {listCells?.delete && (
115
+ <td>
116
+ <a
117
+ onClick={() => {
118
+ onRemoveItem?.(item);
119
+ }}
120
+ className="util-cell-link util-cell-link-remove"
121
+ >
122
+ <TrashIcon className="icon icon-trash" />
123
+ <span className="util-cell-label">{listCells.delete.label}</span>
124
+ </a>
125
+ </td>
126
+ )}
127
+ </tr>
128
+ );
129
+ })}
116
130
  </tbody>
117
131
  </table>
118
132
  )}
@@ -3,11 +3,11 @@ import { ListData } from '../../../decorators/list/ListData';
3
3
  import { CellOptions, StaticSelectFilter } from '../../../decorators/list/Cell';
4
4
  import Select from 'react-select';
5
5
 
6
- interface FilterPopupProps {
6
+ interface FilterPopupProps<T> {
7
7
  isOpen: boolean;
8
8
  onClose: () => void;
9
9
  onApplyFilters: (filters: Record<string, string>) => void;
10
- listData: ListData;
10
+ listData: ListData<T>;
11
11
  activeFilters?: Record<string, string>;
12
12
  }
13
13
 
@@ -126,13 +126,13 @@ function FilterField({ field, value, onChange }: FilterFieldProps): React.ReactE
126
126
  }
127
127
  }
128
128
 
129
- export function FilterPopup({
129
+ export function FilterPopup<T>({
130
130
  isOpen,
131
131
  onClose,
132
132
  onApplyFilters,
133
133
  listData,
134
134
  activeFilters,
135
- }: FilterPopupProps): React.ReactElement | null {
135
+ }: FilterPopupProps<T>): React.ReactElement | null {
136
136
  const [filters, setFilters] = React.useState<Record<string, any>>(activeFilters ?? {});
137
137
  const popupRef = useRef<HTMLDivElement>(null);
138
138
  const fields = useMemo(() => listData.cells.filter(cell => !!cell.filter), [listData.cells]);
@@ -26,17 +26,17 @@ export interface PaginatedResponse<T> {
26
26
 
27
27
  export type GetDataForList<T> = (params: GetDataParams) => Promise<PaginatedResponse<T>>;
28
28
 
29
- const ListHeader = ({
29
+ function ListHeader<T extends AnyClass>({
30
30
  listData,
31
31
  filtered,
32
32
  onFilterClick,
33
33
  customHeader,
34
34
  }: {
35
- listData: ListData;
35
+ listData: ListData<T>;
36
36
  filtered: boolean;
37
37
  onFilterClick: () => void;
38
38
  customHeader?: React.ReactNode;
39
- }) => {
39
+ }) {
40
40
  const fields = useMemo(() => listData.cells.filter(cell => !!cell.filter), [listData.cells]);
41
41
 
42
42
  const header = listData.list?.headers;
@@ -60,15 +60,15 @@ const ListHeader = ({
60
60
  </div>
61
61
  </div>
62
62
  );
63
- };
63
+ }
64
64
 
65
- export function ListPage<T extends AnyClass & { id: string }>({
65
+ export function ListPage<T extends AnyClass>({
66
66
  model,
67
67
  getData,
68
68
  onRemoveItem,
69
69
  customHeader,
70
70
  }: {
71
- model: T;
71
+ model: any;
72
72
  getData: GetDataForList<T>;
73
73
  customHeader?: React.ReactNode;
74
74
  onRemoveItem?: (item: T) => Promise<void>;
@@ -151,15 +151,13 @@ export function ListPage<T extends AnyClass & { id: string }>({
151
151
  data={data}
152
152
  onRemoveItem={async (item: T) => {
153
153
  if (onRemoveItem) {
154
- alert({
155
- title: 'Are you sure you want to delete this item?',
156
- message: 'This action cannot be undone.',
157
- onConfirm: async () => {
158
- await onRemoveItem(item);
159
- setData(data.filter((d: T) => d.id !== item.id));
160
- await fetchData(pagination.page);
161
- },
162
- });
154
+ if (
155
+ confirm('Are you sure you want to delete this item? This action cannot be undone.')
156
+ ) {
157
+ await onRemoveItem(item);
158
+ //setData(data.filter((d: T) => d.id !== item.id));
159
+ await fetchData(pagination.page);
160
+ }
163
161
  }
164
162
  }}
165
163
  />
@@ -2,16 +2,19 @@ import React, { useMemo } from 'react';
2
2
  import { InnerForm } from '../components';
3
3
  import { AnyClass } from '../../types/AnyClass';
4
4
  import { getFormFields } from '../../decorators/form/getFormFields';
5
+ import { InputOptions } from '../../decorators/form/Input';
5
6
 
6
7
  export type GetDetailsDataFN<T> = (param: Record<string, string>) => Promise<T>;
7
8
  export type OnSubmitFN<T> = (data: T) => Promise<T>;
8
9
 
9
10
  export interface FormPageProps<T extends AnyClass> {
10
- model: T;
11
+ model: any; //TODO: use T not typeof T
11
12
  getDetailsData?: GetDetailsDataFN<T>;
12
13
  redirect?: string;
13
14
  onSubmit: OnSubmitFN<T>;
15
+ onSelectPreloader?: (inputOptions: InputOptions) => Promise<{ label: string; value: string }[]>;
14
16
  redirectBackOnSuccess?: boolean;
17
+ type?: 'json' | 'formData';
15
18
  }
16
19
 
17
20
  export function FormPage<T extends AnyClass>({
@@ -19,7 +22,9 @@ export function FormPage<T extends AnyClass>({
19
22
  getDetailsData,
20
23
  onSubmit,
21
24
  redirect,
25
+ onSelectPreloader,
22
26
  redirectBackOnSuccess = true,
27
+ type = 'json',
23
28
  ...rest
24
29
  }: FormPageProps<T>) {
25
30
  const formOptions = useMemo(() => getFormFields(model), [model]);
@@ -29,6 +34,8 @@ export function FormPage<T extends AnyClass>({
29
34
  onSubmit={onSubmit}
30
35
  formOptions={formOptions}
31
36
  redirectBackOnSuccess={redirectBackOnSuccess}
37
+ onSelectPreloader={onSelectPreloader}
38
+ type={type}
32
39
  />
33
40
  );
34
41
  }
@@ -15,6 +15,7 @@ export interface InputOptions {
15
15
  placeholder?: string;
16
16
  cancelPasswordValidationOnEdit?: boolean;
17
17
  options?: { value: string; label: string }[];
18
+ optionsPreload?: boolean;
18
19
  nestedFields?: InputOptions[];
19
20
  }
20
21
 
@@ -31,7 +32,8 @@ export function Input(options?: InputOptions): PropertyDecorator {
31
32
  }
32
33
 
33
34
  export function getInputFields<T extends AnyClass>(entityClass: T): InputOptions[] {
34
- const prototype = entityClass.prototype;
35
+ //TODO2: ANY IS NOT A GOOD SOLUTION, WE NEED TO FIND A BETTER WAY TO DO THIS
36
+ const prototype = (entityClass as any).prototype;
35
37
  const inputFields: string[] = Reflect.getMetadata(INPUT_KEY, prototype) || [];
36
38
  return inputFields.map(field => {
37
39
  const fields = Reflect.getMetadata(`${INPUT_KEY.toString()}:${field}:options`, prototype) || {};
@@ -5,6 +5,7 @@ import { getFormConfiguration } from "./Form";
5
5
  import { classValidatorResolver } from "@hookform/resolvers/class-validator";
6
6
 
7
7
  export function getFormFields<T extends AnyClass>(entityClass: T): FormOptions {
8
+ console.log(entityClass);
8
9
  return {
9
10
  resolver: classValidatorResolver(entityClass as any),
10
11
  form: getFormConfiguration(entityClass),
@@ -7,18 +7,18 @@ export interface ListHeaderOptions {
7
7
  create?: { path: string; label: string };
8
8
  }
9
9
 
10
- export interface ListUtilCellOptions {
10
+ export interface ListCellOptions<T> {
11
11
  details?: { path: string; label: string };
12
12
  edit?: { path: string; label: string };
13
- delete?: { path: string; label: string };
13
+ delete?: { label: string };
14
14
  }
15
15
 
16
- export interface ListOptions {
16
+ export interface ListOptions<T> {
17
17
  headers?: ListHeaderOptions;
18
- utilCells?: ListUtilCellOptions;
18
+ cells?: ((item: T) => ListCellOptions<T>) | ListCellOptions<T>;
19
19
  }
20
20
 
21
- export function List(options?: ListOptions): ClassDecorator {
21
+ export function List<T>(options?: ListOptions<T> | ((item: T) => ListOptions<T>)): ClassDecorator {
22
22
  return (target: Function) => {
23
23
  if (options) {
24
24
  Reflect.defineMetadata(LIST_KEY, options, target);
@@ -26,6 +26,7 @@ export function List(options?: ListOptions): ClassDecorator {
26
26
  };
27
27
  }
28
28
 
29
- export function getClassListData(entityClass: any): ListOptions | undefined {
30
- return Reflect.getMetadata(LIST_KEY, entityClass);
29
+ export function getClassListData<T>(entityClass: T): ListOptions<T> | undefined {
30
+ //TODO: try to remove any
31
+ return Reflect.getMetadata(LIST_KEY, entityClass as any);
31
32
  }
@@ -1,7 +1,7 @@
1
- import { ListOptions } from "./List";
2
- import { CellOptions } from "./Cell";
1
+ import { ListOptions } from './List';
2
+ import { CellOptions } from './Cell';
3
3
 
4
- export interface ListData {
5
- list?: ListOptions;
6
- cells: CellOptions[];
4
+ export interface ListData<T> {
5
+ list?: ListOptions<T>;
6
+ cells: CellOptions[];
7
7
  }
@@ -1,10 +1,10 @@
1
- import { getClassListData } from "./List";
2
- import { getCellFields } from "./GetCellFields";
3
- import { ListData } from "./ListData";
1
+ import { getClassListData } from './List';
2
+ import { getCellFields } from './GetCellFields';
3
+ import { ListData } from './ListData';
4
4
 
5
- export function getListFields<T>(entityClass: T): ListData {
6
- return {
7
- list: getClassListData(entityClass),
8
- cells: getCellFields(entityClass),
9
- };
5
+ export function getListFields<T>(entityClass: T): ListData<T> {
6
+ return {
7
+ list: getClassListData(entityClass),
8
+ cells: getCellFields(entityClass),
9
+ };
10
10
  }
@@ -1,10 +1,8 @@
1
1
  .form-wrapper {
2
- max-width: 400px;
3
2
  padding: 20px;
4
- border-radius: 10px;
5
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
6
3
  background-color: #1a1a1a;
7
4
  color: #f2f2f2;
5
+ width: 100%;
8
6
 
9
7
  form {
10
8
  display: flex;
@@ -59,7 +57,7 @@
59
57
 
60
58
  select {
61
59
  appearance: none;
62
- background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23FFFFFF%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
60
+ background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23FFFFFF%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E');
63
61
  background-repeat: no-repeat;
64
62
  background-position: right 0.7rem top 50%;
65
63
  background-size: 0.65rem auto;
@@ -1,15 +1,15 @@
1
- @use './layout';
2
- // Imported styles will be scoped under .layout
3
- @use './sidebar';
4
- @use './form';
5
- @use './list';
6
- @use './login';
7
- @use './error-boundary';
8
- @use './image-uploader';
9
- @use './counter';
10
- @use './loading-screen';
11
- @use './pagination';
12
- @use './filter-popup';
1
+ @import './layout';
2
+ @import './sidebar';
3
+ @import './form';
4
+ @import './list';
5
+ @import './login';
6
+ @import './error-boundary';
7
+ @import './image-uploader';
8
+ @import './counter';
9
+ @import './loading-screen';
10
+ @import './pagination';
11
+ @import './filter-popup';
12
+ @import './uploader';
13
13
 
14
14
  .layout {
15
15
  display: flex;
@@ -143,7 +143,9 @@ $datagrid-height: calc(100vh - ($header-height + $footer-height));
143
143
  color: #666;
144
144
  text-decoration: none;
145
145
  transition: color 0.2s ease;
146
-
146
+ &.util-cell-link-remove {
147
+ cursor: pointer;
148
+ }
147
149
  &:hover {
148
150
  color: #333;
149
151
  }
@@ -0,0 +1,86 @@
1
+ .uploader {
2
+ &-container {
3
+ padding: 1.5rem;
4
+ border: 2px dashed var(--border-color, #e0e0e0);
5
+ border-radius: 8px;
6
+ background-color: var(--bg-color, #fafafa);
7
+ transition: all 0.3s ease;
8
+
9
+ &:hover {
10
+ border-color: var(--primary-color, #2196f3);
11
+ background-color: var(--bg-hover-color, #f5f5f5);
12
+ }
13
+ }
14
+
15
+ &-button {
16
+ background-color: var(--primary-color, #2196f3);
17
+ color: var(--button-text-color, white);
18
+ padding: 0.75rem 1.5rem;
19
+ border: none;
20
+ border-radius: 4px;
21
+ font-weight: 500;
22
+ cursor: pointer;
23
+ transition: background-color 0.3s ease;
24
+
25
+ &:hover {
26
+ background-color: var(--primary-hover-color, #1976d2);
27
+ }
28
+
29
+ &:active {
30
+ background-color: var(--primary-active-color, #1565c0);
31
+ }
32
+ }
33
+
34
+ &-files {
35
+ margin-top: 1.5rem;
36
+ display: grid;
37
+ gap: 1rem;
38
+ }
39
+
40
+ &-file {
41
+ background-color: var(--file-bg-color, white);
42
+ padding: 1rem;
43
+ border-radius: 6px;
44
+ box-shadow: 0 2px 4px var(--shadow-color, rgba(0, 0, 0, 0.05));
45
+ display: grid;
46
+ gap: 0.5rem;
47
+
48
+ p {
49
+ margin: 0;
50
+ color: var(--text-color, #424242);
51
+ font-size: 0.875rem;
52
+
53
+ &:first-child {
54
+ color: var(--primary-color, #1976d2);
55
+ font-weight: 500;
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ :root {
62
+ --border-color: #424242;
63
+ --bg-color: #1e1e1e;
64
+ --bg-hover-color: #2d2d2d;
65
+ --primary-color: #64b5f6;
66
+ --primary-hover-color: #42a5f5;
67
+ --primary-active-color: #2196f3;
68
+ --button-text-color: #ffffff;
69
+ --file-bg-color: #2d2d2d;
70
+ --shadow-color: rgba(0, 0, 0, 0.2);
71
+ --text-color: #e0e0e0;
72
+ }
73
+
74
+ // Dark theme
75
+ [data-theme='dark'] {
76
+ --border-color: #424242;
77
+ --bg-color: #1e1e1e;
78
+ --bg-hover-color: #2d2d2d;
79
+ --primary-color: #64b5f6;
80
+ --primary-hover-color: #42a5f5;
81
+ --primary-active-color: #2196f3;
82
+ --button-text-color: #ffffff;
83
+ --file-bg-color: #2d2d2d;
84
+ --shadow-color: rgba(0, 0, 0, 0.2);
85
+ --text-color: #e0e0e0;
86
+ }
@@ -1 +1,3 @@
1
- export type AnyClass = abstract new (...args: any[]) => any;
1
+ //export type AnyClass = abstract new (...args: any[]) => any;
2
+ //TODO2: ANY IS NOT A GOOD SOLUTION, WE NEED TO FIND A BETTER WAY TO DO THIS
3
+ export type AnyClass = any;