vue-ready-modular 1.1.1 → 2.0.1
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/bin/cli.js +10 -14
- package/commands/make.command.js +16 -0
- package/engine/blueprint.engine.js +33 -0
- package/generators/module.generator.js +62 -0
- package/generators/submodule.generator.js +95 -0
- package/lib/templates/modal-form.template.js +85 -0
- package/lib/templates/modal-view.template.js +12 -26
- package/lib/templates/page.template.js +48 -36
- package/lib/templates/routes.template.js +6 -1
- package/lib/templates/store.template.js +14 -9
- package/lib/utils/string.js +9 -3
- package/package.json +1 -1
- package/lib/generator.js +0 -165
- package/lib/templates/data.template.js +0 -10
- package/lib/templates/modal-add.template.js +0 -56
- package/lib/templates/modal-common-form.template.js +0 -55
- package/lib/templates/modal-delete.template.js +0 -62
- package/lib/templates/modal-edit.template.js +0 -58
- package/lib/templates/mutation.template.js +0 -53
- package/lib/templates/query.template.js +0 -42
- package/lib/templates/service.template.js +0 -70
package/bin/cli.js
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { makeCommand } from "../commands/make.command.js";
|
|
4
4
|
|
|
5
5
|
const args = process.argv.slice(2);
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
console.log("Usage:");
|
|
9
|
-
console.log(" vue-modular make <module-name>");
|
|
10
|
-
console.log(" vue-modular make <parent-module>:<submodule-name>");
|
|
11
|
-
process.exit(1);
|
|
12
|
-
}
|
|
7
|
+
const [command, input] = args;
|
|
13
8
|
|
|
14
|
-
|
|
9
|
+
switch (command) {
|
|
10
|
+
case "make":
|
|
11
|
+
makeCommand(input);
|
|
12
|
+
break;
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} else {
|
|
21
|
-
createModule(input);
|
|
14
|
+
default:
|
|
15
|
+
console.log("Usage:");
|
|
16
|
+
console.log("vue-modular make module");
|
|
17
|
+
console.log("vue-modular make module:submodule");
|
|
22
18
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { generateModule } from "../generators/module.generator.js";
|
|
2
|
+
import { generateSubModule } from "../generators/submodule.generator.js";
|
|
3
|
+
|
|
4
|
+
export function makeCommand(input) {
|
|
5
|
+
if (!input) {
|
|
6
|
+
console.error("Module name required");
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (input.includes(":")) {
|
|
11
|
+
const [parent, child] = input.split(":");
|
|
12
|
+
generateSubModule(parent, child);
|
|
13
|
+
} else {
|
|
14
|
+
generateModule(input);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { createDir, createFile } from "../lib/utils/file.js";
|
|
3
|
+
|
|
4
|
+
function renderPath(templatePath, ctx) {
|
|
5
|
+
let output = templatePath;
|
|
6
|
+
|
|
7
|
+
Object.keys(ctx).forEach((key) => {
|
|
8
|
+
output = output.replaceAll(`{{${key}}}`, ctx[key]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return output;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function generateFromBlueprint(ctx, blueprint) {
|
|
15
|
+
const { basePath, folders = [], files = [] } = blueprint;
|
|
16
|
+
|
|
17
|
+
// Create folders
|
|
18
|
+
folders.forEach((folder) => {
|
|
19
|
+
const full = path.join(basePath, folder);
|
|
20
|
+
createDir(full);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Create files
|
|
24
|
+
files.forEach((file) => {
|
|
25
|
+
const filePath = renderPath(file.path, ctx);
|
|
26
|
+
|
|
27
|
+
const full = path.join(basePath, filePath);
|
|
28
|
+
|
|
29
|
+
const content = file.template(ctx);
|
|
30
|
+
|
|
31
|
+
createFile(full, content);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
|
|
4
|
+
import { generateFromBlueprint } from "../engine/blueprint.engine.js";
|
|
5
|
+
import { capitalize } from "../lib/utils/string.js";
|
|
6
|
+
|
|
7
|
+
import indexTemplate from "../lib/templates/index.template.js";
|
|
8
|
+
import routesTemplate from "../lib/templates/routes.template.js";
|
|
9
|
+
import storeTemplate from "../lib/templates/store.template.js";
|
|
10
|
+
import pageTemplate from "../lib/templates/page.template.js";
|
|
11
|
+
import formModalTemplate from "../lib/templates/modal-form.template.js";
|
|
12
|
+
import viewModalTemplate from "../lib/templates/modal-view.template.js";
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export function generateModule(name) {
|
|
16
|
+
const module = name.toLowerCase();
|
|
17
|
+
const Module = capitalize(module);
|
|
18
|
+
|
|
19
|
+
const basePath = path.join(process.cwd(), "src/modules", module);
|
|
20
|
+
|
|
21
|
+
if (fs.existsSync(basePath)) {
|
|
22
|
+
console.error(`❌ Module "${module}" already exists`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ctx = {
|
|
27
|
+
name: module,
|
|
28
|
+
Name: Module,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
generateFromBlueprint(ctx, {
|
|
32
|
+
basePath,
|
|
33
|
+
|
|
34
|
+
folders: [
|
|
35
|
+
"stores",
|
|
36
|
+
"pages/components",
|
|
37
|
+
],
|
|
38
|
+
|
|
39
|
+
files: [
|
|
40
|
+
{ path: "index.js", template: indexTemplate },
|
|
41
|
+
{ path: "routes.js", template: routesTemplate },
|
|
42
|
+
|
|
43
|
+
{ path: "stores/{{Name}}Store.js", template: storeTemplate },
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
path: "pages/{{Name}}Page.vue",
|
|
47
|
+
template: pageTemplate,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
path: "pages/components/FormModal.vue",
|
|
51
|
+
template: formModalTemplate,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
path: "pages/components/ViewModal.vue",
|
|
55
|
+
template: viewModalTemplate,
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log(`✅ Module "${module}" created`);
|
|
62
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
|
|
4
|
+
import { generateFromBlueprint } from "../engine/blueprint.engine.js";
|
|
5
|
+
import { capitalize, plural, toKebabCase } from "../lib/utils/string.js";
|
|
6
|
+
|
|
7
|
+
import pageTemplate from "../lib/templates/page.template.js";
|
|
8
|
+
import formModalTemplate from "../lib/templates/modal-form.template.js";
|
|
9
|
+
import viewModalTemplate from "../lib/templates/modal-view.template.js";
|
|
10
|
+
import storeTemplate from "../lib/templates/store.template.js";
|
|
11
|
+
|
|
12
|
+
export function generateSubModule(parentName, subName) {
|
|
13
|
+
const parent = parentName.toLowerCase();
|
|
14
|
+
const sub = subName.toLowerCase();
|
|
15
|
+
|
|
16
|
+
const Parent = capitalize(parent);
|
|
17
|
+
const Sub = capitalize(sub);
|
|
18
|
+
|
|
19
|
+
const parentPath = path.join(process.cwd(), "src/modules", parent);
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(parentPath)) {
|
|
22
|
+
console.error(`❌ Parent module "${parent}" does not exist`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ctx = {
|
|
27
|
+
name: sub,
|
|
28
|
+
Name: Sub,
|
|
29
|
+
subName,
|
|
30
|
+
parent,
|
|
31
|
+
Parent,
|
|
32
|
+
Names: plural(Sub),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function appendRoute(parentPath, subName) {
|
|
36
|
+
const routesFile = path.join(parentPath, "routes.js");
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(routesFile)) return;
|
|
39
|
+
|
|
40
|
+
let content = fs.readFileSync(routesFile, "utf-8");
|
|
41
|
+
|
|
42
|
+
// Add import line if not exists
|
|
43
|
+
const importLine = `import ${subName}Page from './pages/${subName}Page.vue'`;
|
|
44
|
+
if (!content.includes(importLine)) {
|
|
45
|
+
content = importLine + "\n" + content;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Route block to append
|
|
49
|
+
const routeBlock = `
|
|
50
|
+
{
|
|
51
|
+
path: '${plural(toKebabCase(subName))}',
|
|
52
|
+
name: '${capitalize(subName)} List',
|
|
53
|
+
component: ${subName}Page,
|
|
54
|
+
meta: {
|
|
55
|
+
permissions: ['${sub}.view', '${sub}.create', '${sub}.edit', '${sub}.delete'],
|
|
56
|
+
},
|
|
57
|
+
},`;
|
|
58
|
+
|
|
59
|
+
// Insert before the closing bracket of children array
|
|
60
|
+
content = content.replace(
|
|
61
|
+
/children:\s*\[([\s\S]*?)\]/m,
|
|
62
|
+
(match, inner) => `children: [${inner}${routeBlock}\n]`,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
fs.writeFileSync(routesFile, content);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
generateFromBlueprint(ctx, {
|
|
69
|
+
basePath: parentPath,
|
|
70
|
+
|
|
71
|
+
folders: [`pages/${subName}Parts`],
|
|
72
|
+
|
|
73
|
+
files: [
|
|
74
|
+
{ path: "stores/{{subName}}Store.js", template: storeTemplate },
|
|
75
|
+
|
|
76
|
+
{
|
|
77
|
+
path: `pages/{{subName}}Page.vue`,
|
|
78
|
+
template: pageTemplate,
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
{
|
|
82
|
+
path: `pages/{{subName}}Parts/FormModal.vue`,
|
|
83
|
+
template: formModalTemplate,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
path: `pages/{{subName}}Parts/ViewModal.vue`,
|
|
87
|
+
template: viewModalTemplate,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
appendRoute(parentPath, subName);
|
|
93
|
+
|
|
94
|
+
console.log(`✅ Submodule "${sub}" created in "${parent}"`);
|
|
95
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// lib/templates/add-modal.template.js
|
|
2
|
+
export default ({ name, Name, subName = null, parent = null }) => `
|
|
3
|
+
<template>
|
|
4
|
+
<BaseModal
|
|
5
|
+
:isVisible="store.isModal || store.isEditModal"
|
|
6
|
+
:title="store.title"
|
|
7
|
+
@close="store.handleToggleModal"
|
|
8
|
+
:className="'w-full xl:max-w-[50vw]'"
|
|
9
|
+
>
|
|
10
|
+
<ScrollableLayout height="680px">
|
|
11
|
+
|
|
12
|
+
<form @submit.prevent="handleSubmit" class="space-y-4">
|
|
13
|
+
|
|
14
|
+
<div class="space-y-2">
|
|
15
|
+
<BaseLabel for="name">Name</BaseLabel>
|
|
16
|
+
<BaseInput
|
|
17
|
+
id="name"
|
|
18
|
+
v-model="formData.name"
|
|
19
|
+
:placeholder="'Enter ${subName} name'"
|
|
20
|
+
:required="true"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<!-- Actions -->
|
|
27
|
+
<div class="flex justify-end gap-2 py-5">
|
|
28
|
+
<BaseButton
|
|
29
|
+
class="bg-yellow-600 hover:bg-yellow-700"
|
|
30
|
+
type="button"
|
|
31
|
+
@click="store.handleToggleModal"
|
|
32
|
+
>Cancel</BaseButton>
|
|
33
|
+
<BaseButton type="submit" :disabled="submitLoading || updateLoading">
|
|
34
|
+
<span v-if="submitLoading || updateLoading">{{ submitSavingText }}</span>
|
|
35
|
+
<span v-else>{{ submitText }}</span>
|
|
36
|
+
</BaseButton>
|
|
37
|
+
</div>
|
|
38
|
+
</form>
|
|
39
|
+
</ScrollableLayout>
|
|
40
|
+
|
|
41
|
+
</BaseModal>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<script setup>
|
|
45
|
+
|
|
46
|
+
import { useCrudForm } from '@/shared/composables/useCrudForm'
|
|
47
|
+
import { useCrudSubmit } from '@/shared/composables/useCrudSubmit'
|
|
48
|
+
import { useCrudMutations } from '@/shared/composables/useCrudMutations'
|
|
49
|
+
|
|
50
|
+
const props = defineProps({
|
|
51
|
+
extraData: {
|
|
52
|
+
type: Object,
|
|
53
|
+
default: () => ({}),
|
|
54
|
+
},
|
|
55
|
+
store: {
|
|
56
|
+
type: Object,
|
|
57
|
+
required: true,
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
const defaultFormData = {
|
|
63
|
+
name: 'Test Name',
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// API
|
|
67
|
+
const api = useCrudMutations(props.store.moduleName, {
|
|
68
|
+
onSuccess() {
|
|
69
|
+
props.store.handleToggleModal()
|
|
70
|
+
resetForm()
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
const { formData, resetForm } = useCrudForm(props.store, defaultFormData)
|
|
76
|
+
|
|
77
|
+
// SUBMIT
|
|
78
|
+
const { handleSubmit, submitLoading, updateLoading, submitText, submitSavingText } = useCrudSubmit(
|
|
79
|
+
props.store,
|
|
80
|
+
api,
|
|
81
|
+
formData,
|
|
82
|
+
resetForm
|
|
83
|
+
)
|
|
84
|
+
</script>
|
|
85
|
+
`;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// lib/templates/view-modal.template.js
|
|
2
|
-
export default ({ name, Name }) => `
|
|
2
|
+
export default ({ name, Name, subName=null, parent = null }) => `
|
|
3
3
|
<template>
|
|
4
4
|
<BaseModal
|
|
5
5
|
:isVisible="store.isViewModal"
|
|
6
6
|
:title="\`View \${store.moduleName} Details\`"
|
|
7
|
-
:maxWidth="\`\${store.modalWidth || '60vw'}\`"
|
|
8
7
|
@close="store.handleToggleModal"
|
|
8
|
+
:className="'max-w-[95vw] xl:max-w-[80vw]'"
|
|
9
9
|
>
|
|
10
|
-
<
|
|
10
|
+
<ScrollableLayout>
|
|
11
|
+
<pre>{{ store.item }}</pre>
|
|
11
12
|
<div class="space-y-2">
|
|
12
13
|
<p class="text-lg">
|
|
13
14
|
<strong>ID:</strong>
|
|
@@ -27,33 +28,18 @@ export default ({ name, Name }) => `
|
|
|
27
28
|
</p>
|
|
28
29
|
</div>
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{{ store.item?.name }} Clients
|
|
33
|
-
</h2>
|
|
34
|
-
|
|
35
|
-
<BaseTable
|
|
36
|
-
v-if="store.item?.clients"
|
|
37
|
-
:columns="columns"
|
|
38
|
-
:rows="store.item.clients"
|
|
39
|
-
/>
|
|
40
|
-
</ViewModalLayout>
|
|
31
|
+
|
|
32
|
+
</ScrollableLayout>
|
|
41
33
|
</BaseModal>
|
|
42
34
|
</template>
|
|
43
35
|
|
|
44
36
|
<script setup>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
defineProps({
|
|
38
|
+
store: {
|
|
39
|
+
type: Object,
|
|
40
|
+
required: true,
|
|
41
|
+
},
|
|
42
|
+
})
|
|
50
43
|
|
|
51
|
-
const { columns } = useCrudTable(store, [
|
|
52
|
-
{ key: 'name', label: 'Client' },
|
|
53
|
-
{ key: 'email', label: 'Email' },
|
|
54
|
-
{ key: 'phone', label: 'Phone' },
|
|
55
|
-
{ key: 'client_id', label: 'Client ID' },
|
|
56
|
-
{ key: 'country', label: '${Name}' },
|
|
57
|
-
])
|
|
58
44
|
</script>
|
|
59
45
|
`;
|
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
import { plural, capitalize } from "../utils/string.js";
|
|
3
|
-
|
|
4
|
-
export default ({ name, Name, parent=null }) => {
|
|
5
|
-
const pluralName = capitalize(plural(Name)); // Country -> Countries
|
|
6
|
-
|
|
1
|
+
export default ({ name, Name, parent = null, subName = null }) => {
|
|
7
2
|
return `<template>
|
|
8
3
|
<div class="min-h-screen bg-gray-50">
|
|
9
4
|
<!-- Page Header -->
|
|
10
5
|
<div class="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
11
|
-
<div>
|
|
12
|
-
<PageTitle>{{ store.moduleName }}
|
|
6
|
+
<div class="capitalize">
|
|
7
|
+
<PageTitle>{{ store.moduleName }} Management</PageTitle>
|
|
13
8
|
</div>
|
|
14
|
-
<BaseButton @click="store.handleToggleModal('add')">
|
|
9
|
+
<BaseButton v-can="'${name}.create'" @click="store.handleToggleModal('add')">
|
|
15
10
|
Add {{ store.moduleName }}
|
|
16
11
|
</BaseButton>
|
|
17
12
|
</div>
|
|
@@ -21,11 +16,14 @@ export default ({ name, Name, parent=null }) => {
|
|
|
21
16
|
<!-- BULK DELETE -->
|
|
22
17
|
<div>
|
|
23
18
|
<BaseButton
|
|
19
|
+
v-can="'${name}.delete'"
|
|
24
20
|
v-if="selectedIds.length"
|
|
25
21
|
class="bg-red-600 text-white hover:bg-red-700"
|
|
26
22
|
@click="bulkDelete"
|
|
23
|
+
:disabled="removeItemsLoading"
|
|
27
24
|
>
|
|
28
|
-
|
|
25
|
+
<span v-if="removeItemsLoading">Deleting...</span>
|
|
26
|
+
<span v-else>Delete Selected ({{ selectedIds.length }})</span>
|
|
29
27
|
</BaseButton>
|
|
30
28
|
</div>
|
|
31
29
|
|
|
@@ -33,32 +31,37 @@ export default ({ name, Name, parent=null }) => {
|
|
|
33
31
|
<TableFilters
|
|
34
32
|
:filters="filters"
|
|
35
33
|
:has-active-filters="hasActiveFilters"
|
|
34
|
+
:available-filters="availableFilters"
|
|
36
35
|
@reset="resetFilters"
|
|
37
36
|
/>
|
|
38
37
|
</div>
|
|
39
38
|
|
|
40
39
|
<!-- Content Card -->
|
|
41
40
|
<div class="rounded-lg bg-white shadow-sm">
|
|
41
|
+
<BaseTableSkeleton v-if="isLoading" :columns="columns.length" :rows="perPage" />
|
|
42
42
|
<BaseTable
|
|
43
|
-
v-
|
|
43
|
+
v-else
|
|
44
44
|
:columns="columns"
|
|
45
45
|
:rows="rows"
|
|
46
|
+
:current-page="page"
|
|
47
|
+
:per-page="perPage"
|
|
46
48
|
show-actions
|
|
47
49
|
selectable
|
|
48
50
|
:selected-ids="selectedIds"
|
|
49
51
|
@toggleAll="(checked) => toggleAll(rows, checked)"
|
|
50
52
|
@toggleRow="toggleRow"
|
|
53
|
+
@onRowClick="onView"
|
|
51
54
|
>
|
|
52
55
|
<template #actions="{ row }">
|
|
53
|
-
<button @click="onView(row)" class="text-blue-600 cursor-pointer">
|
|
56
|
+
<button v-can="'${name}.view'" @click="onView(row)" class="text-blue-600 cursor-pointer">
|
|
54
57
|
<i class="fa fa-eye"></i>
|
|
55
58
|
</button>
|
|
56
59
|
|
|
57
|
-
<button @click="onEdit(row)" class="text-green-600 cursor-pointer">
|
|
60
|
+
<button v-can="'${name}.edit'" @click="onEdit(row)" class="text-green-600 cursor-pointer">
|
|
58
61
|
<i class="fa fa-pencil"></i>
|
|
59
62
|
</button>
|
|
60
63
|
|
|
61
|
-
<button @click="
|
|
64
|
+
<button v-can="'${name}.delete'" @click="confirmDelete(row.id)" class="text-red-600 cursor-pointer">
|
|
62
65
|
<i class="fa fa-trash"></i>
|
|
63
66
|
</button>
|
|
64
67
|
</template>
|
|
@@ -75,19 +78,15 @@ export default ({ name, Name, parent=null }) => {
|
|
|
75
78
|
@update:perPage="setPerPage"
|
|
76
79
|
/>
|
|
77
80
|
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
<ViewModal />
|
|
81
|
-
<DeleteModal />
|
|
81
|
+
<FormModal :extraData="extraData" :store="store" v-can="['${name}.create', '${name}.edit']" />
|
|
82
|
+
<ViewModal :store="store" v-can="'${name}.view'" />
|
|
82
83
|
</div>
|
|
83
84
|
</div>
|
|
84
85
|
</template>
|
|
85
86
|
|
|
86
87
|
<script setup>
|
|
87
|
-
import {
|
|
88
|
-
import { use${
|
|
89
|
-
import { use${Name}Store } from '../stores/${!parent ? name : Name}Store'
|
|
90
|
-
import { use${Name}Mutations } from '../queries/use${Name}Mutations'
|
|
88
|
+
import { computed, defineAsyncComponent } from 'vue'
|
|
89
|
+
import { use${parent ? subName : Name}Store } from '@/modules/${parent || name}/stores/${parent ? subName : Name}Store'
|
|
91
90
|
|
|
92
91
|
import { usePagination } from '@/shared/composables/usePagination'
|
|
93
92
|
import { useBulkDelete } from '@/shared/composables/useBulkDelete'
|
|
@@ -95,13 +94,17 @@ import { useCrudTable } from '@/shared/composables/useCrudTable'
|
|
|
95
94
|
import { useTableFilters } from '@/shared/composables/useTableFilters'
|
|
96
95
|
|
|
97
96
|
import TableFilters from '@/shared/components/ui/TableFilters.vue'
|
|
97
|
+
import { useDeleteWithConfirm } from '@/shared/composables/useDeleteWithConfirm'
|
|
98
|
+
import { useCrudMutations } from '@/shared/composables/useCrudMutations'
|
|
99
|
+
import { useCrudQuery } from '@/shared/composables/useCrudQuery'
|
|
100
|
+
import { useSyncDynamicFilters } from '@/shared/composables/useSyncDynamicFilters'
|
|
101
|
+
|
|
98
102
|
|
|
99
|
-
const ViewModal = defineAsyncComponent(() => import('
|
|
100
|
-
const
|
|
101
|
-
const EditModal = defineAsyncComponent(() => import('./components/EditModal.vue'))
|
|
102
|
-
const DeleteModal = defineAsyncComponent(() => import('./components/DeleteModal.vue'))
|
|
103
|
+
const ViewModal = defineAsyncComponent(() => import('./${!parent ? "components" : `${subName}Parts`}/ViewModal.vue'))
|
|
104
|
+
const FormModal = defineAsyncComponent(() => import('./${!parent ? "components" : `${subName}Parts`}/FormModal.vue'))
|
|
103
105
|
|
|
104
|
-
|
|
106
|
+
|
|
107
|
+
const store = use${parent ? subName : Name}Store()
|
|
105
108
|
|
|
106
109
|
/* ---------------- Filters ---------------- */
|
|
107
110
|
const { filters, hasActiveFilters, resetFilters } = useTableFilters({
|
|
@@ -115,23 +118,32 @@ const pagination = usePagination()
|
|
|
115
118
|
const { page, perPage, total, showing, links, setPage, setPerPage } = pagination
|
|
116
119
|
|
|
117
120
|
/* ---------------- Query ---------------- */
|
|
118
|
-
const { data, isLoading } =
|
|
121
|
+
const { data, rows,extraData, tableColumns, availableFilters, isLoading } = useCrudQuery(
|
|
122
|
+
store.moduleName,
|
|
123
|
+
page,
|
|
124
|
+
perPage,
|
|
125
|
+
filters
|
|
126
|
+
)
|
|
119
127
|
pagination.bindMeta(data)
|
|
128
|
+
useSyncDynamicFilters(availableFilters, filters)
|
|
120
129
|
|
|
121
130
|
/* ---------------- Mutations ---------------- */
|
|
122
|
-
const { removeItems } =
|
|
131
|
+
const { remove, removeItems, removeItemsLoading } = useCrudMutations(store.moduleName)
|
|
123
132
|
|
|
124
133
|
/* ---------------- Bulk Delete ---------------- */
|
|
125
|
-
const { selectedIds, toggleAll, toggleRow, bulkDelete } = useBulkDelete(removeItems
|
|
126
|
-
confirmText: 'Are you sure to delete selected records?',
|
|
127
|
-
})
|
|
134
|
+
const { selectedIds, toggleAll, toggleRow, bulkDelete } = useBulkDelete(removeItems)
|
|
128
135
|
|
|
129
136
|
/* ---------------- Table ---------------- */
|
|
130
|
-
const { columns, onView, onEdit, onDelete } = useCrudTable(store, [
|
|
131
|
-
{ key: 'name', label: '${Name}' },
|
|
132
|
-
])
|
|
133
137
|
|
|
134
|
-
const
|
|
138
|
+
const computedFields = computed(() => {
|
|
139
|
+
const cols = tableColumns.value
|
|
140
|
+
return Array.isArray(cols) ? cols : []
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const { confirmDelete } = useDeleteWithConfirm(remove)
|
|
144
|
+
|
|
145
|
+
const { columns, onView, onEdit } = useCrudTable(store, computedFields)
|
|
146
|
+
|
|
135
147
|
</script>
|
|
136
148
|
`;
|
|
137
149
|
};
|
|
@@ -11,12 +11,17 @@ export default [
|
|
|
11
11
|
{
|
|
12
12
|
path: '/${pathName}',
|
|
13
13
|
component: DashboardLayout,
|
|
14
|
-
meta: {
|
|
14
|
+
meta: {
|
|
15
|
+
requiresAuth: true,
|
|
16
|
+
},
|
|
15
17
|
children: [
|
|
16
18
|
{
|
|
17
19
|
path: '',
|
|
18
20
|
name: '${routeName}',
|
|
19
21
|
component: ${Name}Page,
|
|
22
|
+
meta: {
|
|
23
|
+
permissions: ['${name}.view', '${name}.create', '${name}.edit', '${name}.delete'],
|
|
24
|
+
},
|
|
20
25
|
},
|
|
21
26
|
],
|
|
22
27
|
},
|
|
@@ -1,28 +1,33 @@
|
|
|
1
|
-
export default ({
|
|
1
|
+
export default ({
|
|
2
|
+
name,
|
|
3
|
+
Name,
|
|
4
|
+
subName = null,
|
|
5
|
+
}) => `import { defineStore } from 'pinia'
|
|
2
6
|
import { useModalHelpers } from '@/shared/composables/useModalHelpers'
|
|
3
7
|
|
|
4
|
-
export const use${Name}Store = defineStore('${
|
|
8
|
+
export const use${subName || Name}Store = defineStore('${subName || Name}', () => {
|
|
9
|
+
|
|
10
|
+
const moduleName = '${subName || name}'
|
|
11
|
+
|
|
5
12
|
const {
|
|
6
13
|
item,
|
|
14
|
+
type,
|
|
7
15
|
isModal,
|
|
8
16
|
isViewModal,
|
|
9
17
|
isEditModal,
|
|
10
|
-
|
|
18
|
+
title,
|
|
11
19
|
handleToggleModal,
|
|
12
20
|
handleReset,
|
|
13
|
-
} = useModalHelpers()
|
|
14
|
-
|
|
15
|
-
const moduleName = '${Name}'
|
|
16
|
-
const modalWidth = '30vw'
|
|
21
|
+
} = useModalHelpers(moduleName)
|
|
17
22
|
|
|
18
23
|
return {
|
|
19
24
|
item,
|
|
25
|
+
type,
|
|
20
26
|
isModal,
|
|
21
27
|
isViewModal,
|
|
22
28
|
isEditModal,
|
|
23
|
-
isDeleteModal,
|
|
24
29
|
moduleName,
|
|
25
|
-
|
|
30
|
+
title,
|
|
26
31
|
handleToggleModal,
|
|
27
32
|
handleReset,
|
|
28
33
|
}
|
package/lib/utils/string.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
// lib/utils/string.js
|
|
2
|
-
import pluralize from
|
|
2
|
+
import pluralize from "pluralize";
|
|
3
3
|
|
|
4
|
-
export const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1)
|
|
4
|
+
export const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
5
5
|
|
|
6
6
|
// pluralize dynamically
|
|
7
|
-
export const plural = (str) => pluralize(str.toLowerCase())
|
|
7
|
+
export const plural = (str) => pluralize(str.toLowerCase());
|
|
8
|
+
|
|
9
|
+
export const toKebabCase = (str) => {
|
|
10
|
+
return str
|
|
11
|
+
.replace(/([a-z])([A-Z])/g, "$1-$2") // CountryDetail -> Country-Detail
|
|
12
|
+
.toLowerCase(); // country-detail
|
|
13
|
+
};
|
package/package.json
CHANGED
package/lib/generator.js
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import { plural, capitalize } from "./utils/string.js";
|
|
4
|
-
|
|
5
|
-
import { createDir, createFile } from "./utils/file.js";
|
|
6
|
-
|
|
7
|
-
// Templates
|
|
8
|
-
import indexTemplate from "./templates/index.template.js";
|
|
9
|
-
import routesTemplate from "./templates/routes.template.js";
|
|
10
|
-
import storeTemplate from "./templates/store.template.js";
|
|
11
|
-
import dataTemplate from "./templates/data.template.js";
|
|
12
|
-
import serviceTemplate from "./templates/service.template.js";
|
|
13
|
-
import queryTemplate from "./templates/query.template.js";
|
|
14
|
-
import mutationTemplate from "./templates/mutation.template.js";
|
|
15
|
-
import pageTemplate from "./templates/page.template.js";
|
|
16
|
-
import addModalTemplate from "./templates/modal-add.template.js";
|
|
17
|
-
import commonFormTemplate from "./templates/modal-common-form.template.js";
|
|
18
|
-
import editModalTemplate from "./templates/modal-edit.template.js";
|
|
19
|
-
import viewModalTemplate from "./templates/modal-view.template.js";
|
|
20
|
-
import deleteModalTemplate from "./templates/modal-delete.template.js";
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Generates a modular Vue.js module with a full structure.
|
|
24
|
-
* @param {string} name - Module name (e.g., "country")
|
|
25
|
-
*/
|
|
26
|
-
export function createModule(name) {
|
|
27
|
-
const module = name.toLowerCase();
|
|
28
|
-
const Module = capitalize(module);
|
|
29
|
-
|
|
30
|
-
const basePath = path.join(process.cwd(), "src/modules", module);
|
|
31
|
-
|
|
32
|
-
// Prevent overwriting existing module
|
|
33
|
-
if (fs.existsSync(basePath)) {
|
|
34
|
-
console.error(`❌ Module "${module}" already exists`);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// -----------------------------
|
|
39
|
-
// 1. Create folder structure
|
|
40
|
-
// -----------------------------
|
|
41
|
-
const folders = [
|
|
42
|
-
`${basePath}/stores`,
|
|
43
|
-
`${basePath}/pages/components`,
|
|
44
|
-
`${basePath}/data`,
|
|
45
|
-
`${basePath}/services`,
|
|
46
|
-
`${basePath}/queries`,
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
folders.forEach(createDir);
|
|
50
|
-
|
|
51
|
-
// -----------------------------
|
|
52
|
-
// 2. Prepare template context
|
|
53
|
-
// -----------------------------
|
|
54
|
-
const ctx = { name: module, Name: Module };
|
|
55
|
-
|
|
56
|
-
// -----------------------------
|
|
57
|
-
// 3. Create core module files
|
|
58
|
-
// -----------------------------
|
|
59
|
-
createFile(`${basePath}/index.js`, indexTemplate(ctx));
|
|
60
|
-
createFile(`${basePath}/routes.js`, routesTemplate(ctx));
|
|
61
|
-
createFile(`${basePath}/stores/${module}Store.js`, storeTemplate(ctx));
|
|
62
|
-
createFile(`${basePath}/data/${module}Data.js`, dataTemplate(ctx));
|
|
63
|
-
createFile(`${basePath}/services/${module}Service.js`, serviceTemplate(ctx));
|
|
64
|
-
createFile(
|
|
65
|
-
`${basePath}/queries/use${capitalize(plural(Module))}Query.js`,
|
|
66
|
-
queryTemplate(ctx)
|
|
67
|
-
);
|
|
68
|
-
createFile(
|
|
69
|
-
`${basePath}/queries/use${Module}Mutations.js`,
|
|
70
|
-
mutationTemplate(ctx)
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
// -----------------------------
|
|
74
|
-
// 4. Create page & modal components
|
|
75
|
-
// -----------------------------
|
|
76
|
-
createFile(`${basePath}/pages/${Module}Page.vue`, pageTemplate(ctx));
|
|
77
|
-
createFile(
|
|
78
|
-
`${basePath}/pages/components/AddModal.vue`,
|
|
79
|
-
addModalTemplate(ctx)
|
|
80
|
-
);
|
|
81
|
-
createFile(
|
|
82
|
-
`${basePath}/pages/components/CommonForm.vue`,
|
|
83
|
-
commonFormTemplate(ctx)
|
|
84
|
-
);
|
|
85
|
-
createFile(
|
|
86
|
-
`${basePath}/pages/components/EditModal.vue`,
|
|
87
|
-
editModalTemplate(ctx)
|
|
88
|
-
);
|
|
89
|
-
createFile(
|
|
90
|
-
`${basePath}/pages/components/ViewModal.vue`,
|
|
91
|
-
viewModalTemplate(ctx)
|
|
92
|
-
);
|
|
93
|
-
createFile(
|
|
94
|
-
`${basePath}/pages/components/DeleteModal.vue`,
|
|
95
|
-
deleteModalTemplate(ctx)
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
console.log(`✅ Module "${module}" created successfully`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function createSubModule(parentName, subName) {
|
|
102
|
-
const parent = parentName.toLowerCase();
|
|
103
|
-
const sub = subName.toLowerCase();
|
|
104
|
-
|
|
105
|
-
const Parent = capitalize(parent);
|
|
106
|
-
const Sub = capitalize(sub);
|
|
107
|
-
|
|
108
|
-
const parentPath = path.join(process.cwd(), "src/modules", parent);
|
|
109
|
-
|
|
110
|
-
if (!fs.existsSync(parentPath)) {
|
|
111
|
-
console.error(`❌ Parent module "${parent}" does not exist`);
|
|
112
|
-
process.exit(1);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const ctx = { name: sub, Name: subName, parent: parent, Parent: Parent };
|
|
116
|
-
|
|
117
|
-
// -----------------------------
|
|
118
|
-
// 1. Create subModuleParts folder ONLY for components
|
|
119
|
-
// -----------------------------
|
|
120
|
-
const componentsFolder = `${parentPath}/pages/${subName}Parts`;
|
|
121
|
-
createDir(componentsFolder);
|
|
122
|
-
|
|
123
|
-
// -----------------------------
|
|
124
|
-
// 2. Create Page
|
|
125
|
-
// -----------------------------
|
|
126
|
-
createFile(`${parentPath}/pages/${subName}Page.vue`, pageTemplate(ctx));
|
|
127
|
-
|
|
128
|
-
// -----------------------------
|
|
129
|
-
// 3. Create Components in subModuleParts
|
|
130
|
-
// -----------------------------
|
|
131
|
-
createFile(`${componentsFolder}/AddModal.vue`, addModalTemplate(ctx));
|
|
132
|
-
createFile(`${componentsFolder}/EditModal.vue`, editModalTemplate(ctx));
|
|
133
|
-
createFile(`${componentsFolder}/ViewModal.vue`, viewModalTemplate(ctx));
|
|
134
|
-
createFile(`${componentsFolder}/DeleteModal.vue`, deleteModalTemplate(ctx));
|
|
135
|
-
createFile(`${componentsFolder}/CommonForm.vue`, commonFormTemplate(ctx));
|
|
136
|
-
|
|
137
|
-
// -----------------------------
|
|
138
|
-
// 4. Services, Store, Query, Mutation, Data
|
|
139
|
-
// -----------------------------
|
|
140
|
-
createFile(
|
|
141
|
-
`${parentPath}/services/${subName}Service.js`,
|
|
142
|
-
serviceTemplate(ctx)
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
createFile(
|
|
146
|
-
`${parentPath}/stores/${subName}Store.js`,
|
|
147
|
-
storeTemplate(ctx)
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
createFile(
|
|
151
|
-
`${parentPath}/queries/use${capitalize(plural(Sub))}Query.js`,
|
|
152
|
-
queryTemplate(ctx)
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
createFile(
|
|
156
|
-
`${parentPath}/queries/use${subName}Mutations.js`,
|
|
157
|
-
mutationTemplate(ctx)
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
createFile(`${parentPath}/data/${subName}Data.js`, dataTemplate(ctx));
|
|
161
|
-
|
|
162
|
-
console.log(
|
|
163
|
-
`✅ Submodule "${subName}" created inside "${parent}" (components in ${subName}Parts)`
|
|
164
|
-
);
|
|
165
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
// lib/templates/add-modal.template.js
|
|
2
|
-
export default ({ name, Name }) => `
|
|
3
|
-
<template>
|
|
4
|
-
<BaseModal
|
|
5
|
-
:isVisible="store.isModal"
|
|
6
|
-
:title="\`Add \${store.moduleName}\`"
|
|
7
|
-
:maxWidth="\`\${store.modalWidth}\`"
|
|
8
|
-
@close="store.handleToggleModal"
|
|
9
|
-
>
|
|
10
|
-
<CommonForm
|
|
11
|
-
v-model:formData="formData"
|
|
12
|
-
:onSubmit="handleSubmit"
|
|
13
|
-
:onCancel="store.handleToggleModal"
|
|
14
|
-
/>
|
|
15
|
-
</BaseModal>
|
|
16
|
-
</template>
|
|
17
|
-
|
|
18
|
-
<script setup>
|
|
19
|
-
import { ref } from 'vue'
|
|
20
|
-
import CommonForm from './CommonForm.vue'
|
|
21
|
-
import { use${Name}Mutations } from '@/modules/${name}/queries/use${Name}Mutations'
|
|
22
|
-
import app from '@/shared/config/appConfig'
|
|
23
|
-
import { use${Name}Store } from '@/modules/${name}/store/${name}Store'
|
|
24
|
-
|
|
25
|
-
const store = use${Name}Store()
|
|
26
|
-
|
|
27
|
-
// Default Form Data
|
|
28
|
-
const defaultFormData = {
|
|
29
|
-
name: 'Test Name',
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Initialize formData
|
|
33
|
-
const formData = ref(
|
|
34
|
-
app.moduleLocal
|
|
35
|
-
? { ...defaultFormData }
|
|
36
|
-
: Object.fromEntries(
|
|
37
|
-
Object.keys(defaultFormData).map((key) => [key, ''])
|
|
38
|
-
)
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
const { submit } = use${Name}Mutations(store.moduleName, {
|
|
42
|
-
onSuccess() {
|
|
43
|
-
store.handleToggleModal()
|
|
44
|
-
store.handleReset(formData.value)
|
|
45
|
-
},
|
|
46
|
-
onError(error) {
|
|
47
|
-
console.log('Error:', error)
|
|
48
|
-
},
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
// Submit handler
|
|
52
|
-
const handleSubmit = async () => {
|
|
53
|
-
await submit.mutateAsync(formData.value)
|
|
54
|
-
}
|
|
55
|
-
</script>
|
|
56
|
-
`;
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// lib/templates/common-form.template.js
|
|
2
|
-
export default ({ Name }) => `
|
|
3
|
-
<template>
|
|
4
|
-
<form @submit.prevent="onSubmit" class="space-y-4">
|
|
5
|
-
<!-- ${Name} Name -->
|
|
6
|
-
<div class="space-y-2">
|
|
7
|
-
<BaseLabel for="name">Name</BaseLabel>
|
|
8
|
-
<BaseInput
|
|
9
|
-
id="name"
|
|
10
|
-
v-model="localForm.name"
|
|
11
|
-
placeholder="Eg: ${Name} Name"
|
|
12
|
-
:required="true"
|
|
13
|
-
/>
|
|
14
|
-
</div>
|
|
15
|
-
|
|
16
|
-
<!-- Actions -->
|
|
17
|
-
<div class="flex justify-end gap-2 pt-4">
|
|
18
|
-
<BaseButton
|
|
19
|
-
class="bg-yellow-600 hover:bg-yellow-700"
|
|
20
|
-
type="button"
|
|
21
|
-
@click="onCancel"
|
|
22
|
-
>
|
|
23
|
-
Cancel
|
|
24
|
-
</BaseButton>
|
|
25
|
-
<BaseButton type="submit">Save</BaseButton>
|
|
26
|
-
</div>
|
|
27
|
-
</form>
|
|
28
|
-
</template>
|
|
29
|
-
|
|
30
|
-
<script setup>
|
|
31
|
-
import { ref, watch } from 'vue'
|
|
32
|
-
|
|
33
|
-
// Props
|
|
34
|
-
const props = defineProps({
|
|
35
|
-
formData: { type: Object, required: true },
|
|
36
|
-
onSubmit: { type: Function, required: true },
|
|
37
|
-
onCancel: { type: Function, required: true },
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
// Emit event to sync formData
|
|
41
|
-
const emit = defineEmits(['update:formData'])
|
|
42
|
-
|
|
43
|
-
// Local reactive copy
|
|
44
|
-
const localForm = ref({ ...props.formData })
|
|
45
|
-
|
|
46
|
-
// Sync to parent when local changes
|
|
47
|
-
watch(
|
|
48
|
-
localForm,
|
|
49
|
-
(newVal) => {
|
|
50
|
-
emit('update:formData', newVal)
|
|
51
|
-
},
|
|
52
|
-
{ deep: true }
|
|
53
|
-
)
|
|
54
|
-
</script>
|
|
55
|
-
`;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// lib/templates/delete-modal.template.js
|
|
2
|
-
export default ({ name, Name }) => `
|
|
3
|
-
<template>
|
|
4
|
-
<BaseModal
|
|
5
|
-
:isVisible="store.isDeleteModal"
|
|
6
|
-
:title="\`Are you sure you want to delete this \${store.moduleName}?\`"
|
|
7
|
-
:maxWidth="\`\${store.modalWidth}\`"
|
|
8
|
-
@close="store.handleToggleModal"
|
|
9
|
-
>
|
|
10
|
-
<form @submit.prevent="handleSubmit" class="space-y-4">
|
|
11
|
-
<div class="space-y-2">
|
|
12
|
-
<p class="text-lg">
|
|
13
|
-
<strong>ID:</strong>
|
|
14
|
-
{{ store.item.id }}
|
|
15
|
-
</p>
|
|
16
|
-
<p class="text-lg">
|
|
17
|
-
<strong>Name:</strong>
|
|
18
|
-
{{ store.item.name }}
|
|
19
|
-
</p>
|
|
20
|
-
</div>
|
|
21
|
-
|
|
22
|
-
<!-- Actions -->
|
|
23
|
-
<div class="flex justify-end gap-2 pt-4">
|
|
24
|
-
<BaseButton
|
|
25
|
-
class="bg-yellow-600 hover:bg-yellow-700"
|
|
26
|
-
type="button"
|
|
27
|
-
@click="store.handleToggleModal"
|
|
28
|
-
>
|
|
29
|
-
Cancel
|
|
30
|
-
</BaseButton>
|
|
31
|
-
|
|
32
|
-
<BaseButton
|
|
33
|
-
type="submit"
|
|
34
|
-
class="bg-red-600 hover:bg-red-700"
|
|
35
|
-
>
|
|
36
|
-
Delete
|
|
37
|
-
</BaseButton>
|
|
38
|
-
</div>
|
|
39
|
-
</form>
|
|
40
|
-
</BaseModal>
|
|
41
|
-
</template>
|
|
42
|
-
|
|
43
|
-
<script setup>
|
|
44
|
-
import { use${Name}Mutations } from '@/modules/${name}/queries/use${Name}Mutations'
|
|
45
|
-
import { use${Name}Store } from '@/modules/${name}/store/${name}Store'
|
|
46
|
-
|
|
47
|
-
const store = use${Name}Store()
|
|
48
|
-
|
|
49
|
-
const { remove } = use${Name}Mutations(store.moduleName, {
|
|
50
|
-
onSuccess() {
|
|
51
|
-
store.handleToggleModal() // close modal
|
|
52
|
-
},
|
|
53
|
-
onError(error) {
|
|
54
|
-
console.log('Custom error handling', error)
|
|
55
|
-
},
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
const handleSubmit = async () => {
|
|
59
|
-
await remove.mutateAsync(store.item.id)
|
|
60
|
-
}
|
|
61
|
-
</script>
|
|
62
|
-
`;
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// lib/templates/edit-modal.template.js
|
|
2
|
-
export default ({ name, Name }) => `
|
|
3
|
-
<template>
|
|
4
|
-
<BaseModal
|
|
5
|
-
:isVisible="store.isEditModal"
|
|
6
|
-
:title="\`Edit \${store.moduleName}\`"
|
|
7
|
-
:maxWidth="\`\${store.modalWidth}\`"
|
|
8
|
-
@close="store.handleToggleModal"
|
|
9
|
-
>
|
|
10
|
-
<CommonForm
|
|
11
|
-
v-model:formData="formData"
|
|
12
|
-
:onSubmit="handleSubmit"
|
|
13
|
-
:onCancel="store.handleToggleModal"
|
|
14
|
-
/>
|
|
15
|
-
</BaseModal>
|
|
16
|
-
</template>
|
|
17
|
-
|
|
18
|
-
<script setup>
|
|
19
|
-
import { ref, watch } from 'vue'
|
|
20
|
-
import CommonForm from './CommonForm.vue'
|
|
21
|
-
import { use${Name}Mutations } from '@/modules/${name}/queries/use${Name}Mutations'
|
|
22
|
-
import { use${Name}Store } from '@/modules/${name}/store/${name}Store'
|
|
23
|
-
|
|
24
|
-
const store = use${Name}Store()
|
|
25
|
-
|
|
26
|
-
// Form state
|
|
27
|
-
const formData = ref({
|
|
28
|
-
id: '',
|
|
29
|
-
name: '',
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
// Populate form when store.item updates
|
|
33
|
-
watch(
|
|
34
|
-
() => store.item,
|
|
35
|
-
(item) => {
|
|
36
|
-
if (item) {
|
|
37
|
-
Object.assign(formData.value, item)
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
{ immediate: true }
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
// Mutation handler
|
|
44
|
-
const { update } = use${Name}Mutations(store.moduleName, {
|
|
45
|
-
onSuccess() {
|
|
46
|
-
store.handleToggleModal() // close modal
|
|
47
|
-
},
|
|
48
|
-
onError(error) {
|
|
49
|
-
console.log('Custom error handling:', error)
|
|
50
|
-
},
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
// Submit
|
|
54
|
-
const handleSubmit = async () => {
|
|
55
|
-
await update.mutateAsync(formData.value)
|
|
56
|
-
}
|
|
57
|
-
</script>
|
|
58
|
-
`;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// lib/templates/use-mutations.template.js
|
|
2
|
-
export default ({ name, Name }) => `
|
|
3
|
-
import { useMutation, useQueryClient } from '@tanstack/vue-query'
|
|
4
|
-
import {
|
|
5
|
-
submitData,
|
|
6
|
-
updateData,
|
|
7
|
-
deleteItem,
|
|
8
|
-
bulkDelete
|
|
9
|
-
} from '../services/${name}Service'
|
|
10
|
-
import { toast } from '@/shared/config/toastConfig'
|
|
11
|
-
|
|
12
|
-
export function use${Name}Mutations(moduleName, options = {}) {
|
|
13
|
-
const queryClient = useQueryClient()
|
|
14
|
-
|
|
15
|
-
const handleSuccess = (data, variables) => {
|
|
16
|
-
toast.success(\`\${moduleName} operation successful\`)
|
|
17
|
-
queryClient.invalidateQueries(['${name}s'])
|
|
18
|
-
options.onSuccess?.(data, variables)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const handleError = (error) => {
|
|
22
|
-
console.error(error)
|
|
23
|
-
toast.error(\`Request Failed: \${error?.message || 'Unknown error'}\`)
|
|
24
|
-
options.onError?.(error)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const submit = useMutation({
|
|
28
|
-
mutationFn: submitData,
|
|
29
|
-
onSuccess: handleSuccess,
|
|
30
|
-
onError: handleError,
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
const update = useMutation({
|
|
34
|
-
mutationFn: updateData,
|
|
35
|
-
onSuccess: handleSuccess,
|
|
36
|
-
onError: handleError,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
const remove = useMutation({
|
|
40
|
-
mutationFn: deleteItem,
|
|
41
|
-
onSuccess: handleSuccess,
|
|
42
|
-
onError: handleError,
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const removeItems = useMutation({
|
|
46
|
-
mutationFn: bulkDelete,
|
|
47
|
-
onSuccess: handleSuccess,
|
|
48
|
-
onError: handleError,
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
return { submit, update, remove, removeItems }
|
|
52
|
-
}
|
|
53
|
-
`;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { plural, capitalize } from "../utils/string.js";
|
|
2
|
-
|
|
3
|
-
export default ({ name, Name }) => {
|
|
4
|
-
const pluralName = plural(name); // country → countries, city → cities
|
|
5
|
-
|
|
6
|
-
return `import { useQuery } from '@tanstack/vue-query'
|
|
7
|
-
import { fetchAll } from '../services/${name}Service'
|
|
8
|
-
import { computed, unref } from 'vue'
|
|
9
|
-
|
|
10
|
-
export function use${capitalize(pluralName)}Query(pageRef, perPageRef, filtersRef) {
|
|
11
|
-
const page = computed(() => unref(pageRef))
|
|
12
|
-
const perPage = computed(() => unref(perPageRef))
|
|
13
|
-
const search = computed(() => unref(filtersRef)?.searchQuery?.trim())
|
|
14
|
-
const fromDate = computed(() => unref(filtersRef)?.from_date)
|
|
15
|
-
const toDate = computed(() => unref(filtersRef)?.to_date)
|
|
16
|
-
const cacheKey = '${pluralName}'
|
|
17
|
-
|
|
18
|
-
return useQuery({
|
|
19
|
-
queryKey: computed(() => [
|
|
20
|
-
cacheKey,
|
|
21
|
-
page.value,
|
|
22
|
-
perPage.value,
|
|
23
|
-
search.value && search.value.length >= 3 ? search.value : 'all',
|
|
24
|
-
fromDate.value || 'all',
|
|
25
|
-
toDate.value || 'all',
|
|
26
|
-
]),
|
|
27
|
-
queryFn: () => fetchAll(page.value, perPage.value, {
|
|
28
|
-
search: search.value,
|
|
29
|
-
from_date: fromDate.value,
|
|
30
|
-
to_date: toDate.value,
|
|
31
|
-
}),
|
|
32
|
-
enabled: computed(() => !search.value || search.value.length >= 3),
|
|
33
|
-
staleTime: 5 * 60 * 1000,
|
|
34
|
-
cacheTime: 60 * 60 * 1000,
|
|
35
|
-
keepPreviousData: true,
|
|
36
|
-
meta: {
|
|
37
|
-
persist: true,
|
|
38
|
-
},
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
`;
|
|
42
|
-
};
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { plural } from "../utils/string.js";
|
|
2
|
-
|
|
3
|
-
export default ({ name, Name }) => {
|
|
4
|
-
const baseUrl = `/${plural(name)}`;
|
|
5
|
-
|
|
6
|
-
return `import { useApi } from '@/shared/composables/useApi'
|
|
7
|
-
import { buildUrl } from '@/shared/utils/buildUrl'
|
|
8
|
-
|
|
9
|
-
const BASE_URL = '${baseUrl}'
|
|
10
|
-
|
|
11
|
-
const response = (api) => ({
|
|
12
|
-
data: api.data.value,
|
|
13
|
-
error: api.error.value,
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
// ✅ Fetch all with pagination & filters
|
|
17
|
-
export async function fetchAll(page = 1, perPage = 10, filters = {}) {
|
|
18
|
-
const api = useApi()
|
|
19
|
-
const url = buildUrl(BASE_URL, {
|
|
20
|
-
page,
|
|
21
|
-
per_page: perPage,
|
|
22
|
-
...filters,
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
await api.sendRequest(url)
|
|
26
|
-
return response(api)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ✅ Fetch single item by ID
|
|
30
|
-
export const fetchOne = async (id) => {
|
|
31
|
-
const api = useApi()
|
|
32
|
-
await api.sendRequest(\`\${BASE_URL}/\${id}\`)
|
|
33
|
-
return response(api)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ✅ Submit new item
|
|
37
|
-
export async function submitData(payload) {
|
|
38
|
-
const api = useApi()
|
|
39
|
-
await api.sendRequest(BASE_URL, 'POST', payload)
|
|
40
|
-
if (api.error.value) throw api.error.value
|
|
41
|
-
return api.data.value
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ✅ Update existing item
|
|
45
|
-
export async function updateData(payload) {
|
|
46
|
-
const api = useApi()
|
|
47
|
-
await api.sendRequest(\`\${BASE_URL}/\${payload.id}\`, 'POST', payload, {
|
|
48
|
-
headers: { 'X-HTTP-Method-Override': 'PUT' }
|
|
49
|
-
})
|
|
50
|
-
if (api.error.value) throw api.error.value
|
|
51
|
-
return api.data.value
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ✅ Delete single item
|
|
55
|
-
export async function deleteItem(id) {
|
|
56
|
-
const api = useApi()
|
|
57
|
-
await api.sendRequest(\`\${BASE_URL}/\${id}\`, 'DELETE')
|
|
58
|
-
if (api.error.value) throw api.error.value
|
|
59
|
-
return api.data.value
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ✅ Bulk delete multiple items
|
|
63
|
-
export async function bulkDelete(ids) {
|
|
64
|
-
const api = useApi()
|
|
65
|
-
await api.sendRequest(\`\${BASE_URL}/bulk-delete\`, 'POST', { ids })
|
|
66
|
-
if (api.error.value) throw api.error.value
|
|
67
|
-
return api.data.value
|
|
68
|
-
}
|
|
69
|
-
`;
|
|
70
|
-
};
|