vue-ready-modular 1.0.0 → 1.0.2

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/lib/generator.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
+ import { plural, capitalize } from "./utils/string.js";
3
4
 
4
5
  import { createDir, createFile } from "./utils/file.js";
5
- import { capitalize } from "./utils/string.js";
6
6
 
7
7
  // Templates
8
8
  import indexTemplate from "./templates/index.template.js";
@@ -14,6 +14,7 @@ import queryTemplate from "./templates/query.template.js";
14
14
  import mutationTemplate from "./templates/mutation.template.js";
15
15
  import pageTemplate from "./templates/page.template.js";
16
16
  import addModalTemplate from "./templates/modal-add.template.js";
17
+ import commonFormTemplate from "./templates/modal-common-form.template.js";
17
18
  import editModalTemplate from "./templates/modal-edit.template.js";
18
19
  import viewModalTemplate from "./templates/modal-view.template.js";
19
20
  import deleteModalTemplate from "./templates/modal-delete.template.js";
@@ -60,17 +61,39 @@ export function createModule(name) {
60
61
  createFile(`${basePath}/stores/${module}Store.js`, storeTemplate(ctx));
61
62
  createFile(`${basePath}/data/${module}Data.js`, dataTemplate(ctx));
62
63
  createFile(`${basePath}/services/${module}Service.js`, serviceTemplate(ctx));
63
- createFile(`${basePath}/queries/use${Module}sQuery.js`, queryTemplate(ctx));
64
- createFile(`${basePath}/queries/use${Module}Mutations.js`, mutationTemplate(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
+ );
65
72
 
66
73
  // -----------------------------
67
74
  // 4. Create page & modal components
68
75
  // -----------------------------
69
76
  createFile(`${basePath}/pages/${Module}Page.vue`, pageTemplate(ctx));
70
- createFile(`${basePath}/pages/components/AddModal.vue`, addModalTemplate(ctx));
71
- createFile(`${basePath}/pages/components/EditModal.vue`, editModalTemplate(ctx));
72
- createFile(`${basePath}/pages/components/ViewModal.vue`, viewModalTemplate(ctx));
73
- createFile(`${basePath}/pages/components/DeleteModal.vue`, deleteModalTemplate(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
+ );
74
97
 
75
98
  console.log(`✅ Module "${module}" created successfully`);
76
99
  }
@@ -1,57 +1,54 @@
1
- export default ({ name, Name }) => `<template>
1
+ // lib/templates/add-modal.template.js
2
+ export default ({ name, Name }) => `
3
+ <template>
2
4
  <BaseModal
3
5
  :isVisible="store.isModal"
4
6
  :title="\`Add \${store.moduleName}\`"
5
7
  :maxWidth="\`\${store.modalWidth}\`"
6
8
  @close="store.handleToggleModal"
7
9
  >
8
- <form @submit.prevent="handleSubmit" class="space-y-4">
9
- <!-- ${Name} Name -->
10
- <div>
11
- <BaseLabel for="name">Name</BaseLabel>
12
- <BaseInput
13
- id="name"
14
- v-model="formData.name"
15
- :required="true"
16
- placeholder="Eg: Example Name"
17
- />
18
- </div>
19
-
20
- <!-- Actions -->
21
- <div class="flex justify-end gap-2 pt-4">
22
- <BaseButton
23
- class="bg-yellow-600 hover:bg-yellow-700"
24
- type="button"
25
- @click="store.handleToggleModal"
26
- >Cancel</BaseButton>
27
- <BaseButton type="submit">Save</BaseButton>
28
- </div>
29
- </form>
10
+ <CommonForm
11
+ v-model:formData="formData"
12
+ :onSubmit="handleSubmit"
13
+ :onCancel="store.handleToggleModal"
14
+ />
30
15
  </BaseModal>
31
16
  </template>
32
17
 
33
18
  <script setup>
34
19
  import { ref } from 'vue'
20
+ import CommonForm from './CommonForm.vue'
35
21
  import { use${Name}Mutations } from '@/modules/${name}/queries/use${Name}Mutations'
36
22
  import app from '@/shared/config/appConfig'
37
- import { use${Name}Store } from '@/modules/${name}/stores/${name}Store'
23
+ import { use${Name}Store } from '@/modules/${name}/store/${name}Store'
38
24
 
39
25
  const store = use${Name}Store()
40
26
 
41
- const formData = ref({
42
- name: app.moduleLocal ? 'Test Name' : '',
43
- })
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
+ )
44
40
 
45
41
  const { submit } = use${Name}Mutations(store.moduleName, {
46
42
  onSuccess() {
47
- store.handleToggleModal() // ✅ close modal
48
- store.handleReset(formData.value) // ✅ reset form
43
+ store.handleToggleModal()
44
+ store.handleReset(formData.value)
49
45
  },
50
- onError: (error) => {
51
- console.log('Custom error handling', error)
46
+ onError(error) {
47
+ console.log('Error:', error)
52
48
  },
53
49
  })
54
50
 
51
+ // Submit handler
55
52
  const handleSubmit = async () => {
56
53
  await submit.mutateAsync(formData.value)
57
54
  }
@@ -0,0 +1,55 @@
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,4 +1,6 @@
1
- export default ({ name, Name }) => `<template>
1
+ // lib/templates/delete-modal.template.js
2
+ export default ({ name, Name }) => `
3
+ <template>
2
4
  <BaseModal
3
5
  :isVisible="store.isDeleteModal"
4
6
  :title="\`Are you sure you want to delete this \${store.moduleName}?\`"
@@ -23,8 +25,16 @@ export default ({ name, Name }) => `<template>
23
25
  class="bg-yellow-600 hover:bg-yellow-700"
24
26
  type="button"
25
27
  @click="store.handleToggleModal"
26
- >Cancel</BaseButton>
27
- <BaseButton type="submit" class="bg-red-600 hover:bg-red-700">Delete</BaseButton>
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>
28
38
  </div>
29
39
  </form>
30
40
  </BaseModal>
@@ -32,15 +42,15 @@ export default ({ name, Name }) => `<template>
32
42
 
33
43
  <script setup>
34
44
  import { use${Name}Mutations } from '@/modules/${name}/queries/use${Name}Mutations'
35
- import { use${Name}Store } from '@/modules/${name}/stores/${name}Store'
45
+ import { use${Name}Store } from '@/modules/${name}/store/${name}Store'
36
46
 
37
47
  const store = use${Name}Store()
38
48
 
39
49
  const { remove } = use${Name}Mutations(store.moduleName, {
40
50
  onSuccess() {
41
- store.handleToggleModal() // close modal
51
+ store.handleToggleModal() // close modal
42
52
  },
43
- onError: (error) => {
53
+ onError(error) {
44
54
  console.log('Custom error handling', error)
45
55
  },
46
56
  })
@@ -1,67 +1,56 @@
1
- export default ({ name, Name }) => `<template>
1
+ // lib/templates/edit-modal.template.js
2
+ export default ({ name, Name }) => `
3
+ <template>
2
4
  <BaseModal
3
5
  :isVisible="store.isEditModal"
4
6
  :title="\`Edit \${store.moduleName}\`"
5
7
  :maxWidth="\`\${store.modalWidth}\`"
6
8
  @close="store.handleToggleModal"
7
9
  >
8
- <form @submit.prevent="handleSubmit" class="space-y-4">
9
- <!-- ${Name} Name -->
10
- <div>
11
- <BaseLabel for="name">Name</BaseLabel>
12
- <BaseInput
13
- id="name"
14
- v-model="formData.name"
15
- placeholder="Eg: Example Name"
16
- />
17
- </div>
18
-
19
- <!-- Actions -->
20
- <div class="flex justify-end gap-2 pt-4">
21
- <BaseButton
22
- class="bg-yellow-600 hover:bg-yellow-700"
23
- type="button"
24
- @click="store.handleToggleModal"
25
- >Cancel</BaseButton>
26
- <BaseButton type="submit">Save</BaseButton>
27
- </div>
28
- </form>
10
+ <CommonForm
11
+ v-model:formData="formData"
12
+ :onSubmit="handleSubmit"
13
+ :onCancel="store.handleToggleModal"
14
+ />
29
15
  </BaseModal>
30
16
  </template>
31
17
 
32
18
  <script setup>
33
19
  import { ref, watch } from 'vue'
20
+ import CommonForm from './CommonForm.vue'
34
21
  import { use${Name}Mutations } from '@/modules/${name}/queries/use${Name}Mutations'
35
- import { use${Name}Store } from '@/modules/${name}/stores/${name}Store'
22
+ import { use${Name}Store } from '@/modules/${name}/store/${name}Store'
36
23
 
37
24
  const store = use${Name}Store()
38
25
 
26
+ // Form state
39
27
  const formData = ref({
40
- name: '',
41
28
  id: '',
29
+ name: '',
42
30
  })
43
31
 
44
- // Prefill form when selected item changes
32
+ // Populate form when store.item updates
45
33
  watch(
46
34
  () => store.item,
47
- (newItem) => {
48
- if (newItem) {
49
- formData.value.name = newItem.name || ''
50
- formData.value.id = newItem.id || ''
35
+ (item) => {
36
+ if (item) {
37
+ Object.assign(formData.value, item)
51
38
  }
52
39
  },
53
40
  { immediate: true }
54
41
  )
55
42
 
43
+ // Mutation handler
56
44
  const { update } = use${Name}Mutations(store.moduleName, {
57
45
  onSuccess() {
58
- store.handleToggleModal() // close modal
46
+ store.handleToggleModal() // close modal
59
47
  },
60
- onError: (error) => {
61
- console.log('Custom error handling', error)
48
+ onError(error) {
49
+ console.log('Custom error handling:', error)
62
50
  },
63
51
  })
64
52
 
53
+ // Submit
65
54
  const handleSubmit = async () => {
66
55
  await update.mutateAsync(formData.value)
67
56
  }
@@ -1,33 +1,59 @@
1
- export default ({ name, Name }) => `<template>
1
+ // lib/templates/view-modal.template.js
2
+ export default ({ name, Name }) => `
3
+ <template>
2
4
  <BaseModal
3
5
  :isVisible="store.isViewModal"
4
6
  :title="\`View \${store.moduleName} Details\`"
5
- :maxWidth="\`\${store.modalWidth}\`"
7
+ :maxWidth="\`\${store.modalWidth || '60vw'}\`"
6
8
  @close="store.handleToggleModal"
7
9
  >
8
- <div class="space-y-2">
9
- <p class="text-lg">
10
- <strong>ID:</strong>
11
- {{ store.item.id }}
12
- </p>
13
- <p class="text-lg">
14
- <strong>Name:</strong>
15
- {{ store.item.name }}
16
- </p>
17
- <p class="text-lg">
18
- <strong>Created At:</strong>
19
- {{ store.item.created_at }}
20
- </p>
21
- <p class="text-lg">
22
- <strong>Updated At:</strong>
23
- {{ store.item.updated_at }}
24
- </p>
25
- </div>
10
+ <ViewModalLayout>
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
+ <p class="text-lg">
21
+ <strong>Created At:</strong>
22
+ {{ store.item?.created_at }}
23
+ </p>
24
+ <p class="text-lg">
25
+ <strong>Updated At:</strong>
26
+ {{ store.item?.updated_at }}
27
+ </p>
28
+ </div>
29
+
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>
26
41
  </BaseModal>
27
42
  </template>
28
43
 
29
44
  <script setup>
30
- import { use${Name}Store } from '@/modules/${name}/stores/${name}Store'
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
+
31
49
  const store = use${Name}Store()
50
+
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
+ ])
32
58
  </script>
33
59
  `;
@@ -1,8 +1,12 @@
1
- export default ({
2
- name,
3
- Name,
4
- }) => `import { useMutation, useQueryClient } from '@tanstack/vue-query'
5
- import { submitData, updateData, deleteItem } from '../services/${name}Service'
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'
6
10
  import { toast } from '@/shared/config/toastConfig'
7
11
 
8
12
  export function use${Name}Mutations(moduleName, options = {}) {
@@ -11,7 +15,7 @@ export function use${Name}Mutations(moduleName, options = {}) {
11
15
  const handleSuccess = (data, variables) => {
12
16
  toast.success(\`\${moduleName} operation successful\`)
13
17
  queryClient.invalidateQueries(['${name}s'])
14
- options.onSuccess?.(data, variables) // ✅ generic callback
18
+ options.onSuccess?.(data, variables)
15
19
  }
16
20
 
17
21
  const handleError = (error) => {
@@ -20,10 +24,30 @@ export function use${Name}Mutations(moduleName, options = {}) {
20
24
  options.onError?.(error)
21
25
  }
22
26
 
23
- const submit = useMutation({ mutationFn: submitData, onSuccess: handleSuccess, onError: handleError })
24
- const update = useMutation({ mutationFn: updateData, onSuccess: handleSuccess, onError: handleError })
25
- const remove = useMutation({ mutationFn: deleteItem, onSuccess: handleSuccess, onError: handleError })
27
+ const submit = useMutation({
28
+ mutationFn: submitData,
29
+ onSuccess: handleSuccess,
30
+ onError: handleError,
31
+ })
26
32
 
27
- return { submit, update, remove }
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 }
28
52
  }
29
53
  `;
@@ -1,21 +1,78 @@
1
- export default ({ name, Name }) => `<template>
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
+
7
+ return `<template>
2
8
  <div class="min-h-screen bg-gray-50">
9
+ <!-- Page Header -->
3
10
  <div class="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
4
- <PageTitle>{{ store.moduleName }} List</PageTitle>
11
+ <div>
12
+ <PageTitle>{{ store.moduleName }} List</PageTitle>
13
+ </div>
5
14
  <BaseButton @click="store.handleToggleModal('add')">
6
15
  Add {{ store.moduleName }}
7
16
  </BaseButton>
8
17
  </div>
9
18
 
19
+ <!-- Bulk Delete & Filters -->
20
+ <div class="flex justify-between items-center my-4">
21
+ <!-- BULK DELETE -->
22
+ <div>
23
+ <BaseButton
24
+ v-if="selectedIds.length"
25
+ class="bg-red-600 text-white hover:bg-red-700"
26
+ @click="bulkDelete"
27
+ >
28
+ Delete Selected ({{ selectedIds.length }})
29
+ </BaseButton>
30
+ </div>
31
+
32
+ <!-- FILTERS -->
33
+ <TableFilters
34
+ :filters="filters"
35
+ :has-active-filters="hasActiveFilters"
36
+ @reset="resetFilters"
37
+ />
38
+ </div>
39
+
40
+ <!-- Content Card -->
10
41
  <div class="rounded-lg bg-white shadow-sm">
11
42
  <BaseTable
12
43
  v-if="!isLoading"
13
44
  :columns="columns"
14
45
  :rows="rows"
15
46
  show-actions
16
- @view="onView"
17
- @edit="onEdit"
18
- @delete="onDelete"
47
+ selectable
48
+ :selected-ids="selectedIds"
49
+ @toggleAll="(checked) => toggleAll(rows, checked)"
50
+ @toggleRow="toggleRow"
51
+ >
52
+ <template #actions="{ row }">
53
+ <button @click="onView(row)" class="text-blue-600 cursor-pointer">
54
+ <i class="fa fa-eye"></i>
55
+ </button>
56
+
57
+ <button @click="onEdit(row)" class="text-green-600 cursor-pointer">
58
+ <i class="fa fa-pencil"></i>
59
+ </button>
60
+
61
+ <button @click="onDelete(row)" class="text-red-600 cursor-pointer">
62
+ <i class="fa fa-trash"></i>
63
+ </button>
64
+ </template>
65
+ </BaseTable>
66
+
67
+ <!-- Pagination -->
68
+ <BasePagination
69
+ v-if="!isLoading"
70
+ :total="total"
71
+ :showing="showing"
72
+ :links="links"
73
+ :per-page="perPage"
74
+ @update:page="setPage"
75
+ @update:perPage="setPerPage"
19
76
  />
20
77
 
21
78
  <AddModal />
@@ -28,8 +85,16 @@ export default ({ name, Name }) => `<template>
28
85
 
29
86
  <script setup>
30
87
  import { computed, defineAsyncComponent } from 'vue'
31
- import { use${Name}sQuery } from '../queries/use${Name}sQuery'
32
- import { use${Name}Store } from '../stores/${name}Store'
88
+ import { use${pluralName}Query } from '../queries/use${pluralName}Query'
89
+ import { use${Name}Store } from '../store/${name}Store'
90
+ import { use${Name}Mutations } from '../queries/use${Name}Mutations'
91
+
92
+ import { usePagination } from '@/shared/composables/usePagination'
93
+ import { useBulkDelete } from '@/shared/composables/useBulkDelete'
94
+ import { useCrudTable } from '@/shared/composables/useCrudTable'
95
+ import { useTableFilters } from '@/shared/composables/useTableFilters'
96
+
97
+ import TableFilters from '@/shared/components/ui/TableFilters.vue'
33
98
 
34
99
  const ViewModal = defineAsyncComponent(() => import('./components/ViewModal.vue'))
35
100
  const AddModal = defineAsyncComponent(() => import('./components/AddModal.vue'))
@@ -37,25 +102,36 @@ const EditModal = defineAsyncComponent(() => import('./components/EditModal.vue'
37
102
  const DeleteModal = defineAsyncComponent(() => import('./components/DeleteModal.vue'))
38
103
 
39
104
  const store = use${Name}Store()
40
- const { data, isLoading } = use${Name}sQuery()
41
105
 
42
- const columns = [
43
- { key: 'sl', label: 'SL' },
106
+ /* ---------------- Filters ---------------- */
107
+ const { filters, hasActiveFilters, resetFilters } = useTableFilters({
108
+ searchQuery: '',
109
+ from_date: null,
110
+ to_date: null,
111
+ })
112
+
113
+ /* ---------------- Pagination ---------------- */
114
+ const pagination = usePagination()
115
+ const { page, perPage, total, showing, links, setPage, setPerPage } = pagination
116
+
117
+ /* ---------------- Query ---------------- */
118
+ const { data, isLoading } = use${pluralName}Query(page, perPage, filters)
119
+ pagination.bindMeta(data)
120
+
121
+ /* ---------------- Mutations ---------------- */
122
+ const { removeItems } = use${Name}Mutations(store.moduleName)
123
+
124
+ /* ---------------- Bulk Delete ---------------- */
125
+ const { selectedIds, toggleAll, toggleRow, bulkDelete } = useBulkDelete(removeItems, {
126
+ confirmText: 'Are you sure to delete selected records?',
127
+ })
128
+
129
+ /* ---------------- Table ---------------- */
130
+ const { columns, onView, onEdit, onDelete } = useCrudTable(store, [
44
131
  { key: 'name', label: '${Name}' },
45
- { key: 'created_at', label: 'Created At' },
46
- { key: 'updated_at', label: 'Updated At' }
47
- ]
132
+ ])
48
133
 
49
134
  const rows = computed(() => data.value?.data?.data ?? [])
50
-
51
- function onView(row) {
52
- store.handleToggleModal('view', row)
53
- }
54
- function onEdit(row) {
55
- store.handleToggleModal('edit', row)
56
- }
57
- function onDelete(row) {
58
- store.handleToggleModal('delete', row)
59
- }
60
135
  </script>
61
136
  `;
137
+ };
@@ -1,19 +1,42 @@
1
- export default ({
2
- name,
3
- Name,
4
- }) => `import { useQuery } from '@tanstack/vue-query'
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'
5
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}'
6
17
 
7
- export function use${Name}sQuery(params = {}) {
8
18
  return useQuery({
9
- queryKey: ['${name}s', params],
10
- queryFn: () => fetchAll(params),
11
- staleTime: 5 * 60 * 1000, // 5 minutes
12
- cacheTime: 1000 * 60 * 60, // 1 hour
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,
13
35
  keepPreviousData: true,
14
36
  meta: {
15
- persist: true, // ✅ persisted
37
+ persist: true,
16
38
  },
17
39
  })
18
40
  }
19
41
  `;
42
+ };
@@ -1,21 +1,25 @@
1
- export default ({
2
- name,
3
- Name,
4
- }) => `import ${Name}Page from './pages/${Name}Page.vue'
1
+ import { plural } from "../utils/string.js";
2
+
3
+ export default ({ name, Name }) => {
4
+ const pathName = plural(name); // e.g., 'country' 'countries'
5
+ const routeName = `${Name}List`; // e.g., 'CountryList'
6
+
7
+ return `import ${Name}Page from './pages/${Name}Page.vue'
5
8
  import DashboardLayout from '@/shared/layouts/DashboardLayout.vue'
6
9
 
7
10
  export default [
8
11
  {
9
- path: '/${name}s',
12
+ path: '/${pathName}',
10
13
  component: DashboardLayout,
11
- meta: { requiresAuth: true },
14
+ meta: { requiresAuth: true, roles: ['super_admin','admin','recruiter'] },
12
15
  children: [
13
16
  {
14
17
  path: '',
15
- name: '${Name}List',
18
+ name: '${routeName}',
16
19
  component: ${Name}Page,
17
20
  },
18
21
  ],
19
22
  },
20
23
  ]
21
24
  `;
25
+ };
@@ -1,30 +1,47 @@
1
- export default ({
2
- name,
3
- Name,
4
- }) => `import { useApi } from '@/shared/composables/useApi'
1
+ import { plural } from "../utils/string.js";
5
2
 
6
- const BASE_URL = '/${name}s'
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}'
7
10
 
8
11
  const response = (api) => ({
9
12
  data: api.data.value,
10
13
  error: api.error.value,
11
14
  })
12
15
 
13
- export async function fetchAll() {
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) => {
14
31
  const api = useApi()
15
- await api.sendRequest(BASE_URL)
32
+ await api.sendRequest(\`\${BASE_URL}/\${id}\`)
16
33
  return response(api)
17
34
  }
18
35
 
36
+ // ✅ Submit new item
19
37
  export async function submitData(payload) {
20
38
  const api = useApi()
21
39
  await api.sendRequest(BASE_URL, 'POST', payload)
22
- if (api.error.value) {
23
- throw api.error.value // 🚨 so useMutation.onError triggers
24
- }
40
+ if (api.error.value) throw api.error.value
25
41
  return api.data.value
26
42
  }
27
43
 
44
+ // ✅ Update existing item
28
45
  export async function updateData(payload) {
29
46
  const api = useApi()
30
47
  await api.sendRequest(\`\${BASE_URL}/\${payload.id}\`, 'POST', payload, {
@@ -34,10 +51,20 @@ export async function updateData(payload) {
34
51
  return api.data.value
35
52
  }
36
53
 
54
+ // ✅ Delete single item
37
55
  export async function deleteItem(id) {
38
56
  const api = useApi()
39
57
  await api.sendRequest(\`\${BASE_URL}/\${id}\`, 'DELETE')
40
58
  if (api.error.value) throw api.error.value
41
59
  return api.data.value
42
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
+ }
43
69
  `;
70
+ };
@@ -2,7 +2,6 @@ export default ({ name, Name }) => `import { defineStore } from 'pinia'
2
2
  import { useModalHelpers } from '@/shared/composables/useModalHelpers'
3
3
 
4
4
  export const use${Name}Store = defineStore('${name}', () => {
5
-
6
5
  const {
7
6
  item,
8
7
  isModal,
@@ -1,3 +1,7 @@
1
- export function capitalize(str) {
2
- return str.charAt(0).toUpperCase() + str.slice(1);
3
- }
1
+ // lib/utils/string.js
2
+ import pluralize from 'pluralize'
3
+
4
+ export const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1)
5
+
6
+ // pluralize dynamically
7
+ export const plural = (str) => pluralize(str.toLowerCase())
package/package.json CHANGED
@@ -1,19 +1,25 @@
1
1
  {
2
- "name": "vue-ready-modular",
3
- "version": "1.0.0",
2
+ "name": "vue-ready-modular",
3
+ "version": "1.0.2",
4
4
  "description": "Vue.js module generator",
5
- "main": "lib/generator.js",
5
+ "main": "lib/generator.js",
6
6
  "bin": {
7
- "vue-modular": "./bin/cli.js"
7
+ "vue-modular": "./bin/cli.js"
8
8
  },
9
- "type": "module",
9
+ "type": "module",
10
10
  "scripts": {
11
11
  "test": "echo \"No tests yet\""
12
12
  },
13
- "keywords": ["vue", "generator", "module", "cli"],
13
+ "keywords": [
14
+ "vue",
15
+ "generator",
16
+ "module",
17
+ "cli"
18
+ ],
14
19
  "author": "Your Name",
15
20
  "license": "MIT",
16
- "dependencies": {
17
- "path": "^0.12.7"
21
+ "dependencies": {
22
+ "path": "^0.12.7",
23
+ "pluralize": "^8.0.0"
18
24
  }
19
25
  }