vue-ready-modular 1.0.4 → 2.0.0
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 +12 -6
- 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 +42 -33
- package/lib/templates/routes.template.js +7 -2
- package/lib/templates/store.template.js +7 -7
- package/lib/utils/string.js +9 -3
- package/package.json +1 -1
- package/lib/generator.js +0 -99
- 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,12 +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: vue-modular make <module-name>");
|
|
9
|
-
process.exit(1);
|
|
10
|
-
}
|
|
7
|
+
const [command, input] = args;
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
switch (command) {
|
|
10
|
+
case "make":
|
|
11
|
+
makeCommand(input);
|
|
12
|
+
break;
|
|
13
|
+
|
|
14
|
+
default:
|
|
15
|
+
console.log("Usage:");
|
|
16
|
+
console.log("vue-modular make module");
|
|
17
|
+
console.log("vue-modular make module:submodule");
|
|
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"
|
|
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,15 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import { plural, capitalize } from "../utils/string.js";
|
|
3
|
-
|
|
4
|
-
export default ({ name, Name }) => {
|
|
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
9
|
<BaseButton @click="store.handleToggleModal('add')">
|
|
15
10
|
Add {{ store.moduleName }}
|
|
@@ -24,8 +19,10 @@ export default ({ name, Name }) => {
|
|
|
24
19
|
v-if="selectedIds.length"
|
|
25
20
|
class="bg-red-600 text-white hover:bg-red-700"
|
|
26
21
|
@click="bulkDelete"
|
|
22
|
+
:disabled="removeItemsLoading"
|
|
27
23
|
>
|
|
28
|
-
|
|
24
|
+
<span v-if="removeItemsLoading">Deleting...</span>
|
|
25
|
+
<span v-else>Delete Selected ({{ selectedIds.length }})</span>
|
|
29
26
|
</BaseButton>
|
|
30
27
|
</div>
|
|
31
28
|
|
|
@@ -33,14 +30,16 @@ export default ({ name, Name }) => {
|
|
|
33
30
|
<TableFilters
|
|
34
31
|
:filters="filters"
|
|
35
32
|
:has-active-filters="hasActiveFilters"
|
|
33
|
+
:available-filters="availableFilters"
|
|
36
34
|
@reset="resetFilters"
|
|
37
35
|
/>
|
|
38
36
|
</div>
|
|
39
37
|
|
|
40
38
|
<!-- Content Card -->
|
|
41
39
|
<div class="rounded-lg bg-white shadow-sm">
|
|
40
|
+
<BaseTableSkeleton v-if="isLoading" :columns="columns.length" :rows="perPage" />
|
|
42
41
|
<BaseTable
|
|
43
|
-
v-
|
|
42
|
+
v-else
|
|
44
43
|
:columns="columns"
|
|
45
44
|
:rows="rows"
|
|
46
45
|
show-actions
|
|
@@ -58,7 +57,7 @@ export default ({ name, Name }) => {
|
|
|
58
57
|
<i class="fa fa-pencil"></i>
|
|
59
58
|
</button>
|
|
60
59
|
|
|
61
|
-
<button @click="
|
|
60
|
+
<button @click="confirmDelete(row.id)" class="text-red-600 cursor-pointer">
|
|
62
61
|
<i class="fa fa-trash"></i>
|
|
63
62
|
</button>
|
|
64
63
|
</template>
|
|
@@ -75,19 +74,15 @@ export default ({ name, Name }) => {
|
|
|
75
74
|
@update:perPage="setPerPage"
|
|
76
75
|
/>
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
<ViewModal />
|
|
81
|
-
<DeleteModal />
|
|
77
|
+
<FormModal :extraData="extraData" :store="store" />
|
|
78
|
+
<ViewModal :store="store" />
|
|
82
79
|
</div>
|
|
83
80
|
</div>
|
|
84
81
|
</template>
|
|
85
82
|
|
|
86
83
|
<script setup>
|
|
87
|
-
import {
|
|
88
|
-
import { use${
|
|
89
|
-
import { use${Name}Store } from '../stores/${name}Store'
|
|
90
|
-
import { use${Name}Mutations } from '../queries/use${Name}Mutations'
|
|
84
|
+
import { computed, defineAsyncComponent } from 'vue'
|
|
85
|
+
import { use${parent ? subName : Name}Store } from '@/modules/${parent || name}/stores/${parent ? subName : Name}Store'
|
|
91
86
|
|
|
92
87
|
import { usePagination } from '@/shared/composables/usePagination'
|
|
93
88
|
import { useBulkDelete } from '@/shared/composables/useBulkDelete'
|
|
@@ -95,13 +90,18 @@ import { useCrudTable } from '@/shared/composables/useCrudTable'
|
|
|
95
90
|
import { useTableFilters } from '@/shared/composables/useTableFilters'
|
|
96
91
|
|
|
97
92
|
import TableFilters from '@/shared/components/ui/TableFilters.vue'
|
|
93
|
+
import { useDeleteWithConfirm } from '@/shared/composables/useDeleteWithConfirm'
|
|
94
|
+
import { useCrudMutations } from '@/shared/composables/useCrudMutations'
|
|
95
|
+
import { useCrudQuery } from '@/shared/composables/useCrudQuery'
|
|
96
|
+
import { useSyncDynamicFilters } from '@/shared/composables/useSyncDynamicFilters'
|
|
97
|
+
|
|
98
|
+
|
|
98
99
|
|
|
99
|
-
const ViewModal = defineAsyncComponent(() => import('
|
|
100
|
-
const
|
|
101
|
-
const EditModal = defineAsyncComponent(() => import('./components/EditModal.vue'))
|
|
102
|
-
const DeleteModal = defineAsyncComponent(() => import('./components/DeleteModal.vue'))
|
|
100
|
+
const ViewModal = defineAsyncComponent(() => import('./${!parent ? "components" : `${subName}Parts`}/ViewModal.vue'))
|
|
101
|
+
const FormModal = defineAsyncComponent(() => import('./${!parent ? "components" : `${subName}Parts`}/FormModal.vue'))
|
|
103
102
|
|
|
104
|
-
|
|
103
|
+
|
|
104
|
+
const store = use${parent ? subName : Name}Store()
|
|
105
105
|
|
|
106
106
|
/* ---------------- Filters ---------------- */
|
|
107
107
|
const { filters, hasActiveFilters, resetFilters } = useTableFilters({
|
|
@@ -115,23 +115,32 @@ const pagination = usePagination()
|
|
|
115
115
|
const { page, perPage, total, showing, links, setPage, setPerPage } = pagination
|
|
116
116
|
|
|
117
117
|
/* ---------------- Query ---------------- */
|
|
118
|
-
const { data, isLoading } =
|
|
118
|
+
const { data, rows,extraData, tableColumns, availableFilters, isLoading } = useCrudQuery(
|
|
119
|
+
store.moduleName,
|
|
120
|
+
page,
|
|
121
|
+
perPage,
|
|
122
|
+
filters
|
|
123
|
+
)
|
|
119
124
|
pagination.bindMeta(data)
|
|
125
|
+
useSyncDynamicFilters(availableFilters, filters)
|
|
120
126
|
|
|
121
127
|
/* ---------------- Mutations ---------------- */
|
|
122
|
-
const { removeItems } =
|
|
128
|
+
const { remove, removeItems, removeItemsLoading } = useCrudMutations(store.moduleName)
|
|
123
129
|
|
|
124
130
|
/* ---------------- Bulk Delete ---------------- */
|
|
125
|
-
const { selectedIds, toggleAll, toggleRow, bulkDelete } = useBulkDelete(removeItems
|
|
126
|
-
confirmText: 'Are you sure to delete selected records?',
|
|
127
|
-
})
|
|
131
|
+
const { selectedIds, toggleAll, toggleRow, bulkDelete } = useBulkDelete(removeItems)
|
|
128
132
|
|
|
129
133
|
/* ---------------- Table ---------------- */
|
|
130
|
-
const { columns, onView, onEdit, onDelete } = useCrudTable(store, [
|
|
131
|
-
{ key: 'name', label: '${Name}' },
|
|
132
|
-
])
|
|
133
134
|
|
|
134
|
-
const
|
|
135
|
+
const computedFields = computed(() => {
|
|
136
|
+
const cols = tableColumns.value
|
|
137
|
+
return Array.isArray(cols) ? cols : []
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const { confirmDelete } = useDeleteWithConfirm(remove)
|
|
141
|
+
|
|
142
|
+
const { columns, onView, onEdit } = useCrudTable(store, computedFields)
|
|
143
|
+
|
|
135
144
|
</script>
|
|
136
145
|
`;
|
|
137
146
|
};
|
|
@@ -2,7 +2,7 @@ import { plural } from "../utils/string.js";
|
|
|
2
2
|
|
|
3
3
|
export default ({ name, Name }) => {
|
|
4
4
|
const pathName = plural(name); // e.g., 'country' → 'countries'
|
|
5
|
-
const routeName = `${Name}List`; // e.g., 'CountryList'
|
|
5
|
+
const routeName = `${Name} List`; // e.g., 'CountryList'
|
|
6
6
|
|
|
7
7
|
return `import ${Name}Page from './pages/${Name}Page.vue'
|
|
8
8
|
import DashboardLayout from '@/shared/layouts/DashboardLayout.vue'
|
|
@@ -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,28 @@
|
|
|
1
|
-
export default ({
|
|
1
|
+
export default ({ name, Name, subName=null }) => `import { defineStore } from 'pinia'
|
|
2
2
|
import { useModalHelpers } from '@/shared/composables/useModalHelpers'
|
|
3
3
|
|
|
4
|
-
export const use${Name}Store = defineStore('${
|
|
4
|
+
export const use${subName || Name}Store = defineStore('${subName || Name}', () => {
|
|
5
5
|
const {
|
|
6
6
|
item,
|
|
7
|
+
type,
|
|
7
8
|
isModal,
|
|
8
9
|
isViewModal,
|
|
9
10
|
isEditModal,
|
|
10
|
-
|
|
11
|
+
title,
|
|
11
12
|
handleToggleModal,
|
|
12
13
|
handleReset,
|
|
13
14
|
} = useModalHelpers()
|
|
14
15
|
|
|
15
|
-
const moduleName = '${
|
|
16
|
-
const modalWidth = '30vw'
|
|
16
|
+
const moduleName = '${subName || name}'
|
|
17
17
|
|
|
18
18
|
return {
|
|
19
19
|
item,
|
|
20
|
+
type,
|
|
20
21
|
isModal,
|
|
21
22
|
isViewModal,
|
|
22
23
|
isEditModal,
|
|
23
|
-
isDeleteModal,
|
|
24
24
|
moduleName,
|
|
25
|
-
|
|
25
|
+
title,
|
|
26
26
|
handleToggleModal,
|
|
27
27
|
handleReset,
|
|
28
28
|
}
|
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,99 +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
|
-
}
|
|
@@ -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
|
-
};
|