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.
- package/dist/components/DetailsPage.d.ts +4 -1
- package/dist/decorators/details/Details.d.ts +5 -1
- package/dist/decorators/list/List.d.ts +6 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +1 -1
- package/dist/services/DataService.d.ts +3 -0
- package/dist/store/store.d.ts +4 -0
- package/package.json +1 -1
- package/src/components/DetailsPage.tsx +22 -1
- package/src/components/form/FormPage.tsx +16 -6
- package/src/components/list/Datagrid.tsx +6 -1
- package/src/decorators/details/Details.ts +6 -1
- package/src/decorators/list/List.ts +9 -2
- package/src/index.ts +4 -0
- package/src/services/DataService.ts +34 -0
- package/src/store/store.ts +19 -1
- package/src/styles/details.scss +4 -0
@@ -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(
|
164
|
-
acc
|
165
|
-
|
166
|
-
|
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
|
-
|
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
|
-
}, [
|
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
|
+
}
|
package/src/store/store.ts
CHANGED
@@ -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
|
),
|