@vc-shell/create-vc-app 1.0.203 → 1.0.205

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.
Files changed (26) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/index.js +190 -180
  3. package/dist/templates/base/_package.json +5 -5
  4. package/dist/templates/mocks/sample-data/constants.ts +98 -0
  5. package/dist/templates/mocks/sample-data/index.ts +2 -0
  6. package/dist/templates/mocks/sample-data/methods.ts +65 -0
  7. package/dist/templates/sample/classic-module/composables/index.ts +2 -0
  8. package/dist/templates/sample/classic-module/composables/useDetails/index.ts +41 -0
  9. package/dist/templates/sample/classic-module/composables/useList/index.ts +41 -0
  10. package/dist/templates/sample/classic-module/index.ts +8 -0
  11. package/dist/templates/sample/classic-module/locales/en.json +59 -0
  12. package/dist/templates/sample/classic-module/locales/index.ts +2 -0
  13. package/dist/templates/sample/classic-module/pages/details.vue +257 -0
  14. package/dist/templates/sample/classic-module/pages/index.ts +2 -0
  15. package/dist/templates/sample/classic-module/pages/list.vue +267 -0
  16. package/dist/templates/sample/dynamic-module/composables/index.ts +2 -0
  17. package/dist/templates/sample/dynamic-module/composables/useDetails/index.ts +49 -0
  18. package/dist/templates/sample/dynamic-module/composables/useList/index.ts +54 -0
  19. package/dist/templates/sample/dynamic-module/index.ts +8 -0
  20. package/dist/templates/sample/dynamic-module/locales/en.json +69 -0
  21. package/dist/templates/sample/dynamic-module/locales/index.ts +2 -0
  22. package/dist/templates/sample/dynamic-module/pages/details.ts +100 -0
  23. package/dist/templates/sample/dynamic-module/pages/index.ts +4 -0
  24. package/dist/templates/sample/dynamic-module/pages/list.ts +81 -0
  25. package/dist/templates/sample/overrides/main.ts +53 -0
  26. package/package.json +2 -2
