proje-react-panel 1.3.3 → 1.4.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.
@@ -4,18 +4,21 @@ import { ErrorComponent } from './ErrorComponent';
4
4
  import { AnyClass, AnyClassConstructor } from '../types/AnyClass';
5
5
  import { getDetailsPageMeta } from '../decorators/details/getDetailsPageMeta';
6
6
  import { LoadingScreen } from './LoadingScreen';
7
+ import { useAppStore } from '../store/store';
7
8
 
8
9
  interface DetailsPageProps<T extends AnyClass> {
9
10
  model: AnyClassConstructor<T>;
11
+ CustomHeader?: ({ data }: { data: T | null }) => React.ReactNode;
10
12
  }
11
13
 
12
- export function DetailsPage<T extends AnyClass>({ model }: DetailsPageProps<T>) {
14
+ export function DetailsPage<T extends AnyClass>({ model, CustomHeader }: DetailsPageProps<T>) {
13
15
  const id = useId();
14
16
  const { class: detailsClass, items } = useMemo(() => getDetailsPageMeta(model), [model]);
15
17
  const params = useParams();
16
18
  const [data, setData] = useState<T | null>(null);
17
19
  const [error, setError] = useState(null);
18
20
  const [loading, setLoading] = useState(true);
21
+ const allDetailsData = useAppStore(state => state.detailsData);
19
22
 
20
23
  useEffect(() => {
21
24
  detailsClass
@@ -29,6 +32,19 @@ export function DetailsPage<T extends AnyClass>({ model }: DetailsPageProps<T>)
29
32
  .finally(() => setLoading(false));
30
33
  }, [params, detailsClass.getDetailsData, detailsClass]);
31
34
 
35
+ useEffect(() => {
36
+ setData(data => {
37
+ if (data) {
38
+ const detailsData =
39
+ allDetailsData?.[detailsClass.key]?.[data[detailsClass.primaryId] as string] ??
40
+ ({} as Partial<T>);
41
+
42
+ return { ...data, ...detailsData };
43
+ }
44
+ return null;
45
+ });
46
+ }, [detailsClass.key, detailsClass.primaryId, allDetailsData]);
47
+
32
48
  if (error) {
33
49
  return (
34
50
  <div className="error-container">
@@ -47,6 +63,11 @@ export function DetailsPage<T extends AnyClass>({ model }: DetailsPageProps<T>)
47
63
 
48
64
  return (
49
65
  <div className="details-page">
66
+ {CustomHeader && (
67
+ <div className="details-header">
68
+ <CustomHeader data={data} />
69
+ </div>
70
+ )}
50
71
  {items.map(item => (
51
72
  <div key={item.name} className="details-item">
52
73
  <div className="item-label">{item.name}</div>
@@ -160,10 +160,13 @@ export function FormPage<T extends AnyClass>({
160
160
  defaultValues: inputs.reduce(
161
161
  (acc, input) => {
162
162
  if (input.type === 'nested') {
163
- acc[input.name] = input.nestedFields?.reduce((acc, nestedInput) => {
164
- acc[nestedInput.name] = nestedInput.defaultValue;
165
- return acc;
166
- }, {} as Record<string, unknown>);
163
+ acc[input.name] = input.nestedFields?.reduce(
164
+ (acc, nestedInput) => {
165
+ acc[nestedInput.name] = nestedInput.defaultValue;
166
+ return acc;
167
+ },
168
+ {} as Record<string, unknown>
169
+ );
167
170
  } else {
168
171
  acc[input.name] = input.defaultValue;
169
172
  }
@@ -183,10 +186,17 @@ export function FormPage<T extends AnyClass>({
183
186
  useEffect(() => {
184
187
  if (formClass.getDetailsData) {
185
188
  formClass.getDetailsData(params as Record<string, string>).then(data => {
186
- form.reset(data as T);
189
+ inputs.forEach(input => {
190
+ if (input.type === 'nested') {
191
+ //TODO: examine this
192
+ form.setValue(input.name as Path<T>, data[input.name]);
193
+ } else {
194
+ form.setValue(input.name as Path<T>, data[input.name]);
195
+ }
196
+ });
187
197
  });
188
198
  }
189
- }, [params, form.reset, formClass.getDetailsData, formClass, form]);
199
+ }, [form, formClass, inputs, params]);
190
200
 
191
201
  return (
192
202
  <div className={`form-wrapper ${className ?? ''}`}>
@@ -8,6 +8,7 @@ import { ListPageMeta } from '../../decorators/list/getListPageMeta';
8
8
  import { AnyClass } from '../../types/AnyClass';
9
9
  import { CellField } from './CellField';
10
10
  import { CellConfiguration } from '../../decorators/list/Cell';
11
+ import { useAppStore } from '../../store/store';
11
12
 
12
13
  interface DatagridProps<T extends AnyClass> {
13
14
  data: T[];
@@ -21,6 +22,7 @@ export function Datagrid<T extends AnyClass>({
21
22
  onRemoveItem,
22
23
  }: DatagridProps<T>) {
23
24
  const cells = listPageMeta.cells;
25
+ const listData = useAppStore(state => state.listData[listPageMeta.class.key]);
24
26
  const listGeneralCells = data?.[0]
25
27
  ? typeof listPageMeta.class.cells === 'function'
26
28
  ? listPageMeta.class.cells?.(data[0])
@@ -50,10 +52,13 @@ export function Datagrid<T extends AnyClass>({
50
52
  ? listPageMeta.class.cells?.(item)
51
53
  : listPageMeta.class.cells
52
54
  : null;
55
+ const listDataItem = listData?.[item[listPageMeta.class.primaryId]] as
56
+ | Record<string, unknown>
57
+ | undefined;
53
58
  return (
54
59
  <tr key={index}>
55
60
  {cells.map((configuration: CellConfiguration) => {
56
- const value = item[configuration.name];
61
+ const value = listDataItem?.[configuration.name] ?? item[configuration.name];
57
62
  return (
58
63
  <CellField
59
64
  key={configuration.name}
@@ -6,9 +6,13 @@ export type GetDetailsDataFN<T> = (param: Record<string, string>) => Promise<T>;
6
6
 
7
7
  interface DetailsOptions<T extends AnyClass> {
8
8
  getDetailsData: GetDetailsDataFN<T>;
9
+ key?: string;
10
+ primaryId: keyof T;
9
11
  }
10
12
 
11
- export type DetailsConfiguration<T extends AnyClass> = DetailsOptions<T>;
13
+ export type DetailsConfiguration<T extends AnyClass> = DetailsOptions<T> & {
14
+ key: string;
15
+ };
12
16
 
13
17
  export function Details<T extends AnyClass>(options?: DetailsOptions<T>): ClassDecorator {
14
18
  return (target: object) => {
@@ -27,5 +31,6 @@ export function getDetailsConfiguration<T extends AnyClass>(
27
31
  }
28
32
  return {
29
33
  ...detailsConfiguration,
34
+ key: detailsConfiguration.key || entityClass.name,
30
35
  };
31
36
  }
@@ -35,9 +35,14 @@ export interface ListOptions<T> {
35
35
  getData: GetDataForList<T>;
36
36
  headers?: ListHeaderOptions;
37
37
  cells?: ((item: T) => ListCellOptions<T>) | ListCellOptions<T>;
38
+ primaryId: string;
39
+ key?: string;
38
40
  }
39
41
 
40
- export type ListConfiguration<T> = ListOptions<T>;
42
+ export type ListConfiguration<T> = ListOptions<T> & {
43
+ primaryId: string;
44
+ key: string;
45
+ };
41
46
 
42
47
  export function List<T>(options?: ListOptions<T> | ((item: T) => ListOptions<T>)): ClassDecorator {
43
48
  return (target: object) => {
@@ -50,11 +55,13 @@ export function List<T>(options?: ListOptions<T> | ((item: T) => ListOptions<T>)
50
55
  export function getListConfiguration<T extends AnyClass>(
51
56
  entityClass: AnyClassConstructor<T>
52
57
  ): ListConfiguration<T> {
53
- const listConfiguration = Reflect.getMetadata(LIST_METADATA_KEY, entityClass);
58
+ const listConfiguration: ListOptions<T> = Reflect.getMetadata(LIST_METADATA_KEY, entityClass);
54
59
  if (!listConfiguration) {
55
60
  throw new Error('List decerator should be used on class');
56
61
  }
57
62
  return {
58
63
  ...listConfiguration,
64
+ primaryId: listConfiguration.primaryId,
65
+ key: listConfiguration.key || entityClass.name,
59
66
  };
60
67
  }
package/src/index.ts CHANGED
@@ -36,3 +36,7 @@ export { Layout } from './components/layout';
36
36
  export { Login } from './components/Login';
37
37
  export { login } from './utils/login';
38
38
  export { logout } from './utils/logout';
39
+
40
+ //SERVICES
41
+ export { updateDetailsData } from './services/DataService';
42
+ export { updateListData } from './services/DataService';
@@ -0,0 +1,34 @@
1
+ import { getDetailsPageMeta } from '../decorators/details/getDetailsPageMeta';
2
+ import { AnyClass, AnyClassConstructor } from '../types/AnyClass';
3
+ import { useAppStore } from '../store/store';
4
+ import { getListPageMeta } from '../decorators/list/getListPageMeta';
5
+
6
+ export function updateDetailsData<T extends AnyClass>(
7
+ model: AnyClassConstructor<T>,
8
+ data: Partial<T>
9
+ ) {
10
+ const { class: detailsClass } = getDetailsPageMeta(model);
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`);
16
+ }
17
+
18
+ useAppStore.getState().updateDetailsData(key, data[id]?.toString(), data);
19
+ }
20
+
21
+ export function updateListData<T extends AnyClass>(
22
+ model: AnyClassConstructor<T>,
23
+ data: Partial<T>
24
+ ) {
25
+ const { class: listClass } = getListPageMeta(model);
26
+ 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`);
31
+ }
32
+
33
+ useAppStore.getState().updateListData(key, data[id]?.toString(), data);
34
+ }
@@ -4,6 +4,10 @@ import { shallow } from 'zustand/vanilla/shallow';
4
4
  import { User } from '../types/User';
5
5
 
6
6
  interface AppState {
7
+ detailsData: Record<string, Record<string, unknown>>;
8
+ updateDetailsData: (key: string, id: string, data: unknown) => void;
9
+ listData: Record<string, Record<string, unknown>>;
10
+ updateListData: (key: string, id: string, data: unknown) => void;
7
11
  user: User | null;
8
12
  login: (user: User) => void;
9
13
  logout: () => void;
@@ -11,16 +15,30 @@ interface AppState {
11
15
 
12
16
  export const useAppStore = createWithEqualityFn<AppState>()(
13
17
  persist(
14
- set => ({
18
+ (set, get) => ({
15
19
  user: null,
16
20
  login: (user: User) => set({ user }),
17
21
  logout: () => set({ user: null }),
22
+ detailsData: {},
23
+ updateDetailsData: (key: string, id: string, data: unknown) =>
24
+ set({
25
+ detailsData: {
26
+ ...get().detailsData,
27
+ [key]: { ...get().detailsData[key], [id]: data },
28
+ },
29
+ }),
30
+ listData: {},
31
+ updateListData: (key: string, id: string, data: unknown) =>
32
+ set({
33
+ listData: { ...get().listData, [key]: { ...get().listData[key], [id]: data } },
34
+ }),
18
35
  }),
19
36
  {
20
37
  name: 'app-store-1',
21
38
  storage: createJSONStorage(() => localStorage),
22
39
  partialize: state => ({
23
40
  user: state.user,
41
+ detailsData: {},
24
42
  }),
25
43
  }
26
44
  ),
@@ -12,6 +12,10 @@
12
12
  max-width: 1200px;
13
13
  margin: 0 auto;
14
14
 
15
+ .details-header {
16
+ margin-bottom: 2rem;
17
+ }
18
+
15
19
  .details-item {
16
20
  background: var(--background-color);
17
21
  border-radius: 8px;