proje-react-panel 1.4.1 → 1.6.0-test-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 (54) hide show
  1. package/.cursor/rules.md +11 -1
  2. package/.vscode/launch.json +9 -0
  3. package/AUTH_LAYOUT_EXAMPLE.md +343 -0
  4. package/AUTH_LAYOUT_GUIDE.md +819 -0
  5. package/IMPLEMENTATION_GUIDE.md +899 -0
  6. package/dist/api/ApiConfig.d.ts +11 -0
  7. package/dist/api/AuthApi.d.ts +2 -5
  8. package/dist/api/CrudApi.d.ts +11 -12
  9. package/dist/components/list/CellField.d.ts +2 -2
  10. package/dist/components/list/cells/BooleanCell.d.ts +5 -2
  11. package/dist/components/list/cells/DateCell.d.ts +5 -2
  12. package/dist/components/list/cells/DefaultCell.d.ts +3 -2
  13. package/dist/components/list/cells/DownloadCell.d.ts +3 -2
  14. package/dist/components/list/cells/ImageCell.d.ts +3 -2
  15. package/dist/components/list/cells/LinkCell.d.ts +8 -0
  16. package/dist/components/list/cells/UUIDCell.d.ts +5 -2
  17. package/dist/decorators/auth/DefaultLoginForm.d.ts +4 -0
  18. package/dist/decorators/details/Details.d.ts +1 -1
  19. package/dist/decorators/list/Cell.d.ts +5 -1
  20. package/dist/decorators/list/List.d.ts +8 -4
  21. package/dist/decorators/list/cells/LinkCell.d.ts +13 -0
  22. package/dist/index.cjs.js +15 -1
  23. package/dist/index.d.ts +7 -1
  24. package/dist/index.esm.js +15 -1
  25. package/dist/types/Login.d.ts +8 -0
  26. package/package.json +3 -1
  27. package/src/api/ApiConfig.ts +63 -0
  28. package/src/api/AuthApi.ts +8 -0
  29. package/src/api/CrudApi.ts +96 -60
  30. package/src/assets/icons/svg/down-arrow-backup-2.svg +3 -0
  31. package/src/components/DetailsPage.tsx +5 -1
  32. package/src/components/list/CellField.tsx +25 -10
  33. package/src/components/list/Datagrid.tsx +83 -53
  34. package/src/components/list/ListPage.tsx +3 -0
  35. package/src/components/list/cells/BooleanCell.tsx +7 -2
  36. package/src/components/list/cells/DateCell.tsx +6 -2
  37. package/src/components/list/cells/DefaultCell.tsx +4 -4
  38. package/src/components/list/cells/DownloadCell.tsx +4 -2
  39. package/src/components/list/cells/ImageCell.tsx +5 -2
  40. package/src/components/list/cells/LinkCell.tsx +31 -0
  41. package/src/components/list/cells/UUIDCell.tsx +6 -2
  42. package/src/decorators/auth/DefaultLoginForm.ts +32 -0
  43. package/src/decorators/details/Details.ts +1 -1
  44. package/src/decorators/list/Cell.ts +5 -1
  45. package/src/decorators/list/List.ts +4 -4
  46. package/src/decorators/list/cells/LinkCell.ts +22 -0
  47. package/src/index.ts +27 -1
  48. package/src/services/DataService.ts +14 -10
  49. package/src/store/store.ts +1 -1
  50. package/src/styles/components/button.scss +14 -0
  51. package/src/styles/index.scss +1 -1
  52. package/src/styles/list.scss +64 -1
  53. package/src/types/Login.ts +9 -0
  54. package/src/utils/logout.ts +2 -0
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import { CellConfiguration } from '../../../decorators/list/Cell';
3
+ import { LinkCellConfiguration } from '../../../decorators/list/cells/LinkCell';
4
+ import { Link } from 'react-router';
5
+
6
+ interface LinkCellProps<T> {
7
+ item: T;
8
+ configuration: CellConfiguration;
9
+ }
10
+
11
+ export function LinkCell<T>({ item, configuration }: LinkCellProps<T>) {
12
+ const linkConfiguration = configuration as LinkCellConfiguration<T>;
13
+ const value = item[configuration.name as keyof T] ?? 'Link';
14
+
15
+ return (
16
+ <Link to={linkConfiguration.path ?? linkConfiguration.url ?? ''}>
17
+ {linkConfiguration.onClick ? (
18
+ <a
19
+ className="util-cell-link"
20
+ onClick={() => {
21
+ linkConfiguration.onClick?.(item as T);
22
+ }}
23
+ >
24
+ {value?.toString()}
25
+ </a>
26
+ ) : (
27
+ value?.toString() || linkConfiguration.placeHolder
28
+ )}
29
+ </Link>
30
+ );
31
+ }
@@ -1,10 +1,14 @@
1
1
  import React from 'react';