@@ -0,0 +1,98 @@
1
+ interface MockedItem {
2
+ imgSrc: string;
3
+ name: string;
4
+ id: string;
5
+ description: string;
6
+ price: number;
7
+ salePrice: number;
8
+ guid: string;
9
+ currency: {
10
+ name: string;
11
+ };
12
+ }
13
+
14
+ interface MockedQuery {
15
+ keyword?: string;
16
+ }
17
+
18
+ const mockedItems: MockedItem[] = [
19
+ {
20
+ id: "beb211ea-1379-5a79-832c-9577ad387feb",
21
+ name: "Product 1",
22
+ imgSrc: "https://via.placeholder.com/150",
23
+ description: "Product 1 description",
24
+ price: 100,
25
+ salePrice: 90,
26
+ guid: "a1e26bcd-2704-5a3f-b97c-3e32e3d77a38",
27
+ currency: {
28
+ name: "USD",
29
+ },
30
+ },
31
+ {
32
+ id: "44b96758-792a-5449-b649-f2a0cd614c4b",
33
+ name: "Product 2",
34
+ imgSrc: "https://via.placeholder.com/150",
35
+ description: "Product 2 description",
36
+ price: 200,
37
+ salePrice: 90,
38
+ guid: "0ab995c0-3798-5011-a4ae-498e4c683cfd",
39
+ currency: {
40
+ name: "USD",
41
+ },
42
+ },
43
+ {
44
+ id: "605b2bfb-ba13-5687-91d5-0261b4b60524",
45
+ name: "Product 3",
46
+ imgSrc: "https://via.placeholder.com/150",
47
+ description: "Product 3 description",
48
+ price: 300,
49
+ salePrice: 90,
50
+ guid: "8a9b6954-7bf7-58b2-a146-c68e06fa7bb4",
51
+ currency: {
52
+ name: "USD",
53
+ },
54
+ },
55
+ {
56
+ id: "5ca8185c-0c28-59bf-ab40-991f134e6db3",
57
+ name: "Product 4",
58
+ imgSrc: "https://via.placeholder.com/150",
59
+ description: "Product 4 description",
60
+ price: 400,
61
+ salePrice: 90,
62
+ guid: "261364c5-f976-5c86-a92c-b0f0ea14efc7",
63
+ currency: {
64
+ name: "USD",
65
+ },
66
+ },
67
+ {
68
+ id: "077930cb-0874-5f85-9a5b-daafdd0bf860",
69
+ name: "Product 5",
70
+ imgSrc: "https://via.placeholder.com/150",
71
+ description: "Product 5 description",
72
+ price: 500,
73
+ salePrice: 90,
74
+ guid: "74d302ef-31e6-5432-91f1-d92c87ee2d2a",
75
+ currency: {
76
+ name: "USD",
77
+ },
78
+ },
79
+ ];
80
+
81
+ const currencyOptions = [
82
+ {
83
+ value: "USD",
84
+ label: "USD",
85
+ },
86
+ {
87
+ value: "EUR",
88
+ label: "EUR",
89
+ },
90
+ {
91
+ value: "GBP",
92
+ label: "GBP",
93
+ },
94
+ ];
95
+
96
+ export { mockedItems, currencyOptions };
97
+
98
+ export type { MockedItem, MockedQuery };
@@ -0,0 +1,2 @@
1
+ export * from './constants'
2
+ export * from './methods'
@@ -0,0 +1,65 @@
1
+ import { type MockedItem, type MockedQuery, mockedItems } from ".";
2
+
3
+ export function loadMockItemsList(query: MockedQuery) {
4
+ return new Promise((resolve: (value: MockedItem[]) => void) => {
5
+ setTimeout(() => resolve(mockedItems), 1000);
6
+ }).then((res) => {
7
+ res = res.filter((x) => {
8
+ if (query.keyword) {
9
+ return x.name.toLowerCase().includes(query.keyword.toLowerCase());
10
+ }
11
+ return true;
12
+ });
13
+
14
+ return { results: res, totalCount: res.length };
15
+ });
16
+ }
17
+
18
+ export async function loadMockItem(args?: { id: string }) {
19
+ return new Promise((resolve: (value: MockedItem) => void) => {
20
+ setTimeout(() => {
21
+ const findMockedItem = mockedItems.find((x) => x.id === args?.id);
22
+
23
+ if (findMockedItem) resolve({ ...findMockedItem });
24
+ }, 1000);
25
+ });
26
+ }
27
+
28
+ export async function removeMockItem(args: { id: string }) {
29
+ new Promise((resolve: (value: boolean) => void) => {
30
+ setTimeout(() => {
31
+ const index = mockedItems.findIndex((x) => x.id === args.id);
32
+
33
+ if (index > -1) {
34
+ mockedItems.splice(index, 1);
35
+ resolve(true);
36
+ } else {
37
+ resolve(false);
38
+ }
39
+ }, 1000);
40
+ });
41
+ }
42
+
43
+ export async function addNewMockItem(args: MockedItem) {
44
+ return new Promise((resolve: (value: MockedItem) => void) => {
45
+ setTimeout(() => {
46
+ mockedItems.push(args);
47
+ resolve(args);
48
+ }, 1000);
49
+ });
50
+ }
51
+
52
+ export async function updateMockItem(args: MockedItem) {
53
+ return new Promise((resolve: (value: MockedItem) => void) => {
54
+ setTimeout(() => {
55
+ const index = mockedItems.findIndex((x) => x.id === args.id);
56
+
57
+ if (index > -1) {
58
+ mockedItems[index] = args;
59
+ resolve(args);
60
+ } else {
61
+ resolve(args);
62
+ }
63
+ }, 1000);
64
+ });
65
+ }
@@ -0,0 +1,2 @@
1
+ export { default as useList } from "./useList";
2
+ export { default as useDetails } from "./useDetails";
@@ -0,0 +1,41 @@
1
+ import { Ref, computed, ref } from "vue";
2
+ import { useAsync, useLoading } from "@vc-shell/framework";
3
+ import {
4
+ MockedItem,
5
+ addNewMockItem,
6
+ currencyOptions,
7
+ loadMockItem,
8
+ removeMockItem,
9
+ updateMockItem,
10
+ } from "../../sample-data";
11
+
12
+ export default () => {
13
+ const item = ref({}) as Ref<MockedItem>;
14
+
15
+ const { loading: itemLoading, action: getItem } = useAsync<{ id: string }>(async (payload) => {
16
+ item.value = await loadMockItem(payload);
17
+ });
18
+
19
+ const { loading: saveLoading, action: saveItem } = useAsync<MockedItem, MockedItem | void>(async (payload) => {
20
+ if (payload) {
21
+ return payload.id ? await updateMockItem(payload) : await addNewMockItem(payload);
22
+ }
23
+ });
24
+
25
+ const { loading: removeLoading, action: removeItem } = useAsync<{ id: string }>(async (payload) => {
26
+ if (payload) {
27
+ return await removeMockItem(payload);
28
+ }
29
+ });
30
+
31
+ const loading = useLoading(itemLoading, saveLoading, removeLoading);
32
+
33
+ return {
34
+ item: computed(() => item.value),
35
+ loading: computed(() => loading.value),
36
+ currencyOptions: computed(() => currencyOptions),
37
+ getItem,
38
+ saveItem,
39
+ removeItem,
40
+ };
41
+ };
@@ -0,0 +1,41 @@
1
+ import { Ref, computed, ref } from "vue";
2
+ import { useAsync, useLoading } from "@vc-shell/framework";
3
+ import { MockedItem, MockedQuery, loadMockItemsList, removeMockItem } from "../../sample-data";
4
+
5
+ export interface useClassicAppList {
6
+ data: Ref<MockedItem[]>;
7
+ loading: Ref<boolean>;
8
+ totalCount: Ref<number>;
9
+ pages: Ref<number>;
10
+ currentPage: number;
11
+ getItems: (query: MockedQuery) => void;
12
+ removeItems: (args: { ids: string[] }) => void;
13
+ }
14
+
15
+ export default (): useClassicAppList => {
16
+ const result = ref() as Ref<{ results: MockedItem[]; totalCount: number }>;
17
+
18
+ const { loading: itemLoading, action: getItems } = useAsync<MockedQuery>(async (query) => {
19
+ if (query) result.value = await loadMockItemsList(query);
20
+ });
21
+
22
+ const { loading: removeLoading, action: removeItems } = useAsync<{ ids: string[] }>(async (args) => {
23
+ if (args) {
24
+ for (const id of args.ids) {
25
+ await removeMockItem({ id });
26
+ }
27
+ }
28
+ });
29
+
30
+ const loading = useLoading(itemLoading, removeLoading);
31
+
32
+ return {
33
+ data: computed(() => result.value?.results),
34
+ loading: computed(() => loading.value),
35
+ totalCount: computed(() => result.value?.totalCount),
36
+ pages: computed(() => Math.ceil(result.value?.totalCount / 20)),
37
+ currentPage: 0 / Math.max(1, 20) + 1,
38
+ getItems,
39
+ removeItems,
40
+ };
41
+ };
@@ -0,0 +1,8 @@
1
+ import * as pages from "./pages";
2
+ import * as locales from "./locales";
3
+ import { createAppModule } from "@vc-shell/framework";
4
+
5
+ export default createAppModule(pages, locales);
6
+
7
+ export * from "./pages";
8
+ export * from "./composables";
@@ -0,0 +1,59 @@
1
+ {
2
+ "SAMPLE_APP": {
3
+ "MENU": {
4
+ "TITLE": "Sample"
5
+ },
6
+ "PAGES": {
7
+ "LIST": {
8
+ "TITLE": "Sample list",
9
+ "TOOLBAR": {
10
+ "REFRESH": "Refresh",
11
+ "REMOVE": "Remove"
12
+ },
13
+ "SEARCH": {
14
+ "PLACEHOLDER": "Search keywords"
15
+ },
16
+ "TABLE": {
17
+ "TOTALS": "Count:",
18
+ "HEADER": {
19
+ "IMAGE": "Image",
20
+ "PRODUCT_NAME": "Product name",
21
+ "DESCRIPTION": "Description",
22
+ "PRICE": "Price",
23
+ "SALE_PRICE": "Sale price",
24
+ "CURRENCY": "Currency"
25
+ },
26
+ "ACTIONS": {
27
+ "DELETE": "Delete"
28
+ }
29
+ }
30
+ },
31
+ "DETAILS": {
32
+ "TITLE": {
33
+ "DETAILS": " details",
34
+ "LOADING": "Loading..."
35
+ },
36
+ "TOOLBAR": {
37
+ "SAVE": "Save",
38
+ "DELETE": "Delete"
39
+ },
40
+ "FIELDS": {
41
+ "NAME": "Name",
42
+ "CONTENT": "Content",
43
+ "GUID": "GUID",
44
+ "DESCRIPTION": "Description",
45
+ "PRICE": "Price",
46
+ "SALE_PRICE": "Sale price"
47
+ }
48
+ },
49
+ "ALERTS": {
50
+ "CLOSE_CONFIRMATION": "You have unsaved changes. Close anyway?",
51
+ "DELETE": "Are you sure you want to delete this item?",
52
+ "DELETE_SELECTED_CONFIRMATION": {
53
+ "MESSAGE": "Are you sure you want to delete {count} selected items?",
54
+ "ALL": "all {totalCount}"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,2 @@
1
+ import * as en from "./en.json";
2
+ export { en };
@@ -0,0 +1,257 @@
1
+ <template>
2
+ <VcBlade
3
+ v-loading="loading"
4
+ :title="title"
5
+ :expanded="expanded"
6
+ :closable="closable"
7
+ width="70%"
8
+ :toolbar-items="bladeToolbar"
9
+ @close="$emit('close:blade')"
10
+ @expand="$emit('expand:blade')"
11
+ @collapse="$emit('collapse:blade')"
12
+ >
13
+ <VcContainer class="tw-p-2">
14
+ <VcForm>
15
+ <Field
16
+ v-slot="{ errorMessage, handleChange, errors }"
17
+ name="name"
18
+ :label="$t('SAMPLE_APP.PAGES.DETAILS.FIELDS.NAME')"
19
+ rules="required"
20
+ :model-value="item.name"
21
+ >
22
+ <VcInput
23
+ v-model="item.name"
24
+ :label="$t('SAMPLE_APP.PAGES.DETAILS.FIELDS.NAME')"
25
+ required
26
+ :error="!!errors.length"
27
+ :error-message="errorMessage"
28
+ class="tw-mb-4"
29
+ @update:model-value="handleChange"
30
+ ></VcInput>
31
+ </Field>
32
+ <VcCard
33
+ header="Content"
34
+ class="tw-mb-4"
35
+ >
36
+ <div class="tw-p-4">
37
+ <Field
38
+ v-slot="{ errorMessage, handleChange, errors }"
39
+ name="guid"
40
+ :label="$t('SAMPLE_APP.PAGES.DETAILS.FIELDS.GUID')"
41
+ :rules="{
42
+ required: true,
43
+ regex: /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/,
44
+ }"
45
+ :model-value="item.guid"
46
+ >
47
+ <VcInput
48
+ v-model="item.guid"
49
+ :label="$t('SAMPLE_APP.PAGES.DETAILS.FIELDS.GUID')"
50
+ required
51
+ :error="!!errors.length"
52
+ :error-message="errorMessage"
53
+ class="tw-mb-4"
54
+ @update:model-value="handleChange"
55
+ ></VcInput>
56
+ </Field>
57
+ <Field
58
+ v-slot="{ errorMessage, handleChange, errors }"
59
+ name="description"
60
+ :label="$t('SAMPLE_APP.PAGES.DETAILS.FIELDS.DESCRIPTION')"
61
+ :rules="{
62
+ required: true,
63
+ }"
64
+ :model-value="item.description"
65
+ >
66
+ <VcTextarea
67
+ v-model="item.description"
68
+ :label="$t('SAMPLE_APP.PAGES.DETAILS.FIELDS.DESCRIPTION')"
69
+ required
70
+ :error="!!errors.length"
71
+ :error-message="errorMessage"
72
+ @update:model-value="handleChange"
73
+ ></VcTextarea>
74
+ </Field>
75
+ </div>
76
+ </VcCard>
77
+ <VcCard
78
+ v-if="item.currency"
79
+ header="Prices"
80
+ >
81
+ <VcRow class="tw-p-4 tw-gap-4">
82
+ <VcCol :size="2">
83
+ <Field
84
+ v-slot="{ errorMessage, handleChange, errors }"
85
+ name="price"
86
+ :label="$t('SAMPLE_APP.PAGES.DETAILS.FIELDS.PRICE')"
87
+ :rules="{
88
+ required: true,
89
+ }"
90
+ :model-value="item.price"
91
+ >
92
+ <VcInputCurrency
93
+ v-model="item.price"
94
+ v-model:option="item.currency.name"
95
+ :label="$t('SAMPLE_APP.PAGES.DETAILS.FIELDS.PRICE')"
96
+ required
97
+ option-value="value"
98
+ option-label="label"
99
+ :error="!!errors.length"
100
+ :error-message="errorMessage"
101
+ :options="currencyOptions"
102
+ @update:model-value="handleChange"
103
+ ></VcInputCurrency>
104
+ </Field>
105
+ </VcCol>
106
+ <VcCol :size="2">
107
+ <Field
108
+ v-slot="{ errorMessage, handleChange, errors }"
109
+ name="salePrice"
110
+ :label="$t('SAMPLE_APP.PAGES.DETAILS.FIELDS.SALE_PRICE')"
111
+ :rules="{
112
+ required: true,
113
+ }"
114
+ :model-value="item.salePrice"
115
+ >
116
+ <VcInputCurrency
117
+ v-model="item.salePrice"
118
+ v-model:option="item.currency.name"
119
+ :label="$t('SAMPLE_APP.PAGES.DETAILS.FIELDS.SALE_PRICE')"
120
+ required
121
+ option-value="value"
122
+ option-label="label"
123
+ :error="!!errors.length"
124
+ :error-message="errorMessage"
125
+ :options="currencyOptions"
126
+ @update:model-value="handleChange"
127
+ ></VcInputCurrency>
128
+ </Field>
129
+ </VcCol>
130
+ </VcRow>
131
+ </VcCard>
132
+ </VcForm>
133
+ </VcContainer>
134
+ </VcBlade>
135
+ </template>
136
+
137
+ <script lang="ts" setup>
138
+ import { IBladeToolbar, IParentCallArgs, useBeforeUnload, useBladeNavigation, usePopup } from "@vc-shell/framework";
139
+ import { useDetails } from "./../composables";
140
+ import { computed, onMounted, ref, unref, watch } from "vue";
141
+ import { Field, useForm, useIsFormDirty, useIsFormValid } from "vee-validate";
142
+ import { useI18n } from "vue-i18n";
143
+ import * as _ from "lodash-es";
144
+ import { MockedItem } from "../sample-data";
145
+
146
+ export interface Props {
147
+ expanded?: boolean;
148
+ closable?: boolean;
149
+ param?: string;
150
+ }
151
+
152
+ export interface Emits {
153
+ (event: "parent:call", args: IParentCallArgs): void;
154
+ (event: "collapse:blade"): void;
155
+ (event: "expand:blade"): void;
156
+ (event: "close:blade"): void;
157
+ }
158
+
159
+ defineOptions({
160
+ url: "/sample-details",
161
+ name: "SampleDetails",
162
+ });
163
+
164
+ const props = withDefaults(defineProps<Props>(), {
165
+ expanded: true,
166
+ closable: true,
167
+ param: undefined,
168
+ });
169
+
170
+ const emit = defineEmits<Emits>();
171
+
172
+ const { loading, getItem, saveItem, removeItem, item, currencyOptions } = useDetails();
173
+ const { showConfirmation } = usePopup();
174
+ const { onBeforeClose } = useBladeNavigation();
175
+ const { t } = useI18n({ useScope: "global" });
176
+ useForm({
177
+ validateOnMount: false,
178
+ });
179
+ useBeforeUnload(computed(() => !isDisabled.value && modified.value));
180
+
181
+ const modified = ref(false);
182
+ const isFormValid = useIsFormValid();
183
+ const isDirty = useIsFormDirty();
184
+ const isDisabled = computed(() => {
185
+ return !isDirty.value || !isFormValid.value;
186
+ });
187
+ let itemCopy: MockedItem;
188
+ const title = computed(() => {
189
+ return props.param
190
+ ? item.value?.name
191
+ ? item.value?.name + t("SAMPLE_APP.PAGES.DETAILS.TITLE.DETAILS")
192
+ : t("SAMPLE_APP.PAGES.DETAILS.TITLE.LOADING")
193
+ : "Test App" + t("SAMPLE_APP.PAGES.DETAILS.TITLE.DETAILS");
194
+ });
195
+
196
+ watch(
197
+ () => item.value,
198
+ (state) => {
199
+ if (itemCopy) {
200
+ modified.value = !_.isEqual(itemCopy, state);
201
+ }
202
+ },
203
+ { deep: true },
204
+ );
205
+
206
+ const bladeToolbar = ref<IBladeToolbar[]>([
207
+ {
208
+ id: "save",
209
+ icon: "fas fa-save",
210
+ title: "Save",
211
+ async clickHandler() {
212
+ await saveItem(item.value);
213
+ itemCopy = _.cloneDeep(item.value);
214
+ modified.value = false;
215
+ emit("parent:call", {
216
+ method: "reload",
217
+ });
218
+ emit("close:blade");
219
+ },
220
+ disabled: computed(() => !(modified.value && !isDisabled.value)),
221
+ },
222
+ {
223
+ id: "delete",
224
+ icon: "fas fa-trash",
225
+ title: "Delete",
226
+ async clickHandler() {
227
+ if (await showConfirmation(computed(() => t(`SAMPLE_APP.PAGES.ALERTS.DELETE`)))) {
228
+ if (props.param) {
229
+ await removeItem({ id: props.param });
230
+ emit("parent:call", {
231
+ method: "reload",
232
+ });
233
+
234
+ emit("close:blade");
235
+ }
236
+ }
237
+ },
238
+ },
239
+ ]);
240
+
241
+ onMounted(async () => {
242
+ if (props.param) {
243
+ await getItem({ id: props.param });
244
+ itemCopy = _.cloneDeep(item.value);
245
+ }
246
+ });
247
+
248
+ onBeforeClose(async () => {
249
+ if (!isDisabled.value && modified.value) {
250
+ return await showConfirmation(unref(computed(() => t("SAMPLE_APP.PAGES.ALERTS.CLOSE_CONFIRMATION"))));
251
+ }
252
+ });
253
+
254
+ defineExpose({
255
+ title,
256
+ });
257
+ </script>
@@ -0,0 +1,2 @@
1
+ export { default as List } from "./list.vue";
2
+ export {default as Details } from './details.vue'