@goweekdays/layer-common 1.6.8 → 1.6.10
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/CHANGELOG.md +12 -0
- package/components/CategoryForm.vue +336 -0
- package/components/CategoryMain.vue +292 -0
- package/components/Input/SnakeCase.vue +10 -3
- package/components/TagForm.vue +186 -0
- package/composables/useCategory.ts +88 -0
- package/composables/useTag.ts +78 -0
- package/package.json +1 -1
- package/pages/index.vue +1 -1
- package/types/asset.d.ts +33 -0
- package/types/category.d.ts +13 -0
- package/types/tag.d.ts +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @goweekdays/layer-common
|
|
2
2
|
|
|
3
|
+
## 1.6.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 19f032f: Add UI components for category and tag management (CategoryForm.vue, CategoryMain.vue, TagForm.vue) including dialogs, pagination, and debounced searches. Update useCategory composable to accept an optional org parameter and route requests to /api/asset/item/categories/org/:org when provided (add and getAll). Update useTag composable and TTag type: expand tag shape with normalizedName, type, orgId, categoryPath, status, usageCount; adjust add, getAll and updateById signatures to support the new fields and filters; remove unused parent/children helpers. These changes enable org-scoped category operations and a richer tag model used by the new components.
|
|
8
|
+
|
|
9
|
+
## 1.6.9
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- ceb8c57: Fix index page
|
|
14
|
+
|
|
3
15
|
## 1.6.8
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card width="100%">
|
|
3
|
+
<v-toolbar>
|
|
4
|
+
<v-row no-gutters class="fill-height px-6" align="center">
|
|
5
|
+
<span class="font-weight-bold text-h5">{{ localProps.title }}</span>
|
|
6
|
+
</v-row>
|
|
7
|
+
</v-toolbar>
|
|
8
|
+
|
|
9
|
+
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-2">
|
|
10
|
+
<v-form v-model="valid">
|
|
11
|
+
<v-row no-gutters>
|
|
12
|
+
<v-col v-if="isCategory || isSubcategory" cols="12" class="mb-2">
|
|
13
|
+
<v-row no-gutters>
|
|
14
|
+
<InputLabel title="Department" :required="isMutable" />
|
|
15
|
+
<v-col cols="12">
|
|
16
|
+
<v-autocomplete
|
|
17
|
+
v-model="department"
|
|
18
|
+
v-model:search="searchDepartment"
|
|
19
|
+
:items="departments"
|
|
20
|
+
item-title="displayName"
|
|
21
|
+
item-value="_id"
|
|
22
|
+
:return-object="false"
|
|
23
|
+
:readonly="!isMutable"
|
|
24
|
+
:loading="loadingDepartments"
|
|
25
|
+
:rules="isMutable ? [requiredRule] : []"
|
|
26
|
+
:clearable="isMutable"
|
|
27
|
+
></v-autocomplete>
|
|
28
|
+
</v-col>
|
|
29
|
+
</v-row>
|
|
30
|
+
</v-col>
|
|
31
|
+
|
|
32
|
+
<v-col v-if="isSubcategory" cols="12" class="mb-2">
|
|
33
|
+
<v-row no-gutters>
|
|
34
|
+
<InputLabel title="Category" :required="isMutable" />
|
|
35
|
+
<v-col cols="12">
|
|
36
|
+
<v-autocomplete
|
|
37
|
+
v-model="categoryParent"
|
|
38
|
+
v-model:search="searchCategory"
|
|
39
|
+
:items="categoryParents"
|
|
40
|
+
item-title="displayName"
|
|
41
|
+
item-value="_id"
|
|
42
|
+
:return-object="false"
|
|
43
|
+
:readonly="!isMutable"
|
|
44
|
+
:loading="loadingCategoryParents"
|
|
45
|
+
:rules="isMutable ? [requiredRule] : []"
|
|
46
|
+
:disabled="!department"
|
|
47
|
+
:clearable="isMutable"
|
|
48
|
+
></v-autocomplete>
|
|
49
|
+
</v-col>
|
|
50
|
+
</v-row>
|
|
51
|
+
</v-col>
|
|
52
|
+
|
|
53
|
+
<v-col cols="12" class="mb-2">
|
|
54
|
+
<v-row no-gutters>
|
|
55
|
+
<InputLabel title="Name" :required="isMutable" />
|
|
56
|
+
<v-col cols="12">
|
|
57
|
+
<v-text-field
|
|
58
|
+
v-model="category.displayName"
|
|
59
|
+
:rules="isMutable ? [requiredRule] : []"
|
|
60
|
+
:readonly="!isMutable"
|
|
61
|
+
></v-text-field>
|
|
62
|
+
</v-col>
|
|
63
|
+
</v-row>
|
|
64
|
+
</v-col>
|
|
65
|
+
</v-row>
|
|
66
|
+
</v-form>
|
|
67
|
+
|
|
68
|
+
<v-alert
|
|
69
|
+
v-if="message"
|
|
70
|
+
type="error"
|
|
71
|
+
variant="flat"
|
|
72
|
+
closable
|
|
73
|
+
position="absolute"
|
|
74
|
+
location="bottom"
|
|
75
|
+
style="bottom: 48px"
|
|
76
|
+
@click:close="message = ''"
|
|
77
|
+
width="100%"
|
|
78
|
+
tile
|
|
79
|
+
class="text-caption"
|
|
80
|
+
>
|
|
81
|
+
{{ message }}
|
|
82
|
+
</v-alert>
|
|
83
|
+
</v-card-text>
|
|
84
|
+
|
|
85
|
+
<v-toolbar density="compact">
|
|
86
|
+
<v-row no-gutters>
|
|
87
|
+
<v-col cols="6">
|
|
88
|
+
<v-btn
|
|
89
|
+
tile
|
|
90
|
+
block
|
|
91
|
+
variant="text"
|
|
92
|
+
class="text-none"
|
|
93
|
+
size="48"
|
|
94
|
+
@click="emits('close')"
|
|
95
|
+
:disabled="localProps.loading"
|
|
96
|
+
>
|
|
97
|
+
{{ isMutable ? "Cancel" : "Close" }}
|
|
98
|
+
</v-btn>
|
|
99
|
+
</v-col>
|
|
100
|
+
|
|
101
|
+
<v-col v-if="localProps.mode === 'view'" cols="6">
|
|
102
|
+
<v-menu>
|
|
103
|
+
<template #activator="{ props }">
|
|
104
|
+
<v-btn
|
|
105
|
+
block
|
|
106
|
+
variant="flat"
|
|
107
|
+
color="black"
|
|
108
|
+
class="text-none"
|
|
109
|
+
height="48"
|
|
110
|
+
v-bind="props"
|
|
111
|
+
tile
|
|
112
|
+
>
|
|
113
|
+
More actions
|
|
114
|
+
</v-btn>
|
|
115
|
+
</template>
|
|
116
|
+
|
|
117
|
+
<v-list class="pa-0">
|
|
118
|
+
<v-list-item @click="emits('edit')">
|
|
119
|
+
<v-list-item-title class="text-subtitle-2">
|
|
120
|
+
Edit
|
|
121
|
+
</v-list-item-title>
|
|
122
|
+
</v-list-item>
|
|
123
|
+
|
|
124
|
+
<v-list-item @click="emits('delete')" class="text-red">
|
|
125
|
+
<v-list-item-title class="text-subtitle-2">
|
|
126
|
+
Delete
|
|
127
|
+
</v-list-item-title>
|
|
128
|
+
</v-list-item>
|
|
129
|
+
</v-list>
|
|
130
|
+
</v-menu>
|
|
131
|
+
</v-col>
|
|
132
|
+
|
|
133
|
+
<v-col v-if="isMutable" cols="6">
|
|
134
|
+
<v-btn
|
|
135
|
+
tile
|
|
136
|
+
block
|
|
137
|
+
variant="flat"
|
|
138
|
+
color="black"
|
|
139
|
+
class="text-none"
|
|
140
|
+
size="48"
|
|
141
|
+
@click="emits('submit')"
|
|
142
|
+
:loading="localProps.loading"
|
|
143
|
+
:disabled="!valid"
|
|
144
|
+
>
|
|
145
|
+
{{ submitTitle }}
|
|
146
|
+
</v-btn>
|
|
147
|
+
</v-col>
|
|
148
|
+
</v-row>
|
|
149
|
+
</v-toolbar>
|
|
150
|
+
</v-card>
|
|
151
|
+
</template>
|
|
152
|
+
|
|
153
|
+
<script setup lang="ts">
|
|
154
|
+
const localProps = defineProps({
|
|
155
|
+
org: {
|
|
156
|
+
type: String,
|
|
157
|
+
default: "",
|
|
158
|
+
},
|
|
159
|
+
title: {
|
|
160
|
+
type: String,
|
|
161
|
+
default: "Category Form",
|
|
162
|
+
},
|
|
163
|
+
mode: {
|
|
164
|
+
type: String,
|
|
165
|
+
default: "add",
|
|
166
|
+
},
|
|
167
|
+
loading: {
|
|
168
|
+
type: Boolean,
|
|
169
|
+
default: false,
|
|
170
|
+
},
|
|
171
|
+
level: {
|
|
172
|
+
type: String,
|
|
173
|
+
default: "department",
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const isMutable = computed(() => ["add", "edit"].includes(localProps.mode));
|
|
178
|
+
|
|
179
|
+
const isDepartment = computed(() => localProps.level === "department");
|
|
180
|
+
const isCategory = computed(() => localProps.level === "category");
|
|
181
|
+
const isSubcategory = computed(() => localProps.level === "subcategory");
|
|
182
|
+
|
|
183
|
+
const submitTitle = computed(() => {
|
|
184
|
+
switch (localProps.mode) {
|
|
185
|
+
case "add":
|
|
186
|
+
return "Submit";
|
|
187
|
+
case "edit":
|
|
188
|
+
return "Save changes";
|
|
189
|
+
default:
|
|
190
|
+
return "";
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const emits = defineEmits(["edit", "delete", "submit", "cancel", "close"]);
|
|
195
|
+
|
|
196
|
+
const category = defineModel({
|
|
197
|
+
type: Object as PropType<TCategory>,
|
|
198
|
+
default: () => useCategory().category.value,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const valid = ref(false);
|
|
202
|
+
|
|
203
|
+
const { getAll: getAllCategories, getById } = useCategory();
|
|
204
|
+
|
|
205
|
+
const department = ref("");
|
|
206
|
+
|
|
207
|
+
if (isCategory.value) {
|
|
208
|
+
department.value = category.value.parentId || "";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const departments = ref<TCategory[]>([]);
|
|
212
|
+
const categoryParent = ref("");
|
|
213
|
+
|
|
214
|
+
watchEffect(() => {
|
|
215
|
+
if (!department.value) {
|
|
216
|
+
categoryParent.value = "";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (department.value) {
|
|
220
|
+
category.value.parentId = department.value;
|
|
221
|
+
} else if (isSubcategory.value) {
|
|
222
|
+
category.value.parentId = "";
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
watchEffect(() => {
|
|
227
|
+
if (categoryParent.value) {
|
|
228
|
+
category.value.parentId = categoryParent.value;
|
|
229
|
+
} else {
|
|
230
|
+
category.value.parentId = "";
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (isSubcategory.value) {
|
|
235
|
+
categoryParent.value = category.value.parentId || "";
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const parentCategory = await getById(category.value.parentId ?? "");
|
|
239
|
+
if (parentCategory) {
|
|
240
|
+
department.value = parentCategory.parentId || "";
|
|
241
|
+
}
|
|
242
|
+
} catch (error: any) {
|
|
243
|
+
console.log(
|
|
244
|
+
error.response._data.message || "Error fetching parent category details."
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const categoryParents = ref<TCategory[]>([]);
|
|
250
|
+
|
|
251
|
+
const message = defineModel("message", { type: String, default: "" });
|
|
252
|
+
|
|
253
|
+
const { requiredRule, debounce } = useUtils();
|
|
254
|
+
|
|
255
|
+
const searchDepartment = ref("");
|
|
256
|
+
|
|
257
|
+
const {
|
|
258
|
+
data: dataDepartments,
|
|
259
|
+
status: statusDepartments,
|
|
260
|
+
refresh: refreshDepartments,
|
|
261
|
+
} = await useLazyAsyncData(
|
|
262
|
+
`get-departments-for-category-form-${localProps.org}`,
|
|
263
|
+
() =>
|
|
264
|
+
getAllCategories({
|
|
265
|
+
page: 1,
|
|
266
|
+
limit: 100,
|
|
267
|
+
level: "department",
|
|
268
|
+
search: searchDepartment.value,
|
|
269
|
+
org: localProps.org,
|
|
270
|
+
})
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const debouncedSearchDepartment = debounce(() => {
|
|
274
|
+
refreshDepartments();
|
|
275
|
+
}, 500);
|
|
276
|
+
|
|
277
|
+
watch(searchDepartment, (val) => {
|
|
278
|
+
if (val !== searchDepartment.value) {
|
|
279
|
+
debouncedSearchDepartment();
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const loadingDepartments = computed(
|
|
284
|
+
() => statusDepartments.value === "pending"
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
watchEffect(() => {
|
|
288
|
+
if (dataDepartments.value) {
|
|
289
|
+
departments.value = dataDepartments.value.items;
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const searchCategory = ref("");
|
|
294
|
+
|
|
295
|
+
const {
|
|
296
|
+
data: dataCategories,
|
|
297
|
+
status: statusCategories,
|
|
298
|
+
refresh: refreshCategories,
|
|
299
|
+
} = await useLazyAsyncData(
|
|
300
|
+
`get-categories-for-category-form-${department.value}-${localProps.org}`,
|
|
301
|
+
() =>
|
|
302
|
+
getAllCategories({
|
|
303
|
+
page: 1,
|
|
304
|
+
limit: 100,
|
|
305
|
+
level: "category",
|
|
306
|
+
parent: department.value,
|
|
307
|
+
search: searchCategory.value,
|
|
308
|
+
org: localProps.org,
|
|
309
|
+
})
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const debouncedSearchCategory = debounce(() => {
|
|
313
|
+
refreshCategories();
|
|
314
|
+
}, 500);
|
|
315
|
+
|
|
316
|
+
watch(searchCategory, (val) => {
|
|
317
|
+
if (val !== searchCategory.value) {
|
|
318
|
+
debouncedSearchCategory();
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
watch(department, () => {
|
|
323
|
+
categoryParent.value = "";
|
|
324
|
+
refreshCategories();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const loadingCategoryParents = computed(
|
|
328
|
+
() => statusCategories.value === "pending"
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
watchEffect(() => {
|
|
332
|
+
if (dataCategories.value) {
|
|
333
|
+
categoryParents.value = dataCategories.value.items;
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
</script>
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-col cols="12">
|
|
3
|
+
<v-row no-gutters>
|
|
4
|
+
<v-col cols="12" class="mb-2">
|
|
5
|
+
<v-row no-gutters align="center">
|
|
6
|
+
<v-btn
|
|
7
|
+
class="text-none mr-2"
|
|
8
|
+
rounded="pill"
|
|
9
|
+
variant="tonal"
|
|
10
|
+
@click="setDepartment({ mode: 'add' })"
|
|
11
|
+
size="large"
|
|
12
|
+
>
|
|
13
|
+
Add {{ formTitle }}
|
|
14
|
+
</v-btn>
|
|
15
|
+
</v-row>
|
|
16
|
+
</v-col>
|
|
17
|
+
|
|
18
|
+
<v-col cols="12">
|
|
19
|
+
<v-card
|
|
20
|
+
width="100%"
|
|
21
|
+
variant="outlined"
|
|
22
|
+
border="thin"
|
|
23
|
+
rounded="lg"
|
|
24
|
+
:loading="loadingDepartments"
|
|
25
|
+
>
|
|
26
|
+
<v-toolbar density="compact" color="grey-lighten-4">
|
|
27
|
+
<template #prepend>
|
|
28
|
+
<v-btn
|
|
29
|
+
fab
|
|
30
|
+
icon
|
|
31
|
+
density="comfortable"
|
|
32
|
+
@click="refreshDepartments()"
|
|
33
|
+
>
|
|
34
|
+
<v-icon>mdi-refresh</v-icon>
|
|
35
|
+
</v-btn>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<template #append>
|
|
39
|
+
<v-row no-gutters justify="end" align="center">
|
|
40
|
+
<span class="mr-2 text-caption text-fontgray">
|
|
41
|
+
{{ pageRange }}
|
|
42
|
+
</span>
|
|
43
|
+
<local-pagination v-model="page" :length="pages" />
|
|
44
|
+
</v-row>
|
|
45
|
+
</template>
|
|
46
|
+
</v-toolbar>
|
|
47
|
+
|
|
48
|
+
<v-data-table
|
|
49
|
+
:headers="headers"
|
|
50
|
+
:items="items"
|
|
51
|
+
item-value="_id"
|
|
52
|
+
items-per-page="20"
|
|
53
|
+
fixed-header
|
|
54
|
+
hide-default-footer
|
|
55
|
+
hide-default-header
|
|
56
|
+
@click:row="tableRowClickHandler"
|
|
57
|
+
style="max-height: calc(100vh - (158px))"
|
|
58
|
+
>
|
|
59
|
+
<template #item.name="{ item }">
|
|
60
|
+
<span
|
|
61
|
+
:class="{
|
|
62
|
+
'pl-6': item.parent && item.path.split('.').length === 2,
|
|
63
|
+
'pl-10': item.parent && item.path.split('.').length === 3,
|
|
64
|
+
'pl-16': item.parent && item.path.split('.').length >= 4,
|
|
65
|
+
}"
|
|
66
|
+
>
|
|
67
|
+
{{ item.name }}
|
|
68
|
+
</span>
|
|
69
|
+
</template>
|
|
70
|
+
</v-data-table>
|
|
71
|
+
</v-card>
|
|
72
|
+
</v-col>
|
|
73
|
+
|
|
74
|
+
<!-- dialogs -->
|
|
75
|
+
<v-dialog v-model="dialogAdd" width="450" persistent>
|
|
76
|
+
<CategoryForm
|
|
77
|
+
v-model="category"
|
|
78
|
+
mode="add"
|
|
79
|
+
:title="`Add ${formTitle}`"
|
|
80
|
+
:level="localProps.level"
|
|
81
|
+
:org="localProps.org"
|
|
82
|
+
@close="setDepartment({ mode: 'add', dialog: false })"
|
|
83
|
+
@submit="submitAdd()"
|
|
84
|
+
v-model:message="messageCategory"
|
|
85
|
+
/>
|
|
86
|
+
</v-dialog>
|
|
87
|
+
|
|
88
|
+
<v-dialog v-model="dialogView" width="450" persistent>
|
|
89
|
+
<CategoryForm
|
|
90
|
+
v-model="category"
|
|
91
|
+
mode="view"
|
|
92
|
+
:title="`View ${formTitle} Details`"
|
|
93
|
+
:level="localProps.level"
|
|
94
|
+
:org="localProps.org"
|
|
95
|
+
@close="setDepartment({ mode: 'view', dialog: false })"
|
|
96
|
+
@edit="handleEdit()"
|
|
97
|
+
@delete="handleDelete()"
|
|
98
|
+
/>
|
|
99
|
+
</v-dialog>
|
|
100
|
+
|
|
101
|
+
<v-dialog v-model="dialogEdit" width="450" persistent>
|
|
102
|
+
<CategoryForm
|
|
103
|
+
v-model="category"
|
|
104
|
+
mode="edit"
|
|
105
|
+
:title="`Edit ${formTitle} Details`"
|
|
106
|
+
:level="localProps.level"
|
|
107
|
+
:org="localProps.org"
|
|
108
|
+
@close="setDepartment({ mode: 'edit', dialog: false })"
|
|
109
|
+
@submit="submitEdit()"
|
|
110
|
+
v-model:message="messageCategory"
|
|
111
|
+
/>
|
|
112
|
+
</v-dialog>
|
|
113
|
+
|
|
114
|
+
<v-dialog v-model="dialogDelete" width="450" persistent>
|
|
115
|
+
<ConfirmationPrompt
|
|
116
|
+
:title="`Delete ${formTitle}`"
|
|
117
|
+
action="Delete Category"
|
|
118
|
+
:content="`Are you sure you want to delete this ${formTitle.toLowerCase()}? This action cannot be undone.`"
|
|
119
|
+
@cancel="dialogDelete = false"
|
|
120
|
+
@confirm="submitDelete()"
|
|
121
|
+
:message="messageCategory"
|
|
122
|
+
:loading="loadingSubmit"
|
|
123
|
+
:disabled="loadingSubmit"
|
|
124
|
+
/>
|
|
125
|
+
</v-dialog>
|
|
126
|
+
</v-row>
|
|
127
|
+
</v-col>
|
|
128
|
+
</template>
|
|
129
|
+
|
|
130
|
+
<script setup lang="ts">
|
|
131
|
+
const localProps = defineProps({
|
|
132
|
+
org: {
|
|
133
|
+
type: String,
|
|
134
|
+
default: "",
|
|
135
|
+
},
|
|
136
|
+
level: {
|
|
137
|
+
type: String,
|
|
138
|
+
default: "department",
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const formTitle = computed(() => {
|
|
143
|
+
switch (localProps.level) {
|
|
144
|
+
case "department":
|
|
145
|
+
return "Department";
|
|
146
|
+
case "category":
|
|
147
|
+
return "Category";
|
|
148
|
+
case "subcategory":
|
|
149
|
+
return "Subcategory";
|
|
150
|
+
default:
|
|
151
|
+
return "Category";
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const headers = [{ title: "Name", value: "displayName" }];
|
|
156
|
+
|
|
157
|
+
const page = ref(1);
|
|
158
|
+
const pages = ref(0);
|
|
159
|
+
const pageRange = ref("-- - -- of --");
|
|
160
|
+
|
|
161
|
+
const items = ref<Array<Record<string, any>>>([]);
|
|
162
|
+
|
|
163
|
+
const { getAll, category, add, updateById, deleteById } = useCategory();
|
|
164
|
+
|
|
165
|
+
const {
|
|
166
|
+
data: dataDepartments,
|
|
167
|
+
refresh: refreshDepartments,
|
|
168
|
+
status: statusDepartments,
|
|
169
|
+
} = await useLazyAsyncData(
|
|
170
|
+
`get-all-from-category-registry-${localProps.org}-${localProps.level}-${page.value}`,
|
|
171
|
+
() =>
|
|
172
|
+
getAll({
|
|
173
|
+
page: page.value,
|
|
174
|
+
limit: 20,
|
|
175
|
+
org: localProps.org,
|
|
176
|
+
level: localProps.level,
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const loadingDepartments = computed(
|
|
181
|
+
() => statusDepartments.value === "pending"
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
watchEffect(() => {
|
|
185
|
+
if (dataDepartments.value) {
|
|
186
|
+
items.value = dataDepartments.value.items;
|
|
187
|
+
pages.value = dataDepartments.value.pages;
|
|
188
|
+
pageRange.value = dataDepartments.value.pageRange;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
watch(page, () => {
|
|
193
|
+
refreshDepartments();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Dialogs
|
|
197
|
+
const dialogAdd = ref(false);
|
|
198
|
+
const dialogView = ref(false);
|
|
199
|
+
const dialogEdit = ref(false);
|
|
200
|
+
const dialogDelete = ref(false);
|
|
201
|
+
|
|
202
|
+
function setDepartment({
|
|
203
|
+
data = category.value,
|
|
204
|
+
mode = "",
|
|
205
|
+
dialog = true,
|
|
206
|
+
} = {}) {
|
|
207
|
+
Object.assign(category.value, JSON.parse(JSON.stringify(data)));
|
|
208
|
+
|
|
209
|
+
switch (mode) {
|
|
210
|
+
case "add":
|
|
211
|
+
dialogAdd.value = dialog;
|
|
212
|
+
break;
|
|
213
|
+
case "view":
|
|
214
|
+
dialogView.value = dialog;
|
|
215
|
+
break;
|
|
216
|
+
case "edit":
|
|
217
|
+
dialogEdit.value = dialog;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const loadingSubmit = ref(false);
|
|
223
|
+
const messageCategory = ref("");
|
|
224
|
+
|
|
225
|
+
async function submitAdd() {
|
|
226
|
+
loadingSubmit.value = true;
|
|
227
|
+
try {
|
|
228
|
+
const payload: Pick<TCategory, "displayName" | "parentId"> = {
|
|
229
|
+
displayName: category.value.displayName,
|
|
230
|
+
};
|
|
231
|
+
if (category.value.parentId) {
|
|
232
|
+
payload.parentId = category.value.parentId;
|
|
233
|
+
}
|
|
234
|
+
await add(payload, localProps.org);
|
|
235
|
+
await refreshDepartments();
|
|
236
|
+
setDepartment({ mode: "add", dialog: false });
|
|
237
|
+
} catch (error: any) {
|
|
238
|
+
messageCategory.value =
|
|
239
|
+
error.response?._data?.message ||
|
|
240
|
+
`An error occurred while adding the ${formTitle.value.toLowerCase()}.`;
|
|
241
|
+
} finally {
|
|
242
|
+
loadingSubmit.value = false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function tableRowClickHandler(_: any, data: any) {
|
|
247
|
+
setDepartment({ mode: "view", data: data.item });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function handleEdit() {
|
|
251
|
+
dialogView.value = false;
|
|
252
|
+
dialogEdit.value = true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function submitEdit() {
|
|
256
|
+
loadingSubmit.value = true;
|
|
257
|
+
try {
|
|
258
|
+
await updateById(category.value._id ?? "", {
|
|
259
|
+
displayName: category.value.displayName,
|
|
260
|
+
});
|
|
261
|
+
await refreshDepartments();
|
|
262
|
+
setDepartment({ mode: "edit", dialog: false });
|
|
263
|
+
} catch (error: any) {
|
|
264
|
+
messageCategory.value =
|
|
265
|
+
error.response?._data?.message ||
|
|
266
|
+
"An error occurred while editing the category.";
|
|
267
|
+
} finally {
|
|
268
|
+
loadingSubmit.value = false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function handleDelete() {
|
|
273
|
+
dialogView.value = false;
|
|
274
|
+
dialogDelete.value = true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function submitDelete() {
|
|
278
|
+
loadingSubmit.value = true;
|
|
279
|
+
try {
|
|
280
|
+
await deleteById(category.value._id ?? "");
|
|
281
|
+
await refreshDepartments();
|
|
282
|
+
setDepartment({ mode: "view", dialog: false });
|
|
283
|
+
dialogDelete.value = false;
|
|
284
|
+
} catch (error: any) {
|
|
285
|
+
messageCategory.value =
|
|
286
|
+
error.response?._data?.message ||
|
|
287
|
+
"An error occurred while deleting the category.";
|
|
288
|
+
} finally {
|
|
289
|
+
loadingSubmit.value = false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
</script>
|
|
@@ -12,11 +12,18 @@
|
|
|
12
12
|
</template>
|
|
13
13
|
|
|
14
14
|
<script setup lang="ts">
|
|
15
|
-
const
|
|
15
|
+
const props = defineProps({
|
|
16
|
+
case: {
|
|
17
|
+
type: String as PropType<"uppercase" | "lowercase">,
|
|
18
|
+
default: "uppercase",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
16
21
|
|
|
17
|
-
const {
|
|
22
|
+
const model = defineModel<string>({ default: "" });
|
|
18
23
|
|
|
19
24
|
function onInput(val: string) {
|
|
20
|
-
|
|
25
|
+
const snake = val.replace(/\s+/g, "_");
|
|
26
|
+
model.value =
|
|
27
|
+
props.case === "lowercase" ? snake.toLowerCase() : snake.toUpperCase();
|
|
21
28
|
}
|
|
22
29
|
</script>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card width="100%">
|
|
3
|
+
<v-toolbar>
|
|
4
|
+
<v-row no-gutters class="fill-height px-6" align="center">
|
|
5
|
+
<span class="font-weight-bold text-h5">{{ localProps.title }}</span>
|
|
6
|
+
</v-row>
|
|
7
|
+
</v-toolbar>
|
|
8
|
+
|
|
9
|
+
<v-card-text style="max-height: 100vh; overflow-y: auto">
|
|
10
|
+
<v-row no-gutters>
|
|
11
|
+
<v-col cols="12" class="mb-2">
|
|
12
|
+
<v-row no-gutters>
|
|
13
|
+
<InputLabel title="Name" :required="isMutable" />
|
|
14
|
+
<v-col cols="12">
|
|
15
|
+
<v-text-field
|
|
16
|
+
v-model="tag.name"
|
|
17
|
+
:rules="isMutable ? [requiredRule] : []"
|
|
18
|
+
:readonly="!isMutable"
|
|
19
|
+
></v-text-field>
|
|
20
|
+
</v-col>
|
|
21
|
+
</v-row>
|
|
22
|
+
</v-col>
|
|
23
|
+
|
|
24
|
+
<v-col cols="12" class="mb-2">
|
|
25
|
+
<v-row no-gutters>
|
|
26
|
+
<InputLabel title="Type" :required="isMutable" />
|
|
27
|
+
<v-col cols="12">
|
|
28
|
+
<v-select
|
|
29
|
+
v-model="tag.type"
|
|
30
|
+
:items="['public', 'private']"
|
|
31
|
+
:readonly="!isMutable"
|
|
32
|
+
></v-select>
|
|
33
|
+
</v-col>
|
|
34
|
+
</v-row>
|
|
35
|
+
</v-col>
|
|
36
|
+
|
|
37
|
+
<v-col cols="12" class="mb-2">
|
|
38
|
+
<v-row no-gutters>
|
|
39
|
+
<InputLabel title="Category Path" />
|
|
40
|
+
<v-col cols="12">
|
|
41
|
+
<v-text-field
|
|
42
|
+
v-model="tag.categoryPath"
|
|
43
|
+
:readonly="!isMutable"
|
|
44
|
+
></v-text-field>
|
|
45
|
+
</v-col>
|
|
46
|
+
</v-row>
|
|
47
|
+
</v-col>
|
|
48
|
+
|
|
49
|
+
<v-col v-if="localProps.mode !== 'add'" cols="12" class="mb-2">
|
|
50
|
+
<v-row no-gutters>
|
|
51
|
+
<InputLabel title="Status" />
|
|
52
|
+
<v-col cols="12">
|
|
53
|
+
<v-select
|
|
54
|
+
v-model="tag.status"
|
|
55
|
+
:items="['active', 'pending']"
|
|
56
|
+
:readonly="!isMutable"
|
|
57
|
+
></v-select>
|
|
58
|
+
</v-col>
|
|
59
|
+
</v-row>
|
|
60
|
+
</v-col>
|
|
61
|
+
</v-row>
|
|
62
|
+
|
|
63
|
+
<v-alert
|
|
64
|
+
v-if="message"
|
|
65
|
+
type="error"
|
|
66
|
+
variant="flat"
|
|
67
|
+
closable
|
|
68
|
+
position="absolute"
|
|
69
|
+
location="bottom"
|
|
70
|
+
style="bottom: 48px"
|
|
71
|
+
@click:close="message = ''"
|
|
72
|
+
width="100%"
|
|
73
|
+
tile
|
|
74
|
+
class="text-caption"
|
|
75
|
+
>
|
|
76
|
+
{{ message }}
|
|
77
|
+
</v-alert>
|
|
78
|
+
</v-card-text>
|
|
79
|
+
|
|
80
|
+
<v-toolbar density="compact">
|
|
81
|
+
<v-row no-gutters>
|
|
82
|
+
<v-col cols="6">
|
|
83
|
+
<v-btn
|
|
84
|
+
tile
|
|
85
|
+
block
|
|
86
|
+
variant="text"
|
|
87
|
+
class="text-none"
|
|
88
|
+
size="48"
|
|
89
|
+
@click="emits('close')"
|
|
90
|
+
:disabled="localProps.loading"
|
|
91
|
+
>
|
|
92
|
+
{{ isMutable ? "Cancel" : "Close" }}
|
|
93
|
+
</v-btn>
|
|
94
|
+
</v-col>
|
|
95
|
+
|
|
96
|
+
<v-col v-if="localProps.mode === 'view'" cols="6">
|
|
97
|
+
<v-menu>
|
|
98
|
+
<template #activator="{ props }">
|
|
99
|
+
<v-btn
|
|
100
|
+
block
|
|
101
|
+
variant="flat"
|
|
102
|
+
color="black"
|
|
103
|
+
class="text-none"
|
|
104
|
+
height="48"
|
|
105
|
+
v-bind="props"
|
|
106
|
+
tile
|
|
107
|
+
>
|
|
108
|
+
More actions
|
|
109
|
+
</v-btn>
|
|
110
|
+
</template>
|
|
111
|
+
|
|
112
|
+
<v-list class="pa-0">
|
|
113
|
+
<v-list-item @click="emits('edit')">
|
|
114
|
+
<v-list-item-title class="text-subtitle-2">
|
|
115
|
+
Edit
|
|
116
|
+
</v-list-item-title>
|
|
117
|
+
</v-list-item>
|
|
118
|
+
|
|
119
|
+
<v-list-item @click="emits('delete')" class="text-red">
|
|
120
|
+
<v-list-item-title class="text-subtitle-2">
|
|
121
|
+
Delete
|
|
122
|
+
</v-list-item-title>
|
|
123
|
+
</v-list-item>
|
|
124
|
+
</v-list>
|
|
125
|
+
</v-menu>
|
|
126
|
+
</v-col>
|
|
127
|
+
|
|
128
|
+
<v-col v-if="isMutable" cols="6">
|
|
129
|
+
<v-btn
|
|
130
|
+
tile
|
|
131
|
+
block
|
|
132
|
+
variant="flat"
|
|
133
|
+
color="black"
|
|
134
|
+
class="text-none"
|
|
135
|
+
size="48"
|
|
136
|
+
@click="emits('submit')"
|
|
137
|
+
:loading="localProps.loading"
|
|
138
|
+
>
|
|
139
|
+
{{ submitTitle }}
|
|
140
|
+
</v-btn>
|
|
141
|
+
</v-col>
|
|
142
|
+
</v-row>
|
|
143
|
+
</v-toolbar>
|
|
144
|
+
</v-card>
|
|
145
|
+
</template>
|
|
146
|
+
|
|
147
|
+
<script setup lang="ts">
|
|
148
|
+
const localProps = defineProps({
|
|
149
|
+
title: {
|
|
150
|
+
type: String,
|
|
151
|
+
default: "Tag Form",
|
|
152
|
+
},
|
|
153
|
+
mode: {
|
|
154
|
+
type: String,
|
|
155
|
+
default: "add",
|
|
156
|
+
},
|
|
157
|
+
loading: {
|
|
158
|
+
type: Boolean,
|
|
159
|
+
default: false,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const isMutable = computed(() => ["add", "edit"].includes(localProps.mode));
|
|
164
|
+
|
|
165
|
+
const submitTitle = computed(() => {
|
|
166
|
+
switch (localProps.mode) {
|
|
167
|
+
case "add":
|
|
168
|
+
return "Submit";
|
|
169
|
+
case "edit":
|
|
170
|
+
return "Save changes";
|
|
171
|
+
default:
|
|
172
|
+
return "";
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const emits = defineEmits(["edit", "delete", "submit", "cancel", "close"]);
|
|
177
|
+
|
|
178
|
+
const tag = defineModel({
|
|
179
|
+
type: Object as PropType<TTag>,
|
|
180
|
+
default: () => useTag().tag.value,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const message = defineModel("message", { type: String, default: "" });
|
|
184
|
+
|
|
185
|
+
const { requiredRule } = useUtils();
|
|
186
|
+
</script>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export default function useCategory() {
|
|
2
|
+
const category = ref<TCategory>({
|
|
3
|
+
_id: "",
|
|
4
|
+
level: "department",
|
|
5
|
+
name: "",
|
|
6
|
+
displayName: "",
|
|
7
|
+
parentId: "",
|
|
8
|
+
path: "",
|
|
9
|
+
isActive: true,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function add(
|
|
13
|
+
value: Pick<TCategory, "displayName" | "parentId">,
|
|
14
|
+
org?: string
|
|
15
|
+
) {
|
|
16
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
17
|
+
org
|
|
18
|
+
? `/api/asset/item/categories/org/${org}`
|
|
19
|
+
: `/api/asset/item/categories`,
|
|
20
|
+
{
|
|
21
|
+
method: "POST",
|
|
22
|
+
body: value,
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getAll({
|
|
28
|
+
org = "",
|
|
29
|
+
parent = "",
|
|
30
|
+
level = "",
|
|
31
|
+
search = "",
|
|
32
|
+
limit = 20,
|
|
33
|
+
page = 1,
|
|
34
|
+
} = {}) {
|
|
35
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
36
|
+
org
|
|
37
|
+
? `/api/asset/item/categories/org/${org}`
|
|
38
|
+
: `/api/asset/item/categories`,
|
|
39
|
+
{
|
|
40
|
+
method: "GET",
|
|
41
|
+
query: {
|
|
42
|
+
parent,
|
|
43
|
+
level,
|
|
44
|
+
search,
|
|
45
|
+
limit,
|
|
46
|
+
page,
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getById(id: string) {
|
|
53
|
+
return useNuxtApp().$api<TCategory>(`/api/asset/item/categories/id/${id}`, {
|
|
54
|
+
method: "GET",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function updateById(
|
|
59
|
+
id: string,
|
|
60
|
+
options: Pick<TCategory, "displayName"> & { isActive?: boolean }
|
|
61
|
+
) {
|
|
62
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
63
|
+
`/api/asset/item/categories/id/${id}`,
|
|
64
|
+
{
|
|
65
|
+
method: "PATCH",
|
|
66
|
+
body: options,
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function deleteById(id: string) {
|
|
72
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
73
|
+
`/api/asset/item/categories/id/${id}`,
|
|
74
|
+
{
|
|
75
|
+
method: "DELETE",
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
category,
|
|
82
|
+
add,
|
|
83
|
+
getAll,
|
|
84
|
+
getById,
|
|
85
|
+
updateById,
|
|
86
|
+
deleteById,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export default function useTag() {
|
|
2
|
+
const tag = ref<TTag>({
|
|
3
|
+
_id: "",
|
|
4
|
+
name: "",
|
|
5
|
+
normalizedName: "",
|
|
6
|
+
type: "public",
|
|
7
|
+
orgId: "",
|
|
8
|
+
categoryPath: "",
|
|
9
|
+
status: "pending",
|
|
10
|
+
usageCount: 0,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function add(
|
|
14
|
+
value: Pick<TTag, "name" | "type"> &
|
|
15
|
+
Partial<Pick<TTag, "orgId" | "categoryPath">>
|
|
16
|
+
) {
|
|
17
|
+
return useNuxtApp().$api<Record<string, any>>(`/api/tags`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
body: value,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getAll(options?: {
|
|
24
|
+
search?: string;
|
|
25
|
+
page?: number;
|
|
26
|
+
limit?: number;
|
|
27
|
+
type?: string;
|
|
28
|
+
orgId?: string;
|
|
29
|
+
status?: string;
|
|
30
|
+
categoryPath?: string;
|
|
31
|
+
}) {
|
|
32
|
+
return useNuxtApp().$api<Record<string, any>>(`/api/tags`, {
|
|
33
|
+
method: "GET",
|
|
34
|
+
query: {
|
|
35
|
+
search: options?.search ?? "",
|
|
36
|
+
page: options?.page ?? 1,
|
|
37
|
+
limit: options?.limit ?? 10,
|
|
38
|
+
type: options?.type ?? "",
|
|
39
|
+
orgId: options?.orgId ?? "",
|
|
40
|
+
status: options?.status ?? "",
|
|
41
|
+
categoryPath: options?.categoryPath ?? "",
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getById(id: string) {
|
|
47
|
+
return useNuxtApp().$api<Record<string, any>>(`/api/tags/id/${id}`, {
|
|
48
|
+
method: "GET",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function updateById(
|
|
53
|
+
id: string,
|
|
54
|
+
options: Partial<
|
|
55
|
+
Pick<TTag, "name" | "type" | "orgId" | "categoryPath" | "status">
|
|
56
|
+
>
|
|
57
|
+
) {
|
|
58
|
+
return useNuxtApp().$api<Record<string, any>>(`/api/tags/id/${id}`, {
|
|
59
|
+
method: "PATCH",
|
|
60
|
+
body: options,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function deleteById(id: string) {
|
|
65
|
+
return useNuxtApp().$api<Record<string, any>>(`/api/tags/id/${id}`, {
|
|
66
|
+
method: "DELETE",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
tag,
|
|
72
|
+
add,
|
|
73
|
+
getAll,
|
|
74
|
+
getById,
|
|
75
|
+
updateById,
|
|
76
|
+
deleteById,
|
|
77
|
+
};
|
|
78
|
+
}
|
package/package.json
CHANGED
package/pages/index.vue
CHANGED
package/types/asset.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
declare type TAssetItemCategory =
|
|
2
|
+
| "facility"
|
|
3
|
+
| "equipment"
|
|
4
|
+
| "vehicle"
|
|
5
|
+
| "tool"
|
|
6
|
+
| "supply";
|
|
7
|
+
|
|
8
|
+
declare type TAssetItemPurpose = "internal" | "for-sale" | "for-rent";
|
|
9
|
+
|
|
10
|
+
declare type TAssetItemTrackMode = "individual" | "quantity";
|
|
11
|
+
|
|
12
|
+
declare type TAssetItemStatus = "active" | "archived";
|
|
13
|
+
|
|
14
|
+
declare type TAssetItem = {
|
|
15
|
+
_id?: ObjectId;
|
|
16
|
+
name: string;
|
|
17
|
+
itemRef?: string;
|
|
18
|
+
category: TAssetItemCategory;
|
|
19
|
+
tags: string[];
|
|
20
|
+
purpose: TAssetItemPurpose;
|
|
21
|
+
trackMode: TAssetItemTrackMode;
|
|
22
|
+
sku?: string;
|
|
23
|
+
price?: number;
|
|
24
|
+
unit: string;
|
|
25
|
+
quantityOnHand: number;
|
|
26
|
+
reserved: number;
|
|
27
|
+
locationId?: string;
|
|
28
|
+
assignedTo?: string;
|
|
29
|
+
status?: TAssetItemStatus;
|
|
30
|
+
createdAt?: string;
|
|
31
|
+
updatedAt?: string;
|
|
32
|
+
deletedAt?: string;
|
|
33
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare type TCategoryLevel = "department" | "category" | "subcategory";
|
|
2
|
+
|
|
3
|
+
declare type TCategory = {
|
|
4
|
+
_id?: string;
|
|
5
|
+
level: TCategoryLevel;
|
|
6
|
+
name: string;
|
|
7
|
+
displayName: string;
|
|
8
|
+
parentId?: string;
|
|
9
|
+
path: string;
|
|
10
|
+
isActive: boolean;
|
|
11
|
+
createdAt?: Date | string;
|
|
12
|
+
updatedAt?: Date | string;
|
|
13
|
+
};
|
package/types/tag.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare type TTag = {
|
|
2
|
+
_id?: string;
|
|
3
|
+
name: string;
|
|
4
|
+
normalizedName: string;
|
|
5
|
+
type: "public" | "private";
|
|
6
|
+
orgId?: string;
|
|
7
|
+
categoryPath?: string;
|
|
8
|
+
status?: "active" | "pending";
|
|
9
|
+
usageCount?: number;
|
|
10
|
+
createdAt?: Date | string;
|
|
11
|
+
updatedAt?: Date | string;
|
|
12
|
+
};
|