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 CHANGED
@@ -1,22 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createModule, createSubModule } from "../lib/generator.js";
3
+ import { makeCommand } from "../commands/make.command.js";
4
4
 
5
5
  const args = process.argv.slice(2);
6
6
 
7
- if (args[0] !== "make" || !args[1]) {
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
- const input = args[1];
9
+ switch (command) {
10
+ case "make":
11
+ makeCommand(input);
12
+ break;
15
13
 
16
- // Check if user wants a submodule
17
- if (input.includes(":")) {
18
- const [parent, child] = input.split(":");
19
- createSubModule(parent, child);
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
- <ViewModalLayout>
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
- <!-- Related Items -->
31
- <h2 class="text-lg font-semibold py-3">
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
- import { use${Name}Store } from '@/modules/${name}/store/${name}Store'
46
- import ViewModalLayout from '@/shared/components/ui/ViewModalLayout.vue'
47
- import { useCrudTable } from '@/shared/composables/useCrudTable'
48
-
49
- const store = use${Name}Store()
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
- // lib/templates/page.template.js
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 }} List</PageTitle>
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
- Delete Selected ({{ selectedIds.length }})
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-if="!isLoading"
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="onDelete(row)" class="text-red-600 cursor-pointer">
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
- <AddModal />
79
- <EditModal />
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 { computed, defineAsyncComponent } from 'vue'
88
- import { use${pluralName}Query } from '../queries/use${pluralName}Query'
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('./components/ViewModal.vue'))
100
- const AddModal = defineAsyncComponent(() => import('./components/AddModal.vue'))
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
- const store = use${Name}Store()
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 } = use${pluralName}Query(page, perPage, filters)
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 } = use${Name}Mutations(store.moduleName)
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 rows = computed(() => data.value?.data?.data ?? [])
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: { requiresAuth: true, roles: ['super_admin','admin','recruiter'] },
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 ({ name, Name }) => `import { defineStore } from 'pinia'
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('${name}', () => {
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
- isDeleteModal,
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
- modalWidth,
30
+ title,
26
31
  handleToggleModal,
27
32
  handleReset,
28
33
  }
@@ -1,7 +1,13 @@
1
1
  // lib/utils/string.js
2
- import pluralize from 'pluralize'
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-ready-modular",
3
- "version": "1.1.1",
3
+ "version": "2.0.1",
4
4
  "description": "Vue.js module generator",
5
5
  "main": "lib/generator.js",
6
6
  "bin": {
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,10 +0,0 @@
1
- export default ({ name }) => `const ${name}Data = [
2
- { id: 1, name: 'Test Name' },
3
- { id: 2, name: 'Test Name 2' },
4
- { id: 3, name: 'Test Name 3' },
5
- { id: 4, name: 'Test Name 4' },
6
- { id: 5, name: 'Test Name 5' }
7
- ]
8
-
9
- export default ${name}Data
10
- `;
@@ -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
- };