proje-react-panel 1.3.2 → 1.4.0
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/index.cjs.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +1 -1
- package/dist/services/DataService.d.ts +2 -0
- package/dist/store/store.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/DetailsPage.tsx +22 -1
- package/src/components/form/FormField.tsx +2 -2
- package/src/components/form/FormPage.tsx +20 -3
- package/src/decorators/details/Details.ts +6 -1
- package/src/index.ts +3 -0
- package/src/services/DataService.ts +18 -0
- package/src/store/store.ts +12 -1
- package/src/styles/details.scss +4 -0
- package/src/styles/form.scss +16 -0
- package/src/styles/login.scss +4 -2
@@ -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>
|
@@ -28,7 +28,7 @@ function NestedFormFields({ input, register, fieldName }: NestedFormFieldsProps)
|
|
28
28
|
//TODO: inputOptions İnputResult seperate
|
29
29
|
const data = form.getValues(fieldName);
|
30
30
|
return (
|
31
|
-
<div>
|
31
|
+
<div className="nested-form-field-inner">
|
32
32
|
{/* TODO: any is not a good solution, we need to find a better way to do this */}
|
33
33
|
{Array.isArray(data) ? (
|
34
34
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
@@ -101,7 +101,7 @@ export function FormField({ input, register, error, baseName }: FormFieldProps)
|
|
101
101
|
}, [input, register, fieldName]);
|
102
102
|
|
103
103
|
return (
|
104
|
-
<div className=
|
104
|
+
<div className={`form-field ${input.type === 'nested' ? 'nested-form-field' : ''}`}>
|
105
105
|
{input.type !== 'hidden' && input.type !== 'checkbox' && (
|
106
106
|
<Label htmlFor={fieldName} label={input.label} fieldName={fieldName} />
|
107
107
|
)}
|
@@ -159,7 +159,17 @@ export function FormPage<T extends AnyClass>({
|
|
159
159
|
resolver: resolver as Resolver<T>,
|
160
160
|
defaultValues: inputs.reduce(
|
161
161
|
(acc, input) => {
|
162
|
-
|
162
|
+
if (input.type === 'nested') {
|
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
|
+
);
|
170
|
+
} else {
|
171
|
+
acc[input.name] = input.defaultValue;
|
172
|
+
}
|
163
173
|
return acc;
|
164
174
|
},
|
165
175
|
{} as Record<string, unknown>
|
@@ -176,10 +186,17 @@ export function FormPage<T extends AnyClass>({
|
|
176
186
|
useEffect(() => {
|
177
187
|
if (formClass.getDetailsData) {
|
178
188
|
formClass.getDetailsData(params as Record<string, string>).then(data => {
|
179
|
-
|
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
|
+
});
|
180
197
|
});
|
181
198
|
}
|
182
|
-
}, [
|
199
|
+
}, [form, formClass, inputs, params]);
|
183
200
|
|
184
201
|
return (
|
185
202
|
<div className={`form-wrapper ${className ?? ''}`}>
|
@@ -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
|
}
|
package/src/index.ts
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
import { getDetailsPageMeta } from '../decorators/details/getDetailsPageMeta';
|
2
|
+
import { AnyClass, AnyClassConstructor } from '../types/AnyClass';
|
3
|
+
import { useAppStore } from '../store/store';
|
4
|
+
|
5
|
+
export function updateDetailsData<T extends AnyClass>(
|
6
|
+
model: AnyClassConstructor<T>,
|
7
|
+
data: Partial<T>
|
8
|
+
) {
|
9
|
+
const { class: detailsClass } = getDetailsPageMeta(model);
|
10
|
+
const key = detailsClass.key;
|
11
|
+
const id = detailsClass.primaryId;
|
12
|
+
console.log('updateDetailsData', model, data, detailsClass);
|
13
|
+
if (!data[id]) {
|
14
|
+
throw new Error(`Id ${id} not found in data`);
|
15
|
+
}
|
16
|
+
|
17
|
+
useAppStore.getState().updateDetailsData(key, data[id]?.toString(), data);
|
18
|
+
}
|
package/src/store/store.ts
CHANGED
@@ -4,6 +4,8 @@ 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;
|
7
9
|
user: User | null;
|
8
10
|
login: (user: User) => void;
|
9
11
|
logout: () => void;
|
@@ -11,16 +13,25 @@ interface AppState {
|
|
11
13
|
|
12
14
|
export const useAppStore = createWithEqualityFn<AppState>()(
|
13
15
|
persist(
|
14
|
-
set => ({
|
16
|
+
(set, get) => ({
|
15
17
|
user: null,
|
16
18
|
login: (user: User) => set({ user }),
|
17
19
|
logout: () => set({ user: null }),
|
20
|
+
detailsData: {},
|
21
|
+
updateDetailsData: (key: string, id: string, data: unknown) =>
|
22
|
+
set({
|
23
|
+
detailsData: {
|
24
|
+
...get().detailsData,
|
25
|
+
[key]: { ...get().detailsData[key], [id]: data },
|
26
|
+
},
|
27
|
+
}),
|
18
28
|
}),
|
19
29
|
{
|
20
30
|
name: 'app-store-1',
|
21
31
|
storage: createJSONStorage(() => localStorage),
|
22
32
|
partialize: state => ({
|
23
33
|
user: state.user,
|
34
|
+
detailsData: {},
|
24
35
|
}),
|
25
36
|
}
|
26
37
|
),
|
package/src/styles/details.scss
CHANGED
package/src/styles/form.scss
CHANGED
@@ -12,6 +12,22 @@
|
|
12
12
|
.form-field {
|
13
13
|
margin-bottom: 16px;
|
14
14
|
|
15
|
+
&.nested-form-field {
|
16
|
+
position: relative;
|
17
|
+
&:before {
|
18
|
+
display: block;
|
19
|
+
content: '';
|
20
|
+
position: absolute;
|
21
|
+
left: 12px;
|
22
|
+
top: 25px;
|
23
|
+
bottom: 4px;
|
24
|
+
width: 1px;
|
25
|
+
border-left: 1px dashed #444;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
.nested-form-field-inner {
|
29
|
+
margin-left: 42px;
|
30
|
+
}
|
15
31
|
input[type='text'],
|
16
32
|
input[type='password'],
|
17
33
|
input[type='email'],
|
package/src/styles/login.scss
CHANGED
@@ -3,9 +3,11 @@
|
|
3
3
|
justify-content: center;
|
4
4
|
align-items: center;
|
5
5
|
min-height: 100vh;
|
6
|
+
max-width: 100vw;
|
7
|
+
max-height: 100vh;
|
6
8
|
background: linear-gradient(135deg, #1a1a1a 0%, #2d3436 100%);
|
7
|
-
|
8
|
-
|
9
|
+
padding: 0;
|
10
|
+
form {
|
9
11
|
width: 400px;
|
10
12
|
background: #1a1a1a;
|
11
13
|
border-radius: 8px;
|