@ramathibodi/nuxt-commons 4.0.1 → 4.0.3
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/README.md +35 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/BarcodeReader.vue +3 -2
- package/dist/runtime/components/form/ActionPad.d.vue.ts +2 -2
- package/dist/runtime/components/form/ActionPad.vue.d.ts +2 -2
- package/dist/runtime/components/form/DateTime.vue +7 -1
- package/dist/runtime/components/form/Dialog.d.vue.ts +2 -2
- package/dist/runtime/components/form/Dialog.vue.d.ts +2 -2
- package/dist/runtime/components/form/EditPad.d.vue.ts +2 -2
- package/dist/runtime/components/form/EditPad.vue.d.ts +2 -2
- package/dist/runtime/components/form/System.vue +1 -1
- package/dist/runtime/components/master/Select.d.vue.ts +2 -2
- package/dist/runtime/components/master/Select.vue +3 -3
- package/dist/runtime/components/master/Select.vue.d.ts +2 -2
- package/dist/runtime/components/model/Autocomplete.d.vue.ts +6 -1
- package/dist/runtime/components/model/Autocomplete.vue +8 -6
- package/dist/runtime/components/model/Autocomplete.vue.d.ts +6 -1
- package/dist/runtime/components/model/Combobox.d.vue.ts +6 -1
- package/dist/runtime/components/model/Combobox.vue +8 -6
- package/dist/runtime/components/model/Combobox.vue.d.ts +6 -1
- package/dist/runtime/components/model/Pad.d.vue.ts +5 -2
- package/dist/runtime/components/model/Pad.vue +3 -1
- package/dist/runtime/components/model/Pad.vue.d.ts +5 -2
- package/dist/runtime/components/model/Select.d.vue.ts +8 -3
- package/dist/runtime/components/model/Select.vue +11 -16
- package/dist/runtime/components/model/Select.vue.d.ts +8 -3
- package/dist/runtime/components/model/Table.d.vue.ts +55 -52
- package/dist/runtime/components/model/Table.vue +3 -1
- package/dist/runtime/components/model/Table.vue.d.ts +55 -52
- package/dist/runtime/components/model/iterator.d.vue.ts +65 -62
- package/dist/runtime/components/model/iterator.vue +3 -1
- package/dist/runtime/components/model/iterator.vue.d.ts +65 -62
- package/dist/runtime/components/model/label.d.vue.ts +2 -0
- package/dist/runtime/components/model/label.vue +14 -2
- package/dist/runtime/components/model/label.vue.d.ts +2 -0
- package/dist/runtime/composables/apiLookupList.d.ts +26 -0
- package/dist/runtime/composables/apiLookupList.js +245 -0
- package/dist/runtime/composables/apiModel.d.ts +40 -0
- package/dist/runtime/composables/apiModel.js +191 -0
- package/dist/runtime/composables/apiModelItem.d.ts +16 -0
- package/dist/runtime/composables/apiModelItem.js +101 -0
- package/package.json +3 -4
- package/templates/.codegen/codegen.ts +5 -3
- /package/templates/.codegen/{plugin-schema-object.js → plugin-schema-object.cjs} +0 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { computed, ref, shallowRef, watch } from "vue";
|
|
2
|
+
import { watchDebounced } from "@vueuse/core";
|
|
3
|
+
import { union, isEmpty, isArray, sortBy, castArray } from "lodash-es";
|
|
4
|
+
import { useFuzzy } from "./utils/fuzzy.js";
|
|
5
|
+
import { useApi } from "./api.js";
|
|
6
|
+
import { buildApiEndpoint } from "./apiModel.js";
|
|
7
|
+
export function useApiLookupList(props, emit, selectedItems) {
|
|
8
|
+
const modelItems = shallowRef([]);
|
|
9
|
+
const items = shallowRef([]);
|
|
10
|
+
const selectedItemsObject = shallowRef([]);
|
|
11
|
+
const searchData = ref("");
|
|
12
|
+
const isLoading = ref(false);
|
|
13
|
+
const isErrorLoading = ref(false);
|
|
14
|
+
const statusField = computed(() => props.statusField ?? "statusCode");
|
|
15
|
+
const activeValue = computed(() => props.activeValue ?? "active");
|
|
16
|
+
const hideInactiveInList = computed(() => props.hideInactiveInList ?? false);
|
|
17
|
+
const queryFields = computed(() => {
|
|
18
|
+
let fieldsArray = [props.itemTitle, props.itemValue];
|
|
19
|
+
if (props.fields) fieldsArray = union(fieldsArray, props.fields);
|
|
20
|
+
if (hideInactiveInList.value && statusField.value) {
|
|
21
|
+
fieldsArray = union(fieldsArray, [statusField.value]);
|
|
22
|
+
}
|
|
23
|
+
return fieldsArray;
|
|
24
|
+
});
|
|
25
|
+
const computedFilterKeys = computed(() => {
|
|
26
|
+
if (props.filterKeys) return props.filterKeys;
|
|
27
|
+
return ["title", "raw." + props.itemValue];
|
|
28
|
+
});
|
|
29
|
+
const computedPlaceholder = computed(() => {
|
|
30
|
+
if (!props.serverSearch) return props.placeholder;
|
|
31
|
+
return isLoading.value ? props.serverSearchLoadingText : props.serverSearchText;
|
|
32
|
+
});
|
|
33
|
+
const computedNoDataText = computed(() => {
|
|
34
|
+
if (!props.serverSearch) return props.noDataText;
|
|
35
|
+
if (isLoading.value) return props.serverSearchLoadingText;
|
|
36
|
+
if (!searchData.value) return props.serverSearchText;
|
|
37
|
+
return props.noDataText;
|
|
38
|
+
});
|
|
39
|
+
let requestId = 0;
|
|
40
|
+
async function loadItems() {
|
|
41
|
+
if (!props.modelName) {
|
|
42
|
+
modelItems.value = [];
|
|
43
|
+
items.value = [];
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const id = ++requestId;
|
|
47
|
+
isLoading.value = true;
|
|
48
|
+
const variables = { ...props.modelBy || {} };
|
|
49
|
+
if (props.serverSearch && props.serverSearchKey) {
|
|
50
|
+
variables[props.serverSearchKey] = searchData.value;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const result = await useApi().postPromise(
|
|
54
|
+
buildApiEndpoint(props.modelName),
|
|
55
|
+
{ fields: queryFields.value, variables },
|
|
56
|
+
void 0,
|
|
57
|
+
void 0,
|
|
58
|
+
props.cache
|
|
59
|
+
);
|
|
60
|
+
if (id !== requestId) return;
|
|
61
|
+
if (isArray(result)) {
|
|
62
|
+
modelItems.value = result;
|
|
63
|
+
items.value = result;
|
|
64
|
+
isErrorLoading.value = false;
|
|
65
|
+
} else {
|
|
66
|
+
isErrorLoading.value = true;
|
|
67
|
+
modelItems.value = [];
|
|
68
|
+
items.value = [];
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
if (id !== requestId) return;
|
|
72
|
+
isErrorLoading.value = true;
|
|
73
|
+
modelItems.value = [];
|
|
74
|
+
items.value = [];
|
|
75
|
+
} finally {
|
|
76
|
+
if (id === requestId) isLoading.value = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function applyFuzzy() {
|
|
80
|
+
if (!props.fuzzy) return;
|
|
81
|
+
if (isEmpty(searchData.value)) {
|
|
82
|
+
items.value = modelItems.value;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const results = useFuzzy(searchData.value, modelItems.value, queryFields.value);
|
|
86
|
+
const output = [];
|
|
87
|
+
if (results.value?.length) {
|
|
88
|
+
for (let index = 0; index < results.value.length; index++) {
|
|
89
|
+
const result = results.value[index];
|
|
90
|
+
if (result?.item) output.push(result.item);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
items.value = output;
|
|
94
|
+
}
|
|
95
|
+
const itemMap = computed(() => {
|
|
96
|
+
const m = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const it of modelItems.value) {
|
|
98
|
+
m.set(it?.[props.itemValue], it);
|
|
99
|
+
}
|
|
100
|
+
return m;
|
|
101
|
+
});
|
|
102
|
+
async function syncSelectedFromModelValue() {
|
|
103
|
+
const modelValueData = selectedItems.value;
|
|
104
|
+
const values = castArray(modelValueData).filter(
|
|
105
|
+
(value) => value !== void 0 && value !== null && value !== ""
|
|
106
|
+
);
|
|
107
|
+
if (!values.length) {
|
|
108
|
+
emit("update:selectedObject", props.multiple ? [] : void 0);
|
|
109
|
+
selectedItemsObject.value = [];
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const alreadyInObject = selectedItemsObject.value?.filter((it) => values.includes(it?.[props.itemValue])) || [];
|
|
113
|
+
const haveSet = new Set(alreadyInObject.map((it) => it?.[props.itemValue]));
|
|
114
|
+
const missingValues = values.filter((v) => !haveSet.has(v));
|
|
115
|
+
const stillMissing = [];
|
|
116
|
+
for (const v of missingValues) {
|
|
117
|
+
const localHit = itemMap.value.get(v);
|
|
118
|
+
if (localHit) {
|
|
119
|
+
alreadyInObject.push(localHit);
|
|
120
|
+
haveSet.add(v);
|
|
121
|
+
} else {
|
|
122
|
+
stillMissing.push(v);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (stillMissing.length && props.modelSelectedItem) {
|
|
126
|
+
try {
|
|
127
|
+
const key = props.modelSelectedItemKey || "id";
|
|
128
|
+
const variables = { ...props.modelSelectedItemBy || {} };
|
|
129
|
+
variables[key] = stillMissing;
|
|
130
|
+
const result = await useApi().postPromise(
|
|
131
|
+
buildApiEndpoint(props.modelSelectedItem),
|
|
132
|
+
{ fields: queryFields.value, variables },
|
|
133
|
+
void 0,
|
|
134
|
+
void 0,
|
|
135
|
+
props.cache
|
|
136
|
+
);
|
|
137
|
+
const resolved = castArray(result);
|
|
138
|
+
for (const obj of resolved) {
|
|
139
|
+
const v = obj?.[props.itemValue];
|
|
140
|
+
if (!haveSet.has(v)) {
|
|
141
|
+
alreadyInObject.push(obj);
|
|
142
|
+
haveSet.add(v);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
selectedItemsObject.value = alreadyInObject;
|
|
149
|
+
emit("update:selectedObject", props.multiple ? alreadyInObject : alreadyInObject[0]);
|
|
150
|
+
}
|
|
151
|
+
watchDebounced(
|
|
152
|
+
() => [
|
|
153
|
+
props.modelName,
|
|
154
|
+
props.serverSearch,
|
|
155
|
+
props.serverSearchKey,
|
|
156
|
+
props.cache,
|
|
157
|
+
props.modelBy,
|
|
158
|
+
queryFields.value
|
|
159
|
+
],
|
|
160
|
+
() => loadItems(),
|
|
161
|
+
{ immediate: true, deep: true, debounce: 30, maxWait: 200 }
|
|
162
|
+
);
|
|
163
|
+
watchDebounced(
|
|
164
|
+
searchData,
|
|
165
|
+
() => {
|
|
166
|
+
if (props.serverSearch) loadItems();
|
|
167
|
+
else applyFuzzy();
|
|
168
|
+
},
|
|
169
|
+
{ debounce: props.serverSearch ? props.serverSearchDebounce ?? 500 : 300, maxWait: 1500 }
|
|
170
|
+
);
|
|
171
|
+
watch(
|
|
172
|
+
() => modelItems.value,
|
|
173
|
+
() => {
|
|
174
|
+
if (!props.serverSearch) applyFuzzy();
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
watch(
|
|
178
|
+
() => selectedItems.value,
|
|
179
|
+
() => {
|
|
180
|
+
syncSelectedFromModelValue();
|
|
181
|
+
},
|
|
182
|
+
{ immediate: true }
|
|
183
|
+
);
|
|
184
|
+
const computedItems = computed(() => {
|
|
185
|
+
const sortByField = !props.sortBy || typeof props.sortBy === "string" ? [props.sortBy || (props.showCode ? props.itemValue : props.itemTitle)] : props.sortBy;
|
|
186
|
+
const baseSource = (props.fuzzy || props.serverSearch && !props.searchSearchSort) && !isEmpty(searchData.value) ? items.value : sortBy(items.value, sortByField);
|
|
187
|
+
let baseItems = [...baseSource];
|
|
188
|
+
if (hideInactiveInList.value) {
|
|
189
|
+
baseItems = baseItems.filter((it) => it?.[statusField.value] === activeValue.value);
|
|
190
|
+
}
|
|
191
|
+
for (const selectedItem of selectedItemsObject.value || []) {
|
|
192
|
+
const v = selectedItem?.[props.itemValue];
|
|
193
|
+
if (!baseItems.find((x) => x?.[props.itemValue] === v)) {
|
|
194
|
+
baseItems.push(selectedItem);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return baseItems;
|
|
198
|
+
});
|
|
199
|
+
function isSelectableValue(value) {
|
|
200
|
+
if (!hideInactiveInList.value) return true;
|
|
201
|
+
const local = itemMap.value.get(value);
|
|
202
|
+
const obj = local ?? selectedItemsObject.value.find((it) => it?.[props.itemValue] === value) ?? modelItems.value.find((it) => it?.[props.itemValue] === value);
|
|
203
|
+
if (!obj) return true;
|
|
204
|
+
return obj?.[statusField.value] === activeValue.value;
|
|
205
|
+
}
|
|
206
|
+
function setModelValueGuarded(val) {
|
|
207
|
+
if (!(props.preventSelectingInactive ?? false)) {
|
|
208
|
+
selectedItems.value = val;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (!props.multiple) {
|
|
212
|
+
if (val != null && !isSelectableValue(val)) return;
|
|
213
|
+
selectedItems.value = val;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const arr = Array.isArray(val) ? val : [];
|
|
217
|
+
selectedItems.value = arr.filter((v) => isSelectableValue(v));
|
|
218
|
+
}
|
|
219
|
+
const onUpdateModelValue = (val) => setModelValueGuarded(val);
|
|
220
|
+
return {
|
|
221
|
+
// state
|
|
222
|
+
modelItems,
|
|
223
|
+
items,
|
|
224
|
+
searchData,
|
|
225
|
+
isLoading,
|
|
226
|
+
isErrorLoading,
|
|
227
|
+
// derived
|
|
228
|
+
queryFields,
|
|
229
|
+
computedFilterKeys,
|
|
230
|
+
computedPlaceholder,
|
|
231
|
+
computedNoDataText,
|
|
232
|
+
computedItems,
|
|
233
|
+
// selection
|
|
234
|
+
selectedItemsObject,
|
|
235
|
+
syncSelectedFromModelValue,
|
|
236
|
+
// selection policy helper
|
|
237
|
+
isSelectableValue,
|
|
238
|
+
// guarded setter for v-model updates
|
|
239
|
+
setModelValueGuarded,
|
|
240
|
+
onUpdateModelValue,
|
|
241
|
+
// actions
|
|
242
|
+
loadItems,
|
|
243
|
+
applyFuzzy
|
|
244
|
+
};
|
|
245
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { FormDialogCallback } from '../types/formDialog.js';
|
|
2
|
+
import { type GraphqlModelConfigProps } from './graphqlModelOperation.js';
|
|
3
|
+
interface ApiHeaderProps {
|
|
4
|
+
headers?: any[];
|
|
5
|
+
}
|
|
6
|
+
interface ApiInitialDataProps {
|
|
7
|
+
initialData?: Record<string, any>;
|
|
8
|
+
}
|
|
9
|
+
export interface ApiModelProps {
|
|
10
|
+
api?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export type ApiModelComposableProps = GraphqlModelConfigProps & Partial<ApiHeaderProps> & Partial<ApiInitialDataProps>;
|
|
13
|
+
/**
|
|
14
|
+
* Build a REST API endpoint URL for a model.
|
|
15
|
+
* @param modelName - The model name (e.g., 'Patient')
|
|
16
|
+
* @param action - Optional action (e.g., 'create', 'pageable')
|
|
17
|
+
* @returns The endpoint path (e.g., '/model/patient/create')
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildApiEndpoint(modelName: string, action?: string): string;
|
|
20
|
+
export declare function useApiModel<T extends ApiModelComposableProps>(props: T): {
|
|
21
|
+
items: import("vue").Ref<Record<string, any>[], Record<string, any>[]>;
|
|
22
|
+
itemsLength: import("vue").Ref<number, number>;
|
|
23
|
+
search: import("vue").Ref<string | undefined, string | undefined>;
|
|
24
|
+
setSearch: (keyword: string) => void;
|
|
25
|
+
currentOptions: import("vue").Ref<any, any>;
|
|
26
|
+
fields: import("vue").ComputedRef<(string | object)[]>;
|
|
27
|
+
canServerPageable: import("vue").ComputedRef<boolean>;
|
|
28
|
+
canServerSearch: import("vue").ComputedRef<boolean>;
|
|
29
|
+
canCreate: import("vue").ComputedRef<boolean>;
|
|
30
|
+
canUpdate: import("vue").ComputedRef<boolean>;
|
|
31
|
+
canDelete: import("vue").ComputedRef<boolean>;
|
|
32
|
+
createItem: (item: Record<string, any>, callback?: FormDialogCallback, importing?: boolean) => Promise<any>;
|
|
33
|
+
importItems: (importItemsList: Record<string, any>[], callback?: FormDialogCallback) => void;
|
|
34
|
+
updateItem: (item: Record<string, any>, callback?: FormDialogCallback) => Promise<any>;
|
|
35
|
+
deleteItem: (item: Record<string, any>, callback?: FormDialogCallback) => Promise<unknown>;
|
|
36
|
+
loadItems: (options: any) => void;
|
|
37
|
+
reload: () => void;
|
|
38
|
+
isLoading: import("vue").Ref<boolean, boolean>;
|
|
39
|
+
};
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { computed, onMounted, ref } from "vue";
|
|
2
|
+
import { watchDebounced } from "@vueuse/core";
|
|
3
|
+
import { useAlert } from "./alert.js";
|
|
4
|
+
import { useApi } from "./api.js";
|
|
5
|
+
import { arrayWrap } from "../utils/array.js";
|
|
6
|
+
import pLimit from "p-limit";
|
|
7
|
+
export function buildApiEndpoint(modelName, action) {
|
|
8
|
+
const lowercasedName = modelName.charAt(0).toLowerCase() + modelName.slice(1);
|
|
9
|
+
const base = `/model/${lowercasedName}`;
|
|
10
|
+
return action ? `${base}/${action}` : base;
|
|
11
|
+
}
|
|
12
|
+
export function useApiModel(props) {
|
|
13
|
+
const alert = useAlert();
|
|
14
|
+
const api = useApi();
|
|
15
|
+
const items = ref([]);
|
|
16
|
+
const itemsLength = ref(0);
|
|
17
|
+
const search = ref();
|
|
18
|
+
const currentOptions = ref();
|
|
19
|
+
const isLoading = ref(false);
|
|
20
|
+
function setSearch(keyword) {
|
|
21
|
+
search.value = keyword;
|
|
22
|
+
}
|
|
23
|
+
const canServerPageable = computed(() => true);
|
|
24
|
+
const canServerSearch = computed(() => true);
|
|
25
|
+
const canCreate = computed(() => true);
|
|
26
|
+
const canUpdate = computed(() => true);
|
|
27
|
+
const canDelete = computed(() => true);
|
|
28
|
+
function keyToField(key) {
|
|
29
|
+
const parts = key.split(".");
|
|
30
|
+
if (parts.length <= 1) return key;
|
|
31
|
+
let lastValue = parts.pop();
|
|
32
|
+
let result = {};
|
|
33
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
34
|
+
if (i == parts.length - 1) result = { [parts[i]]: [lastValue] };
|
|
35
|
+
else result = { [parts[i]]: result };
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
const fields = computed(() => {
|
|
40
|
+
const tmpFields = [];
|
|
41
|
+
const fieldsProps = props.fields || [];
|
|
42
|
+
if (props.headers && props.headers.length > 0) {
|
|
43
|
+
props.headers.forEach((header) => {
|
|
44
|
+
if (!fieldsProps.includes(header.key)) tmpFields.push(keyToField(header.key));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return [.../* @__PURE__ */ new Set([...fieldsProps, ...tmpFields])];
|
|
48
|
+
});
|
|
49
|
+
function getModelName() {
|
|
50
|
+
return props.modelName.split("By")[0].trim();
|
|
51
|
+
}
|
|
52
|
+
function createItem(item, callback, importing = false) {
|
|
53
|
+
isLoading.value = true;
|
|
54
|
+
const endpoint = buildApiEndpoint(getModelName(), "create");
|
|
55
|
+
return api.postPromise(endpoint, { input: item, fields: fields.value }).then((result) => {
|
|
56
|
+
if (canServerPageable.value) {
|
|
57
|
+
if (!importing) loadItems(currentOptions.value);
|
|
58
|
+
} else {
|
|
59
|
+
items.value.push(result);
|
|
60
|
+
}
|
|
61
|
+
if (callback && callback.setData) callback.setData(result);
|
|
62
|
+
return result;
|
|
63
|
+
}).catch((error) => {
|
|
64
|
+
alert?.addAlert({ alertType: "error", message: error?.message || String(error) });
|
|
65
|
+
}).finally(() => {
|
|
66
|
+
if (!importing) isLoading.value = false;
|
|
67
|
+
if (callback) callback.done();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function importItems(importItemsList, callback) {
|
|
71
|
+
isLoading.value = true;
|
|
72
|
+
const limit = pLimit(50);
|
|
73
|
+
const importPromises = importItemsList.map(
|
|
74
|
+
(item) => limit(
|
|
75
|
+
() => (item[props.modelKey || "id"] ? updateItem(item, void 0).then((result) => {
|
|
76
|
+
if (!result) {
|
|
77
|
+
return createItem(Object.assign({}, props.initialData, item), void 0, true);
|
|
78
|
+
}
|
|
79
|
+
}) : createItem(Object.assign({}, props.initialData, item), void 0, true)).catch((error) => {
|
|
80
|
+
alert?.addAlert({ alertType: "error", message: error?.message || String(error) });
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
);
|
|
84
|
+
Promise.all(importPromises).finally(() => {
|
|
85
|
+
isLoading.value = false;
|
|
86
|
+
reload();
|
|
87
|
+
if (callback) callback.done();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function updateItem(item, callback) {
|
|
91
|
+
isLoading.value = true;
|
|
92
|
+
const endpoint = buildApiEndpoint(getModelName(), "update");
|
|
93
|
+
return api.postPromise(endpoint, { input: item, fields: fields.value }).then((result) => {
|
|
94
|
+
if (canServerPageable.value) {
|
|
95
|
+
loadItems(currentOptions.value);
|
|
96
|
+
} else {
|
|
97
|
+
const index = items.value.findIndex((i) => i[props.modelKey || "id"] === result[props.modelKey || "id"]);
|
|
98
|
+
if (index !== -1) {
|
|
99
|
+
items.value[index] = result;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (callback && callback.setData) callback.setData(result);
|
|
103
|
+
return result;
|
|
104
|
+
}).catch((error) => {
|
|
105
|
+
alert?.addAlert({ alertType: "error", message: error?.message || String(error) });
|
|
106
|
+
}).finally(() => {
|
|
107
|
+
isLoading.value = false;
|
|
108
|
+
if (callback) callback.done();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function deleteItem(item, callback) {
|
|
112
|
+
isLoading.value = true;
|
|
113
|
+
const endpoint = buildApiEndpoint(getModelName(), "delete");
|
|
114
|
+
return api.postPromise(endpoint, { input: item, fields: fields.value }).catch((error) => {
|
|
115
|
+
alert?.addAlert({ alertType: "error", message: error?.message || String(error) });
|
|
116
|
+
}).finally(() => {
|
|
117
|
+
isLoading.value = false;
|
|
118
|
+
reload();
|
|
119
|
+
if (callback) callback.done();
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function loadItems(options) {
|
|
123
|
+
currentOptions.value = options;
|
|
124
|
+
const endpoint = buildApiEndpoint(getModelName(), "pageable");
|
|
125
|
+
isLoading.value = true;
|
|
126
|
+
api.postPromise(
|
|
127
|
+
endpoint,
|
|
128
|
+
Object.assign({}, props.modelBy, {
|
|
129
|
+
variables: props.modelBy,
|
|
130
|
+
fields: fields.value,
|
|
131
|
+
page: options?.page,
|
|
132
|
+
perPage: options?.itemsPerPage,
|
|
133
|
+
sortBy: options?.sortBy
|
|
134
|
+
})
|
|
135
|
+
).then((result) => {
|
|
136
|
+
items.value = result.data;
|
|
137
|
+
itemsLength.value = result.meta.totalItems;
|
|
138
|
+
}).catch((error) => {
|
|
139
|
+
items.value = [];
|
|
140
|
+
itemsLength.value = 0;
|
|
141
|
+
alert?.addAlert({ alertType: "error", message: error?.message || String(error) });
|
|
142
|
+
}).finally(() => {
|
|
143
|
+
isLoading.value = false;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function reload() {
|
|
147
|
+
if (currentOptions.value) {
|
|
148
|
+
loadItems(currentOptions.value);
|
|
149
|
+
} else {
|
|
150
|
+
isLoading.value = true;
|
|
151
|
+
const endpoint = buildApiEndpoint(getModelName());
|
|
152
|
+
api.postPromise(
|
|
153
|
+
endpoint,
|
|
154
|
+
{ variables: props.modelBy, fields: fields.value }
|
|
155
|
+
).then((result) => {
|
|
156
|
+
items.value = arrayWrap(result);
|
|
157
|
+
}).catch((error) => {
|
|
158
|
+
items.value = [];
|
|
159
|
+
alert?.addAlert({ alertType: "error", message: error?.message || String(error) });
|
|
160
|
+
}).finally(() => {
|
|
161
|
+
isLoading.value = false;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
watchDebounced([() => props.modelName, () => props.modelBy, () => props.modelKey], () => {
|
|
166
|
+
reload();
|
|
167
|
+
}, { deep: true, debounce: 500, maxWait: 500 });
|
|
168
|
+
onMounted(() => {
|
|
169
|
+
reload();
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
items,
|
|
173
|
+
itemsLength,
|
|
174
|
+
search,
|
|
175
|
+
setSearch,
|
|
176
|
+
currentOptions,
|
|
177
|
+
fields,
|
|
178
|
+
canServerPageable,
|
|
179
|
+
canServerSearch,
|
|
180
|
+
canCreate,
|
|
181
|
+
canUpdate,
|
|
182
|
+
canDelete,
|
|
183
|
+
createItem,
|
|
184
|
+
importItems,
|
|
185
|
+
updateItem,
|
|
186
|
+
deleteItem,
|
|
187
|
+
loadItems,
|
|
188
|
+
reload,
|
|
189
|
+
isLoading
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FormDialogCallback } from '../types/formDialog.js';
|
|
2
|
+
import type { GraphqlModelConfigProps } from './graphqlModelOperation.js';
|
|
3
|
+
export type ApiModelItemProps = Omit<GraphqlModelConfigProps, 'operationSearch' | 'operationReadPageable'>;
|
|
4
|
+
export declare function useApiModelItem<T extends ApiModelItemProps>(props: T): {
|
|
5
|
+
modelBy: import("vue").Ref<object | undefined, object | undefined>;
|
|
6
|
+
item: import("vue").Ref<Record<string, any> | undefined, Record<string, any> | undefined>;
|
|
7
|
+
isLoading: import("vue").Ref<boolean, boolean>;
|
|
8
|
+
fields: import("vue").ComputedRef<(string | object)[]>;
|
|
9
|
+
canCreate: import("vue").ComputedRef<boolean>;
|
|
10
|
+
canUpdate: import("vue").ComputedRef<boolean>;
|
|
11
|
+
canDelete: import("vue").ComputedRef<boolean>;
|
|
12
|
+
createItem: (createItem: Record<string, any>, callback?: FormDialogCallback) => Promise<void>;
|
|
13
|
+
updateItem: (updateItem: Record<string, any>, callback?: FormDialogCallback) => Promise<void>;
|
|
14
|
+
deleteItem: (deleteItem: Record<string, any>, callback?: FormDialogCallback) => Promise<any>;
|
|
15
|
+
reload: () => Promise<void>;
|
|
16
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { computed, ref, watch } from "vue";
|
|
2
|
+
import { useAlert } from "./alert.js";
|
|
3
|
+
import { useApi } from "./api.js";
|
|
4
|
+
import { buildApiEndpoint } from "./apiModel.js";
|
|
5
|
+
export function useApiModelItem(props) {
|
|
6
|
+
const alert = useAlert();
|
|
7
|
+
const api = useApi();
|
|
8
|
+
const modelBy = ref();
|
|
9
|
+
const item = ref();
|
|
10
|
+
const isLoading = ref(false);
|
|
11
|
+
const fields = computed(() => {
|
|
12
|
+
return props.fields || [];
|
|
13
|
+
});
|
|
14
|
+
const canCreate = computed(() => true);
|
|
15
|
+
const canUpdate = computed(() => true);
|
|
16
|
+
const canDelete = computed(() => true);
|
|
17
|
+
const computedModelBy = computed(() => {
|
|
18
|
+
return Object.assign({}, props.modelBy, modelBy.value);
|
|
19
|
+
});
|
|
20
|
+
function reload() {
|
|
21
|
+
isLoading.value = true;
|
|
22
|
+
item.value = void 0;
|
|
23
|
+
const endpoint = buildApiEndpoint(props.modelName);
|
|
24
|
+
return api.postPromise(endpoint, {
|
|
25
|
+
variables: computedModelBy.value,
|
|
26
|
+
fields: fields.value
|
|
27
|
+
}).then((result) => {
|
|
28
|
+
if (Array.isArray(result)) {
|
|
29
|
+
alert?.addAlert({ alertType: "error", message: "Return result is not single item" });
|
|
30
|
+
} else {
|
|
31
|
+
item.value = result;
|
|
32
|
+
}
|
|
33
|
+
}).catch((error) => {
|
|
34
|
+
alert?.addAlert({ alertType: "error", message: error?.message ?? error });
|
|
35
|
+
}).finally(() => {
|
|
36
|
+
isLoading.value = false;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function createItem(createItem2, callback) {
|
|
40
|
+
isLoading.value = true;
|
|
41
|
+
const endpoint = buildApiEndpoint(props.modelName, "create");
|
|
42
|
+
return api.postPromise(endpoint, {
|
|
43
|
+
input: createItem2,
|
|
44
|
+
fields: fields.value
|
|
45
|
+
}).then((result) => {
|
|
46
|
+
item.value = result;
|
|
47
|
+
}).catch((error) => {
|
|
48
|
+
alert?.addAlert({ alertType: "error", message: error?.message ?? error });
|
|
49
|
+
reload();
|
|
50
|
+
}).finally(() => {
|
|
51
|
+
isLoading.value = false;
|
|
52
|
+
if (callback) callback.done();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function updateItem(updateItem2, callback) {
|
|
56
|
+
isLoading.value = true;
|
|
57
|
+
const endpoint = buildApiEndpoint(props.modelName, "update");
|
|
58
|
+
return api.postPromise(endpoint, {
|
|
59
|
+
input: updateItem2,
|
|
60
|
+
fields: fields.value
|
|
61
|
+
}).then((result) => {
|
|
62
|
+
item.value = result;
|
|
63
|
+
}).catch((error) => {
|
|
64
|
+
alert?.addAlert({ alertType: "error", message: error?.message ?? error });
|
|
65
|
+
reload();
|
|
66
|
+
}).finally(() => {
|
|
67
|
+
isLoading.value = false;
|
|
68
|
+
if (callback) callback.done();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function deleteItem(deleteItem2, callback) {
|
|
72
|
+
isLoading.value = true;
|
|
73
|
+
const endpoint = buildApiEndpoint(props.modelName, "delete");
|
|
74
|
+
return api.postPromise(endpoint, {
|
|
75
|
+
input: deleteItem2,
|
|
76
|
+
fields: fields.value
|
|
77
|
+
}).catch((error) => {
|
|
78
|
+
alert?.addAlert({ alertType: "error", message: error?.message ?? error });
|
|
79
|
+
}).finally(() => {
|
|
80
|
+
isLoading.value = false;
|
|
81
|
+
reload();
|
|
82
|
+
if (callback) callback.done();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
watch([() => props.modelName, () => props.modelKey, computedModelBy], () => {
|
|
86
|
+
reload();
|
|
87
|
+
}, { immediate: true, deep: true });
|
|
88
|
+
return {
|
|
89
|
+
modelBy,
|
|
90
|
+
item,
|
|
91
|
+
isLoading,
|
|
92
|
+
fields,
|
|
93
|
+
canCreate,
|
|
94
|
+
canUpdate,
|
|
95
|
+
canDelete,
|
|
96
|
+
createItem,
|
|
97
|
+
updateItem,
|
|
98
|
+
deleteItem,
|
|
99
|
+
reload
|
|
100
|
+
};
|
|
101
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ramathibodi/nuxt-commons",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.3",
|
|
4
4
|
"description": "Ramathibodi Nuxt modules for common components",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,9 +43,6 @@
|
|
|
43
43
|
"scripts",
|
|
44
44
|
"templates"
|
|
45
45
|
],
|
|
46
|
-
"workspaces": [
|
|
47
|
-
"playground"
|
|
48
|
-
],
|
|
49
46
|
"scripts": {
|
|
50
47
|
"prepack": "nuxt-module-build build",
|
|
51
48
|
"dev": "npm run dev:prepare && nuxt dev playground",
|
|
@@ -128,10 +125,12 @@
|
|
|
128
125
|
"@types/lodash-es": "^4.17.12",
|
|
129
126
|
"@types/luxon": "^3.7.1",
|
|
130
127
|
"@types/node": "latest",
|
|
128
|
+
"@vue/test-utils": "^2.4.6",
|
|
131
129
|
"@vueuse/core": "^14.2.1",
|
|
132
130
|
"@vueuse/nuxt": "^14.2.1",
|
|
133
131
|
"changelogen": "^0.6.2",
|
|
134
132
|
"eslint": "^10.0.3",
|
|
133
|
+
"happy-dom": "^20.8.9",
|
|
135
134
|
"nuxt": "^4.3.1",
|
|
136
135
|
"sass": "^1.97.3",
|
|
137
136
|
"typedoc": "^0.28.17",
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type {CodegenConfig} from '@graphql-codegen/cli'
|
|
2
2
|
|
|
3
|
+
declare const process: { env: Record<string, string | undefined> }
|
|
4
|
+
|
|
3
5
|
const config: CodegenConfig = {
|
|
4
6
|
overwrite: true,
|
|
5
7
|
schema: process.env.NUXT_PUBLIC_WS_GRAPHQL,
|
|
6
8
|
generates: {
|
|
7
|
-
'./types/graphql.ts': {
|
|
9
|
+
'./app/types/graphql.ts': {
|
|
8
10
|
plugins: [{
|
|
9
11
|
add: {
|
|
10
12
|
content: '//Auto-generated file, do not make any change. Content could be overwritten.',
|
|
@@ -18,12 +20,12 @@ const config: CodegenConfig = {
|
|
|
18
20
|
skipTypename: true,
|
|
19
21
|
},
|
|
20
22
|
},
|
|
21
|
-
'./composables/graphqlObject.ts': {
|
|
23
|
+
'./app/composables/graphqlObject.ts': {
|
|
22
24
|
plugins: [{
|
|
23
25
|
add: {
|
|
24
26
|
content: '//Auto-generated file, do not make any change. Content could be overwritten.',
|
|
25
27
|
},
|
|
26
|
-
}, './.codegen/plugin-schema-object.
|
|
28
|
+
}, './.codegen/plugin-schema-object.cjs'],
|
|
27
29
|
},
|
|
28
30
|
},
|
|
29
31
|
hooks: { afterAllFileWrite: ['prettier --parser typescript --tab-width 4 --write'] },
|
|
File without changes
|