* Add FormerButtonArea component for action buttons * Introduce FormerLayoutTop and FormerLayoutBottom for structured layout * Update Former types to include new properties * Implement dynamic ID generation for forms * Refactor example to demonstrate new layout features * Mark tasks as completed in todo.md
192 lines
5.4 KiB
TypeScript
192 lines
5.4 KiB
TypeScript
import { createSyncStore } from '@warkypublic/zustandsyncstore';
|
|
import { produce } from 'immer';
|
|
|
|
import type { FormerProps, FormerState } from './Former.types';
|
|
import { newUUID } from '@warkypublic/artemis-kit';
|
|
|
|
const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
|
FormerState<any> & Partial<FormerProps<any>>,
|
|
FormerProps<any>
|
|
>(
|
|
(set, get) => ({
|
|
getState: (key) => {
|
|
const current = get();
|
|
return current?.[key];
|
|
},
|
|
load: async (reset?: boolean) => {
|
|
try {
|
|
set({ loading: true });
|
|
const keyName = get()?.apiKeyField || 'id';
|
|
const keyValue = (get().values as any)?.[keyName] ?? (get().primeData as any)?.[keyName];
|
|
if (get().onAPICall && keyValue !== undefined) {
|
|
let data = await get().onAPICall!(
|
|
'read',
|
|
get().request || 'insert',
|
|
get().values,
|
|
keyValue
|
|
);
|
|
if (get().afterGet) {
|
|
data = await get().afterGet!({ ...data });
|
|
}
|
|
set({ loading: false, values: data });
|
|
get().onChange?.(data);
|
|
}
|
|
if (reset && get().getFormMethods) {
|
|
const formMethods = get().getFormMethods!();
|
|
formMethods.reset();
|
|
}
|
|
} catch (e) {
|
|
set({ error: (e as Error)?.message ?? e, loading: false });
|
|
}
|
|
set({ loading: false });
|
|
},
|
|
|
|
onChange: (values) => {
|
|
set({ values });
|
|
},
|
|
request: 'insert',
|
|
reset: async () => {
|
|
const state = get();
|
|
if (state.getFormMethods) {
|
|
if (state.request !== 'insert') {
|
|
await state.load(true);
|
|
}
|
|
|
|
const formMethods = state.getFormMethods!();
|
|
formMethods.reset({ ...state.values, ...state.primeData });
|
|
}
|
|
},
|
|
save: async (e?: React.BaseSyntheticEvent<object, any, any> | undefined) => {
|
|
try {
|
|
const keepOpen = get().keepOpen ?? false;
|
|
set({ loading: true });
|
|
if (get().getFormMethods) {
|
|
const formMethods = get().getFormMethods!();
|
|
|
|
let data = formMethods.getValues();
|
|
|
|
if (get().beforeSave) {
|
|
const newData = await get().beforeSave!(data);
|
|
data = newData;
|
|
}
|
|
|
|
let exit = false;
|
|
const handler = formMethods.handleSubmit(
|
|
(newdata) => {
|
|
data = newdata;
|
|
},
|
|
(errors) => {
|
|
set({ error: errors.root?.message || 'Validation errors', loading: false });
|
|
exit = true;
|
|
}
|
|
);
|
|
|
|
await handler(e);
|
|
|
|
//console.log('Former.store.tsx save called', success, e, data, get().getFormMethods);
|
|
if (exit) {
|
|
set({ loading: false });
|
|
return undefined;
|
|
}
|
|
|
|
if (get().request === 'delete' && !get().deleteConfirmed) {
|
|
const confirmed = (await get().onConfirmDelete?.(data)) ?? false;
|
|
if (!confirmed) {
|
|
set({ loading: false });
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
if (get().onAPICall) {
|
|
const keyName = get()?.apiKeyField || 'id';
|
|
const keyValue =
|
|
(get().values as any)?.[keyName] ?? (get().primeData as any)?.[keyName];
|
|
const savedData = await get().onAPICall!(
|
|
'mutate',
|
|
get().request || 'insert',
|
|
data,
|
|
keyValue
|
|
);
|
|
if (get().afterSave) {
|
|
await get().afterSave!(savedData);
|
|
}
|
|
set({ loading: false, values: savedData });
|
|
get().onChange?.(savedData);
|
|
if (!keepOpen) {
|
|
get().onClose?.(savedData);
|
|
}
|
|
return savedData;
|
|
}
|
|
|
|
set({ loading: false, values: data });
|
|
get().onChange?.(data);
|
|
if (!keepOpen) {
|
|
get().onClose?.(data);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
} catch (e) {
|
|
set({ error: (e as Error)?.message ?? e, loading: false });
|
|
}
|
|
|
|
return undefined;
|
|
},
|
|
setRequest: (request) => {
|
|
set({ request });
|
|
},
|
|
setState: (key, value) => {
|
|
set(
|
|
produce((state) => {
|
|
state[key] = value;
|
|
})
|
|
);
|
|
},
|
|
setStateFN: (key, value) => {
|
|
const p = new Promise<void>((resolve, reject) => {
|
|
set(
|
|
produce((state) => {
|
|
if (typeof value === 'function') {
|
|
state[key] = (value as (value: unknown) => unknown)(state[key]);
|
|
} else {
|
|
reject(new Error(`Not a function ${value}`));
|
|
throw Error(`Not a function ${value}`);
|
|
}
|
|
})
|
|
);
|
|
resolve();
|
|
});
|
|
|
|
return p;
|
|
},
|
|
validate: async () => {
|
|
if (get().getFormMethods) {
|
|
const formMethods = get().getFormMethods!();
|
|
const isValid = await formMethods.trigger();
|
|
return isValid;
|
|
}
|
|
return true;
|
|
},
|
|
values: undefined,
|
|
}),
|
|
({ onConfirmDelete, primeData, request, values, id }) => {
|
|
let _onConfirmDelete = onConfirmDelete;
|
|
if (!onConfirmDelete) {
|
|
_onConfirmDelete = async () => {
|
|
return confirm('Are you sure you want to delete this item?');
|
|
};
|
|
}
|
|
|
|
return {
|
|
onConfirmDelete: _onConfirmDelete,
|
|
primeData,
|
|
request: request || 'insert',
|
|
values: { ...primeData, ...values },
|
|
id: !id ? newUUID() : id,
|
|
};
|
|
}
|
|
);
|
|
|
|
export { FormerProvider };
|
|
export { useFormerStore };
|