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.
@@ -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="form-field">
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
- acc[input.name] = input.defaultValue;
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
- 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
+ });
180
197
  });
181
198
  }
182
- }, [params, form.reset, formClass.getDetailsData, formClass, form]);
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
@@ -36,3 +36,6 @@ 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';
@@ -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
+ }
@@ -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
  ),
@@ -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;
@@ -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'],
@@ -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
- form {
9
+ padding: 0;
10
+ form {
9
11
  width: 400px;
10
12
  background: #1a1a1a;
11
13
  border-radius: 8px;