2
+ import { AnyClass } from '../../../types/AnyClass';
3
+ import { CellConfiguration } from '../../../decorators/list/Cell';
2
4
 
3
5
  interface UUIDCellProps {
4
- value: string;
6
+ item: AnyClass;
7
+ configuration: CellConfiguration;
5
8
  }
6
9
 
7
- export function UUIDCell({ value }: UUIDCellProps) {
10
+ export function UUIDCell({ item, configuration }: UUIDCellProps) {
11
+ const value = item[configuration.name];
8
12
  if (!value || typeof value !== 'string' || value.length < 6) return <>-</>;
9
13
 
10
14
  return <>{`${value.slice(0, 3)}...${value.slice(-3)}`}</>;
@@ -0,0 +1,32 @@
1
+ import { MinLength } from 'class-validator';
2
+ import { Form } from '../form/Form';
3
+ import { Input } from '../form/Input';
4
+ import { LoginForm as LoginFormInterface, LoginResponse } from '../../types/Login';
5
+ import { setAuthToken } from '../../api/ApiConfig';
6
+ import { login } from '../../utils/login';
7
+ import { User } from '../../types/User';
8
+ import { authLogin } from '../..';
9
+
10
+ @Form<LoginFormInterface, LoginResponse<User>>({
11
+ onSubmit: authLogin,
12
+ onSubmitSuccess: (data: LoginResponse<User>) => {
13
+ setAuthToken(data.access_token);
14
+ login(data.admin as User, data.access_token, () => {
15
+ window.location.href = '/';
16
+ });
17
+ },
18
+ type: 'formData',
19
+ })
20
+ export class DefaultLoginForm {
21
+ @MinLength(3)
22
+ @Input({
23
+ label: 'Username',
24
+ })
25
+ username: string;
26
+
27
+ @Input({
28
+ label: 'Password',
29
+ inputType: 'password',
30
+ })
31
+ password: string;
32
+ }
@@ -7,7 +7,7 @@ export type GetDetailsDataFN<T> = (param: Record<string, string>) => Promise<T>;
7
7
  interface DetailsOptions<T extends AnyClass> {
8
8
  getDetailsData: GetDetailsDataFN<T>;
9
9
  key?: string;
10
- primaryId: keyof T;
10
+ primaryId?: keyof T;
11
11
  }
12
12
 
13
13
  export type DetailsConfiguration<T extends AnyClass> = DetailsOptions<T> & {
@@ -14,7 +14,7 @@ export interface StaticSelectFilter extends Filter {
14
14
  }
15
15
 
16
16
  export type CellTypes = 'string' | 'date' | 'number' | 'boolean' | 'uuid';
17
- export type ExtendedCellTypes = CellTypes | 'image' | 'download';
17
+ export type ExtendedCellTypes = CellTypes | 'image' | 'download' | 'link';
18
18
 
19
19
  export interface CellOptions {
20
20
  name?: string;
@@ -22,6 +22,10 @@ export interface CellOptions {
22
22
  type?: CellTypes;
23
23
  placeHolder?: string;
24
24
  filter?: Filter | StaticSelectFilter;
25
+ style?: {
26
+ minWidth?: string;
27
+ width?: string;
28
+ };
25
29
  }
26
30
 
27
31
  export interface CellConfiguration extends Omit<CellOptions, 'type'> {
@@ -25,7 +25,8 @@ export interface ListHeaderOptions {
25
25
  create?: { path: string; label: string };
26
26
  }
27
27
 
28
- export interface ListCellOptions<T> {
28
+ export interface ListActionOptions<T> {
29
+ customActions?: { label: string; onClick: (item: T) => void; icon?: string }[];
29
30
  details?: { path: string; label: string };
30
31
  edit?: { path: string; label: string };
31
32
  delete?: { label: string; onRemoveItem?: (item: T) => Promise<void> };
@@ -34,13 +35,12 @@ export interface ListCellOptions<T> {
34
35
  export interface ListOptions<T> {
35
36
  getData: GetDataForList<T>;
36
37
  headers?: ListHeaderOptions;
37
- cells?: ((item: T) => ListCellOptions<T>) | ListCellOptions<T>;
38
- primaryId: string;
38
+ actions?: ((item: T) => ListActionOptions<T>) | ListActionOptions<T>;
39
+ primaryId?: string;
39
40
  key?: string;
40
41
  }
41
42
 
42
43
  export type ListConfiguration<T> = ListOptions<T> & {
43
- primaryId: string;
44
44
  key: string;
45
45
  };
46
46
 
@@ -0,0 +1,22 @@
1
+ import { CellConfiguration, CellOptions } from '../Cell';
2
+ import { ExtendedCell } from '../ExtendedCell';
3
+
4
+ export interface LinkCellOptions<T> extends Omit<CellOptions, 'type'> {
5
+ url?: string;
6
+ path?: string;
7
+ onClick?: (data: T) => void;
8
+ }
9
+
10
+ export interface LinkCellConfiguration<T> extends CellConfiguration {
11
+ type: 'link';
12
+ url?: string;
13
+ path?: string;
14
+ onClick?: (data: T) => void;
15
+ }
16
+
17
+ export function LinkCell<T>(options?: LinkCellOptions<T>): PropertyDecorator {
18
+ return ExtendedCell(options, (_, options) => ({
19
+ ...options,
20
+ type: 'link',
21
+ }));
22
+ }
package/src/index.ts CHANGED
@@ -13,13 +13,14 @@ export {
13
13
  } from './decorators/list/List';
14
14
  export { ImageCell } from './decorators/list/cells/ImageCell';
15
15
  export { Cell } from './decorators/list/Cell';
16
+ export { DownloadCell } from './decorators/list/cells/DownloadCell';
17
+ export { LinkCell } from './decorators/list/cells/LinkCell';
16
18
 
17
19
  //FORM
18
20
  export { FormPage } from './components/form/FormPage';
19
21
  export { Form, type OnSubmitFN } from './decorators/form/Form';
20
22
  export { Input } from './decorators/form/Input';
21
23
  export { SelectInput } from './decorators/form/inputs/SelectInput';
22
- export { DownloadCell } from './decorators/list/cells/DownloadCell';
23
24
  //for nested form fields
24
25
  export { getInputFields } from './decorators/form/Input';
25
26
 
@@ -36,6 +37,31 @@ export { Layout } from './components/layout';
36
37
  export { Login } from './components/Login';
37
38
  export { login } from './utils/login';
38
39
  export { logout } from './utils/logout';
40
+ export { DefaultLoginForm } from './decorators/auth/DefaultLoginForm';
41
+
42
+ //API
43
+ export {
44
+ initApi,
45
+ initAuthToken,
46
+ setAuthToken,
47
+ setAuthLogout,
48
+ getAxiosInstance,
49
+ axiosInstance,
50
+ } from './api/ApiConfig';
51
+ export {
52
+ getAll,
53
+ getOne,
54
+ create,
55
+ createFormData,
56
+ update,
57
+ updateFormData,
58
+ updateSimple,
59
+ remove,
60
+ } from './api/CrudApi';
61
+ export { login as authLogin } from './api/AuthApi';
62
+
63
+ //TYPES
64
+ export type { LoginForm, LoginResponse } from './types/Login';
39
65
 
40
66
  //SERVICES
41
67
  export { updateDetailsData } from './services/DataService';
@@ -9,13 +9,15 @@ export function updateDetailsData<T extends AnyClass>(
9
9
  ) {
10
10
  const { class: detailsClass } = getDetailsPageMeta(model);
11
11
  const key = detailsClass.key;
12
- const id = detailsClass.primaryId;
13
- console.log('updateDetailsData', model, data, detailsClass);
14
- if (!data[id]) {
15
- throw new Error(`Id ${id} not found in data`);
12
+ if (!detailsClass.primaryId) {
13
+ throw new Error('Primary id is required to use this utility function');
16
14
  }
17
15
 
18
- useAppStore.getState().updateDetailsData(key, data[id]?.toString(), data);
16
+ if (!data[detailsClass.primaryId]) {
17
+ throw new Error(`Id ${detailsClass.primaryId} not found in data`);
18
+ }
19
+
20
+ useAppStore.getState().updateDetailsData(key, data[detailsClass.primaryId]?.toString(), data);
19
21
  }
20
22
 
21
23
  export function updateListData<T extends AnyClass>(
@@ -24,11 +26,13 @@ export function updateListData<T extends AnyClass>(
24
26
  ) {
25
27
  const { class: listClass } = getListPageMeta(model);
26
28
  const key = listClass.key;
27
- const id = listClass.primaryId;
28
- console.log('updateListData', model, data, listClass);
29
- if (!data[id]) {
30
- throw new Error(`Id ${id} not found in data`);
29
+ if (!listClass.primaryId) {
30
+ throw new Error('Primary id is required to use this utility function');
31
+ }
32
+
33
+ if (!data[listClass.primaryId]) {
34
+ throw new Error(`Id ${listClass.primaryId} not found in data`);
31
35
  }
32
36
 
33
- useAppStore.getState().updateListData(key, data[id]?.toString(), data);
37
+ useAppStore.getState().updateListData(key, data[listClass.primaryId]?.toString(), data);
34
38
  }
@@ -34,7 +34,7 @@ export const useAppStore = createWithEqualityFn<AppState>()(
34
34
  }),
35
35
  }),
36
36
  {
37
- name: 'app-store-1',
37
+ name: 'proje-panel-store',
38
38
  storage: createJSONStorage(() => localStorage),
39
39
  partialize: state => ({
40
40
  user: state.user,
@@ -0,0 +1,14 @@
1
+ .panel-button {
2
+ padding: 0.5rem 1rem;
3
+ border: 1px solid #444444;
4
+ border-radius: 4px;
5
+ background-color: cornflowerblue;
6
+ color: #ffffff;
7
+ font-size: 0.875rem;
8
+ font-weight: 500;
9
+ cursor: pointer;
10
+ transition: all 0.2s ease;
11
+ &:hover {
12
+ transform: scale(1.01);
13
+ }
14
+ }
@@ -11,8 +11,8 @@
11
11
  @import './components/uploader.scss';
12
12
  @import './components/checkbox';
13
13
  @import './components/form-header';
14
+ @import './components/button';
14
15
  @import './details';
15
-
16
16
  //TODO: import deprecated
17
17
  .layout {
18
18
  display: flex;
@@ -64,10 +64,16 @@ $datagrid-height: calc(100vh - #{$header-height} - #{$footer-height});
64
64
  align-items: center;
65
65
  height: $footer-height;
66
66
  font-size: 24px;
67
+ padding-right: 12px;
67
68
  font-weight: bold;
68
69
  text-align: center;
69
70
  color: #ffffff;
70
71
  }
72
+ .list-footer-total {
73
+ font-size: 12px;
74
+ font-weight: 500;
75
+ color: #ffffff;
76
+ }
71
77
  .datagrid {
72
78
  padding: 0 0 0 8px;
73
79
  height: $datagrid-height;
@@ -80,14 +86,18 @@ $datagrid-height: calc(100vh - #{$header-height} - #{$footer-height});
80
86
  }
81
87
 
82
88
  .datagrid-table {
83
- width: 100%;
89
+ min-width: 100%;
90
+ table-layout: fixed;
84
91
  border-collapse: collapse;
92
+ position: relative;
85
93
 
86
94
  th,
87
95
  td {
88
96
  padding: 12px 16px;
89
97
  text-align: left;
90
98
  border-bottom: 1px solid #444444;
99
+ text-overflow: ellipsis;
100
+ white-space: nowrap;
91
101
  }
92
102
 
93
103
  th {
@@ -158,6 +168,11 @@ $datagrid-height: calc(100vh - #{$header-height} - #{$footer-height});
158
168
  fill: #ff0000;
159
169
  stroke: none;
160
170
  }
171
+ &.icon-down {
172
+ fill: none;
173
+ stroke: currentColor;
174
+ stroke-width: 2;
175
+ }
161
176
  }
162
177
 
163
178
  .util-cell-label {
@@ -209,3 +224,51 @@ $datagrid-height: calc(100vh - #{$header-height} - #{$footer-height});
209
224
  transition: background-color 0.2s ease;
210
225
  }
211
226
  }
227
+ .util-cell-actions {
228
+ display: flex;
229
+ flex-direction: column;
230
+ gap: 8px;
231
+ position: relative;
232
+ &:hover {
233
+ .util-cell-actions-list {
234
+ display: flex;
235
+ }
236
+ }
237
+ }
238
+ .util-cell-actions-label {
239
+ font-size: 14px;
240
+ font-weight: 500;
241
+ cursor: pointer;
242
+ white-space: nowrap;
243
+ text-decoration: underline;
244
+ }
245
+ .util-cell-actions-list {
246
+ position: absolute;
247
+ top: 100%;
248
+ right: 0;
249
+ display: none;
250
+ flex-direction: column;
251
+ list-style: none;
252
+ margin: 0;
253
+ padding: 0;
254
+ background-color: #2b2b2b;
255
+ border: 1px solid #444444;
256
+ border-radius: 4px;
257
+ z-index: 1000;
258
+ > li {
259
+ display: flex;
260
+ align-items: center;
261
+ padding: 12px 16px;
262
+ border-bottom: 1px solid #444444;
263
+ cursor: pointer;
264
+ &:hover {
265
+ background-color: #444444;
266
+ }
267
+ &:last-child {
268
+ border-bottom: none;
269
+ }
270
+ }
271
+ }
272
+ .util-cell-link {
273
+ cursor: pointer;
274
+ }
@@ -0,0 +1,9 @@
1
+ export interface LoginResponse<T> {
2
+ access_token: string;
3
+ admin: T;
4
+ }
5
+
6
+ export interface LoginForm {
7
+ username: string;
8
+ password: string;
9
+ }
@@ -1,6 +1,8 @@
1
+ import { setAuthLogout } from '../api/ApiConfig';
1
2
  import { useAppStore } from '../store/store';
2
3
 
3
4
  export function logout(navigate: () => void) {
5
+ setAuthLogout();
4
6
  localStorage.removeItem('token');
5
7
  useAppStore.getState().logout();
6
8
  navigate();