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 CHANGED
@@ -1,12 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createModule } 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: vue-modular make <module-name>");
9
- process.exit(1);
10
- }
7
+ const [command, input] = args;
11
8
 
12
- createModule(args[1]);
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
- <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,15 +1,10 @@
1
- // lib/templates/page.template.js
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 }} List</PageTitle>
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
- Delete Selected ({{ selectedIds.length }})
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-if="!isLoading"
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="onDelete(row)" class="text-red-600 cursor-pointer">
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
- <AddModal />
79
- <EditModal />
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 { computed, defineAsyncComponent } from 'vue'
88
- import { use${pluralName}Query } from '../queries/use${pluralName}Query'
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('./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'))
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
- const store = use${Name}Store()
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 } = use${pluralName}Query(page, perPage, filters)
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 } = use${Name}Mutations(store.moduleName)
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 rows = computed(() => data.value?.data?.data ?? [])
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: { 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,28 @@
1
- export default ({ name, Name }) => `import { defineStore } from 'pinia'
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('${name}', () => {
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
- isDeleteModal,
11
+ title,
11
12
  handleToggleModal,
12
13
  handleReset,
13
14
  } = useModalHelpers()
14
15
 
15
- const moduleName = '${Name}'
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
- modalWidth,
25
+ title,
26
26
  handleToggleModal,
27
27
  handleReset,
28
28
  }
@@ -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.0.4",
3
+ "version": "2.0.0",
4
4
  "description": "Vue.js module generator",
5
5
  "main": "lib/generator.js",
6
6
  "bin": {
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,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
- };