adminforth 1.1.32 → 1.1.33

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.
Files changed (98) hide show
  1. package/dist/index.js +9 -0
  2. package/dist/modules/codeInjector.js +19 -7
  3. package/dist/modules/styles.js +2 -2
  4. package/dist/spa/index.html +2 -2
  5. package/dist/spa/package-lock.json +17 -4
  6. package/dist/spa/package.json +2 -1
  7. package/dist/spa/src/App.vue +125 -45
  8. package/dist/spa/src/assets/logo.svg +19 -1
  9. package/dist/spa/src/components/AcceptModal.vue +2 -9
  10. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  11. package/dist/spa/src/components/CustomDatePicker.vue +16 -3
  12. package/dist/spa/src/components/CustomDateRangePicker.vue +11 -2
  13. package/dist/spa/src/components/Dropdown.vue +6 -1
  14. package/dist/spa/src/components/Filters.vue +40 -19
  15. package/dist/spa/src/components/ResourceForm.vue +26 -24
  16. package/dist/spa/src/components/ValueRenderer.vue +14 -8
  17. package/dist/spa/src/main.ts +1 -1
  18. package/dist/spa/src/router/index.ts +30 -20
  19. package/dist/spa/src/stores/core.ts +48 -31
  20. package/dist/spa/src/stores/modal.ts +13 -3
  21. package/dist/spa/src/utils.ts +62 -7
  22. package/dist/spa/src/views/CreateView.vue +67 -15
  23. package/dist/spa/src/views/EditView.vue +45 -11
  24. package/dist/spa/src/views/ListView.vue +82 -366
  25. package/dist/spa/src/views/LoginView.vue +16 -4
  26. package/dist/spa/src/views/ShowView.vue +105 -21
  27. package/dist/spa/tailwind.config.js +1 -1
  28. package/dist/spa/vite.config.ts +6 -0
  29. package/index.ts +11 -0
  30. package/modules/codeInjector.ts +19 -1
  31. package/modules/styles.ts +2 -2
  32. package/package.json +1 -1
  33. package/spa/index.html +1 -1
  34. package/spa/src/App.vue +2 -3
  35. package/types/AdminForthConfig.ts +15 -0
  36. package/dist/plugins/AccessControl/index.js +0 -66
  37. package/dist/plugins/AccessControl/types.js +0 -1
  38. package/dist/spa/public/favicon.ico +0 -0
  39. package/dist/spa/spa/.eslintrc.cjs +0 -14
  40. package/dist/spa/spa/.vscode/extensions.json +0 -6
  41. package/dist/spa/spa/README.md +0 -39
  42. package/dist/spa/spa/env.d.ts +0 -1
  43. package/dist/spa/spa/index.html +0 -23
  44. package/dist/spa/spa/package-lock.json +0 -4359
  45. package/dist/spa/spa/package.json +0 -47
  46. package/dist/spa/spa/postcss.config.js +0 -6
  47. package/dist/spa/spa/public/favicon.ico +0 -0
  48. package/dist/spa/spa/src/App.vue +0 -272
  49. package/dist/spa/spa/src/assets/base.css +0 -0
  50. package/dist/spa/spa/src/assets/logo.svg +0 -19
  51. package/dist/spa/spa/src/components/AcceptModal.vue +0 -45
  52. package/dist/spa/spa/src/components/Breadcrumbs.vue +0 -40
  53. package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  54. package/dist/spa/spa/src/components/CustomDatePicker.vue +0 -174
  55. package/dist/spa/spa/src/components/CustomDateRangePicker.vue +0 -218
  56. package/dist/spa/spa/src/components/CustomRangePicker.vue +0 -148
  57. package/dist/spa/spa/src/components/Dropdown.vue +0 -168
  58. package/dist/spa/spa/src/components/Filters.vue +0 -211
  59. package/dist/spa/spa/src/components/HelloWorld.vue +0 -17
  60. package/dist/spa/spa/src/components/MenuLink.vue +0 -24
  61. package/dist/spa/spa/src/components/ResourceForm.vue +0 -265
  62. package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
  63. package/dist/spa/spa/src/components/ValueRenderer.vue +0 -60
  64. package/dist/spa/spa/src/components/icons/IconCalendar.vue +0 -5
  65. package/dist/spa/spa/src/components/icons/IconCommunity.vue +0 -7
  66. package/dist/spa/spa/src/components/icons/IconDocumentation.vue +0 -7
  67. package/dist/spa/spa/src/components/icons/IconEcosystem.vue +0 -7
  68. package/dist/spa/spa/src/components/icons/IconSupport.vue +0 -7
  69. package/dist/spa/spa/src/components/icons/IconTime.vue +0 -5
  70. package/dist/spa/spa/src/components/icons/IconTooling.vue +0 -19
  71. package/dist/spa/spa/src/index.scss +0 -26
  72. package/dist/spa/spa/src/main.ts +0 -18
  73. package/dist/spa/spa/src/router/index.ts +0 -64
  74. package/dist/spa/spa/src/stores/core.ts +0 -144
  75. package/dist/spa/spa/src/stores/modal.ts +0 -48
  76. package/dist/spa/spa/src/utils.ts +0 -101
  77. package/dist/spa/spa/src/views/CreateView.vue +0 -163
  78. package/dist/spa/spa/src/views/EditView.vue +0 -165
  79. package/dist/spa/spa/src/views/ListView.vue +0 -257
  80. package/dist/spa/spa/src/views/LoginView.vue +0 -134
  81. package/dist/spa/spa/src/views/ResourceParent.vue +0 -18
  82. package/dist/spa/spa/src/views/ShowView.vue +0 -188
  83. package/dist/spa/spa/tailwind.config.js +0 -15
  84. package/dist/spa/spa/tsconfig.app.json +0 -14
  85. package/dist/spa/spa/tsconfig.json +0 -11
  86. package/dist/spa/spa/tsconfig.node.json +0 -19
  87. package/dist/spa/spa/vite.config.ts +0 -49
  88. package/dist/types.js +0 -30
  89. /package/dist/spa/{spa/public → public/assets}/favicon.png +0 -0
  90. /package/dist/spa/{spa/src → src}/components/ResourceListTable.vue +0 -0
  91. /package/dist/spa/{spa/src → src}/components/Toast.vue +0 -0
  92. /package/dist/spa/{spa/src → src}/composables/useFrontendApi.ts +0 -0
  93. /package/dist/spa/{spa/src → src}/composables/useStores.ts +0 -0
  94. /package/dist/spa/{spa/src → src}/spa_types/core.ts +0 -0
  95. /package/dist/spa/{spa/src → src}/stores/filters.ts +0 -0
  96. /package/dist/spa/{spa/src → src}/stores/toast.ts +0 -0
  97. /package/dist/spa/{spa/src → src}/stores/user.ts +0 -0
  98. /package/spa/public/{favicon.png → assets/favicon.png} +0 -0
@@ -2,11 +2,11 @@
2
2
  <!-- drawer component -->
3
3
  <div id="drawer-navigation"
4
4
 
5
- class="fixed top-14 right-0 z-40 p-4 overflow-y-auto transition-transform translate-x-full bg-white w-80 dark:bg-gray-800 shadow-xl dark:shadow-black"
5
+ class="fixed right-0 z-50 p-4 overflow-y-auto transition-transform translate-x-full bg-white w-80 dark:bg-gray-800 shadow-xl dark:shadow-black"
6
6
 
7
7
  :class="show ? 'top-0 transform-none' : ''"
8
8
  tabindex="-1" aria-labelledby="drawer-navigation-label"
9
- :style="{ height: `calc(100vh - 3.5rem)` }"
9
+ :style="{ height: `calc(100vh ` }"
10
10
  >
11
11
  <h5 id="drawer-navigation-label" class="text-base font-semibold text-gray-500 uppercase dark:text-gray-400">
12
12
  Filters
@@ -26,13 +26,13 @@
26
26
  v-if="c.foreignResource"
27
27
  :options="columnOptions[c.name] || []"
28
28
  @update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
29
- :modelValue="filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
29
+ :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
30
30
  />
31
31
  <Dropdown
32
32
  v-else-if="c.type === 'boolean'"
33
33
  :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
34
34
  @update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
35
- :modelValue="filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
35
+ :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
36
36
  />
37
37
 
38
38
  <Dropdown
@@ -40,7 +40,7 @@
40
40
  :options="c.enum"
41
41
  :allowCustom="c.allowCustom"
42
42
  @update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
43
- :modelValue="filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
43
+ :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
44
44
  />
45
45
 
46
46
  <input
@@ -54,9 +54,9 @@
54
54
  <CustomDateRangePicker
55
55
  v-else-if="['datetime'].includes(c.type)"
56
56
  :column="c"
57
- :valueStart="filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
57
+ :valueStart="filtersStore.filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
58
58
  @update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
59
- :valueEnd="filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
59
+ :valueEnd="filtersStore.filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
60
60
  @update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
61
61
  />
62
62
 
@@ -69,7 +69,7 @@
69
69
  >
70
70
 
71
71
  <CustomRangePicker
72
- v-else-if="['integer', 'decimal', 'float'].includes(c.type)"
72
+ v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
73
73
  :min="c.min"
74
74
  :max="c.max"
75
75
  :valueStart="getFilterItem({ column: c, operator: 'gte' })"
@@ -77,6 +77,23 @@
77
77
  :valueEnd="getFilterItem({ column: c, operator: 'lte' })"
78
78
  @update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
79
79
  />
80
+
81
+ <div v-else-if="['integer', 'decimal', 'float'].includes(c.type)" class="flex gap-2">
82
+ <input
83
+ type="number" aria-describedby="helper-text-explanation"
84
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
85
+ placeholder="From"
86
+ @input="setFilterItem({ column: c, operator: 'gte', value: $event.target.value || undefined })"
87
+ :value="getFilterItem({ column: c, operator: 'gte' })"
88
+ >
89
+ <input
90
+ type="number" aria-describedby="helper-text-explanation"
91
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
92
+ placeholder="To"
93
+ @input="setFilterItem({ column: c, operator: 'lte', value: $event.target.value || undefined})"
94
+ :value="getFilterItem({ column: c, operator: 'lte' })"
95
+ >
96
+ </div>
80
97
 
81
98
  </li>
82
99
  </ul>
@@ -84,15 +101,15 @@
84
101
 
85
102
  <div class="flex justify-end gap-2">
86
103
  <button
87
- :disabled="!filters.length"
104
+ :disabled="!filtersStore.filters.length"
88
105
  type="button"
89
- class="flex items-center py-1 px-3 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
106
+ class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
90
107
  @click="clear">Clear all</button>
91
108
 
92
109
  </div>
93
110
  </div>
94
111
 
95
- <div v-if="show" drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-50"
112
+ <div v-if="show" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30"
96
113
  @click="$emit('hide')">
97
114
  </div>
98
115
  </template>
@@ -105,6 +122,10 @@ import { callAdminForthApi } from '@/utils';
105
122
  import { useRouter } from 'vue-router';
106
123
  import { computedAsync } from '@vueuse/core'
107
124
  import CustomRangePicker from "@/components/CustomRangePicker.vue";
125
+ import { useFiltersStore } from '@/stores/filters';
126
+
127
+ const filtersStore = useFiltersStore();
128
+
108
129
 
109
130
  // props: columns
110
131
  // add support for v-model:filers
@@ -163,28 +184,28 @@ watch(() => props.show, (show) => {
163
184
 
164
185
  function setFilterItem({ column, operator, value }) {
165
186
 
166
- const index = props.filters.findIndex(f => f.field === column.name && f.operator === operator);
187
+ const index = filtersStore.filters.findIndex(f => f.field === column.name && f.operator === operator);
167
188
  if (value === undefined) {
168
189
  if (index !== -1) {
169
- props.filters.splice(index, 1);
190
+ filtersStore.filters.splice(index, 1);
170
191
  }
171
- emits('update:filters', [...props.filters]);
192
+ emits('update:filters', [...filtersStore.filters]);
172
193
  return;
173
194
  } else {
174
195
  if (index === -1) {
175
- props.filters.push({ field: column.name, value, operator });
196
+ filtersStore.setFilter({ field: column.name, value, operator });
176
197
  } else {
177
- props.filters[index].value = value;
198
+ filtersStore.setFilters([...filtersStore.filters.slice(0, index), { field: column.name, value, operator }, ...filtersStore.filters.slice(index + 1)])
178
199
  }
179
200
  }
180
- emits('update:filters', [...props.filters]);
201
+ emits('update:filters', [...filtersStore.filters]);
181
202
  }
182
203
 
183
204
  function getFilterItem({ column, operator }) {
184
- return props.filters.find(f => f.field === column.name && f.operator === operator)?.value || '';
205
+ return filtersStore.filters.find(f => f.field === column.name && f.operator === operator)?.value || '';
185
206
  }
186
207
 
187
208
  async function clear() {
188
- emits('update:filters', []);
209
+ filtersStore.clearFilters();
189
210
  }
190
211
  </script>
@@ -1,11 +1,11 @@
1
1
  <template>
2
- <div>
2
+ <div class="rounded-default">
3
3
 
4
4
  <div
5
- class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black"
5
+ class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black rounded-default"
6
6
  >
7
7
  <form autocomplete="off" @submit.prevent>
8
- <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
8
+ <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 ">
9
9
  <thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
10
10
  <tr>
11
11
  <th scope="col" class="px-6 py-3">
@@ -30,28 +30,28 @@
30
30
  />
31
31
  </template>
32
32
  <template v-else>
33
- <td class="px-6 py-4 whitespace-nowrap">
34
- {{ column.label }}
35
- <span :data-tooltip-target="`tooltip-show-${i}`" class="relative inline-block">
36
- <IconExclamationCircleSolid v-if="column.required[mode]" class="w-4 h-4"
37
- :class="(columnError(column) && validating) ? 'text-red-500 dark:text-red-400' : 'text-gray-400 dark:text-gray-500'"
38
- />
39
- </span>
40
- <div :id="`tooltip-show-${i}`"
41
- role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
42
- Required field
43
- <div class="tooltip-arrow" data-popper-arrow></div>
44
- </div>
33
+ <td class="px-6 py-4 whitespace-nowrap "> <!--align-top-->
34
+ {{ column.label }}
35
+ <span :data-tooltip-target="`tooltip-show-${i}`" class="relative inline-block">
36
+ <IconExclamationCircleSolid v-if="column.required[mode]" class="w-4 h-4"
37
+ :class="(columnError(column) && validating) ? 'text-red-500 dark:text-red-400' : 'text-gray-400 dark:text-gray-500'"
38
+ />
39
+ </span>
40
+ <div :id="`tooltip-show-${i}`"
41
+ role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
42
+ Required field
43
+ <div class="tooltip-arrow" data-popper-arrow></div>
44
+ </div>
45
45
  </td>
46
46
  <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative">
47
-
48
47
  <Dropdown
49
48
  single
50
49
  v-if="column.foreignResource"
51
50
  :options="columnOptions[column.name] || []"
51
+ :placeholder = "columnOptions[column.name]?.length || 'There are no options available'"
52
52
  :modelValue="currentValues[column.name]"
53
53
  @update:modelValue="setCurrentValue(column.name, $event)"
54
- />
54
+ ></Dropdown>
55
55
  <Dropdown
56
56
  single
57
57
  v-else-if="column.enum"
@@ -79,6 +79,7 @@
79
79
  v-else-if="['datetime'].includes(column.type)"
80
80
  :column="column"
81
81
  :valueStart="currentValues[column.name]"
82
+ auto-hide
82
83
  @update:valueStart="setCurrentValue(column.name, $event)"
83
84
  />
84
85
  <input
@@ -120,9 +121,6 @@
120
121
  <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
121
122
  <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
122
123
  </button>
123
-
124
-
125
-
126
124
  <div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
127
125
 
128
126
  <div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
@@ -154,7 +152,7 @@ const router = useRouter();
154
152
 
155
153
  const props = defineProps({
156
154
  loading: Boolean,
157
- resourceColumns: Object,
155
+ resource: Object,
158
156
  record: Object,
159
157
  validating: Boolean,
160
158
  customComponentsPerColumn: Object,
@@ -196,7 +194,11 @@ const columnError = (column) => {
196
194
  for (let i = 0; i < validationArray.length; i++) {
197
195
  if (validationArray[i].regExp) {
198
196
  const regExp = new RegExp(validationArray[i].regExp);
199
- if (!regExp.test(currentValues.value[column.name])) {
197
+ let value = currentValues.value[column.name];
198
+ if (value === undefined || value === null) {
199
+ value = '';
200
+ }
201
+ if (!regExp.test(value)) {
200
202
  return validationArray[i].message;
201
203
  }
202
204
  }
@@ -226,7 +228,7 @@ onMounted(() => {
226
228
 
227
229
  const columnOptions = computedAsync(async () => {
228
230
  return (await Promise.all(
229
- Object.values(props.resourceColumns).map(async (column) => {
231
+ Object.values(props.resource.columns).map(async (column) => {
230
232
  if (column.foreignResource) {
231
233
  const list = await callAdminForthApi({
232
234
  method: 'POST',
@@ -249,7 +251,7 @@ const coreStore = useCoreStore();
249
251
 
250
252
  const editableColumns = computed(() => {
251
253
  const mode = props.record ? 'edit' : 'create';
252
- return props.resourceColumns?.filter(column => column.showIn.includes(mode));
254
+ return props.resource?.columns?.filter(column => column.showIn.includes(mode));
253
255
  });
254
256
 
255
257
  const isValid = computed(() => {
@@ -1,9 +1,10 @@
1
1
  <template>
2
2
  <div>
3
+
3
4
  <span v-if="column.foreignResource">
4
- <RouterLink v-if="row[column.name]" class="font-medium text-blue-600 dark:text-blue-500 hover:brightness-110"
5
- :to="{ name: 'resource-show', params: { resourceId: column.foreignResource.resourceId, primaryKey: row[column.name].pk } }">
6
- {{ row[column.name].label }}
5
+ <RouterLink v-if="record[column.name]" class="font-medium text-blue-600 dark:text-blue-500 hover:brightness-110"
6
+ :to="{ name: 'resource-show', params: { resourceId: column.foreignResource.resourceId, primaryKey: record[column.name].pk } }">
7
+ {{ record[column.name].label }}
7
8
  </RouterLink>
8
9
  <div v-else>
9
10
  <span class="text-gray-400">-</span>
@@ -11,18 +12,18 @@
11
12
  </span>
12
13
 
13
14
  <span v-else-if="column.type === 'boolean'">
14
- <span v-if="row[column.name]" class="bg-green-100 text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">Yes</span>
15
+ <span v-if="record[column.name]" class="bg-green-100 text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">Yes</span>
15
16
  <span v-else class="bg-red-100 text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400">No</span>
16
17
  </span>
17
18
  <span v-else-if="column.enum">
18
- {{ column.enum.find(e => e.value === row[column.name])?.label }}
19
+ {{ checkEmptyValues(column.enum.find(e => e.value === record[column.name])?.label,route.meta.type) }}
19
20
  </span>
20
21
  <span v-else-if="column.type === 'datetime'">
21
- {{ formatDate(row[column.name]) }}
22
+ {{ checkEmptyValues(formatDate(record[column.name]),route.meta.type) }}
22
23
 
23
24
  </span>
24
25
  <span v-else>
25
- {{ row[column.name] }}
26
+ {{ checkEmptyValues(record[column.name],route.meta.type) }}
26
27
  </span>
27
28
  </div>
28
29
  </template>
@@ -33,17 +34,22 @@
33
34
  import dayjs from 'dayjs';
34
35
  import utc from 'dayjs/plugin/utc';
35
36
  import timezone from 'dayjs/plugin/timezone';
37
+ import {checkEmptyValues} from '@/utils';
38
+ import { useRoute, useRouter } from 'vue-router';
39
+
36
40
 
37
41
  import { useCoreStore } from '@/stores/core';
38
42
 
39
43
  const coreStore = useCoreStore();
44
+ const route = useRoute();
45
+
40
46
 
41
47
  dayjs.extend(utc);
42
48
  dayjs.extend(timezone);
43
49
 
44
50
  const props = defineProps({
45
51
  column: Object,
46
- row: Object
52
+ record: Object
47
53
  });
48
54
 
49
55
 
@@ -9,7 +9,7 @@ import router from './router'
9
9
  const app = createApp(App)
10
10
  /* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */
11
11
 
12
-
12
+
13
13
  app.use(createPinia())
14
14
  app.use(router)
15
15
 
@@ -1,24 +1,24 @@
1
1
  import { createRouter, createWebHistory } from 'vue-router'
2
2
  import ResourceParent from '@/views/ResourceParent.vue'
3
- import ListView from '@/views/ListView.vue'
4
- import ShowView from '@/views/ShowView.vue'
5
- import EditView from '@/views/EditView.vue'
6
- import CreateView from '@/views/CreateView.vue'
3
+ import { useUserStore } from '@/stores/user'
4
+ /* IMPORTANT:ADMINFORTH ROUTES IMPORTS */
7
5
 
8
6
  const router = createRouter({
9
7
  history: createWebHistory(import.meta.env.BASE_URL),
10
8
  routes: [
11
- {
12
- path: '/',
13
- name: 'home',
14
- //redirect to login
15
- /* IMPORTANT:ADMINFORTH REDIRECT */
16
-
17
- },
18
9
  {
19
10
  path: '/login',
20
11
  name: 'login',
21
- component: () => import('@/views/LoginView.vue')
12
+ component: () => import('@/views/LoginView.vue'),
13
+ meta: { title: 'login' },
14
+ beforeEnter: async (to, from, next) => {
15
+ const userStore = useUserStore()
16
+ if(localStorage.getItem('isAuthorized') === 'true'){
17
+ next({name: 'home'})
18
+ } else {
19
+ next()
20
+ }
21
+ }
22
22
  },
23
23
  {
24
24
  path: '/resource/:resourceId',
@@ -27,23 +27,29 @@ const router = createRouter({
27
27
  children: [
28
28
  {
29
29
  path: '',
30
- component: ListView,
31
- name: 'resource-list'
30
+ component: () => import('@/views/ListView.vue'),
31
+ name: 'resource-list',
32
+ meta: { title: 'list',type: 'list' }
32
33
  },
33
34
  {
34
35
  path: 'show/:primaryKey',
35
- component: ShowView,
36
- name: 'resource-show'
36
+ component: () => import('@/views/ShowView.vue'),
37
+ name: 'resource-show',
38
+ meta: { title: 'show', type: 'show'}
39
+
37
40
  },
38
41
  {
39
42
  path: 'edit/:primaryKey',
40
- component: EditView,
41
- name: 'resource-edit'
43
+ component: () => import('@/views/EditView.vue'),
44
+ name: 'resource-edit',
45
+ meta: { title: 'edit', type: 'edit'}
42
46
  },
43
47
  {
44
48
  path: 'create',
45
- component: CreateView,
46
- name: 'resource-create'
49
+ component: () => import('@/views/CreateView.vue'),
50
+ name: 'resource-create',
51
+ meta: { title: 'create', type: 'create'}
52
+
47
53
  },
48
54
  ]
49
55
  },
@@ -51,4 +57,8 @@ const router = createRouter({
51
57
  ]
52
58
  })
53
59
 
60
+
61
+
62
+
63
+
54
64
  export default router
@@ -1,39 +1,50 @@
1
1
  import { ref, computed } from 'vue'
2
2
  import { defineStore } from 'pinia'
3
3
  import { callAdminForthApi } from '@/utils';
4
+ import type { AdminForthResource, AdminForthResourceColumn } from '@/types/AdminForthConfig';
5
+ import type { Ref } from 'vue'
4
6
 
5
7
  export const useCoreStore = defineStore('core', () => {
6
- const resourceById = ref([]);
8
+ const resourceById: Ref<Object> = ref({});
7
9
  const menu = ref([]);
8
10
  const config = ref({});
9
- const record = ref({});
10
- const resourceColumns = ref(null);
11
+ const record: Ref<any | null> = ref({});
12
+ const resource: Ref<AdminForthResource | null> = ref(null);
13
+
14
+ const resourceColumnsWithFilters = computed(() => {
15
+ if (!resource.value) {
16
+ return [];
17
+ }
18
+ return resource.value.columns.filter((col: AdminForthResourceColumn) => col.showIn?.includes('filter'));
19
+ })
20
+
21
+ const resourceOptions = ref(null);
11
22
  const resourceColumnsError = ref('');
12
23
  const resourceColumnsId = ref(null);
13
- const user = ref(null);
24
+ const adminUser = ref(null);
14
25
 
15
26
  async function fetchMenuAndResource() {
16
27
  const resp = await callAdminForthApi({
17
28
  path: '/get_base_config',
18
29
  method: 'GET',
19
30
  });
31
+ if(!resp){
32
+ return
33
+ }
20
34
  menu.value = resp.menu;
21
- resourceById.value = resp.resources.reduce((acc, resource) => {
35
+ resourceById.value = resp.resources.reduce((acc: Object, resource: AdminForthResource) => {
22
36
  acc[resource.resourceId] = resource;
23
37
  return acc;
24
38
  }, {});
25
39
  config.value = resp.config;
26
- user.value = resp.user;
40
+ adminUser.value = resp.user;
27
41
  console.log('🌍 AdminForth v', resp.version);
28
-
29
- // find homepage:true in menu recuresively
30
-
31
42
  }
32
43
 
33
44
  async function fetchRecord({ resourceId, primaryKey }) {
34
45
  record.value = null;
35
46
 
36
- if (!resourceColumns.value) {
47
+ if (!resource.value) {
37
48
  throw new Error('Columns not fetched yet');
38
49
  }
39
50
 
@@ -45,7 +56,7 @@ export const useCoreStore = defineStore('core', () => {
45
56
  resourceId: resourceId,
46
57
  filters: [
47
58
  {
48
- field: resourceColumns.value.find((col) => col.primaryKey).name,
59
+ field: resource.value.columns.find((col: AdminForthResourceColumn) => col.primaryKey).name,
49
60
  operator: 'eq',
50
61
  value: primaryKey
51
62
  }
@@ -56,30 +67,39 @@ export const useCoreStore = defineStore('core', () => {
56
67
  }
57
68
  });
58
69
 
59
- console.log('📦 record', respData);
60
- record.value = respData.data[0];
70
+ if (respData.error) {
71
+ window.adminforth.alert({
72
+ message: respData.error,
73
+ variant: 'danger',
74
+ timeout: 'unlimited'
75
+ });
76
+ record.value = {};
77
+ } else {
78
+ record.value = respData.data[0];
79
+ }
80
+
61
81
  }
62
82
 
63
- async function fetchColumns({ resourceId }) {
64
- if (resourceColumnsId.value === resourceId && resourceColumns.value) {
83
+ async function fetchResourceFull({ resourceId }: { resourceId: string }) {
84
+ if (resourceColumnsId.value === resourceId && resource.value) {
65
85
  // already fetched
66
86
  return;
67
87
  }
68
88
  resourceColumnsId.value = resourceId;
69
- resourceColumns.value = null;
70
89
  resourceColumnsError.value = '';
71
90
  const res = await callAdminForthApi({
72
- path: '/get_resource_columns',
91
+ path: '/get_resource',
73
92
  method: 'POST',
74
93
  body: {
75
94
  resourceId,
76
95
  }
77
- }
78
- );
96
+ });
79
97
  if (res.error) {
80
98
  resourceColumnsError.value = res.error;
81
99
  } else {
82
- resourceColumns.value = res.resource.columns;
100
+ resourceById.value[resourceId] = res.resource;
101
+ resource.value = res.resource;
102
+ resourceOptions.value = res.resource.options;
83
103
  }
84
104
  }
85
105
 
@@ -91,21 +111,16 @@ export const useCoreStore = defineStore('core', () => {
91
111
  config.value = {...config.value, ...res};
92
112
  }
93
113
 
94
- async function logout() {
95
- await callAdminForthApi({
96
- path: '/logout',
97
- method: 'POST',
98
- });
99
- }
114
+
100
115
 
101
116
  const username = computed(() => {
102
117
  const usernameField = config.value.usernameField;
103
- return user.value && user.value[usernameField];
118
+ return adminUser.value && adminUser.value[usernameField];
104
119
  });
105
120
 
106
121
  const userFullname = computed(() => {
107
122
  const userFullnameField = config.value.userFullnameField;
108
- return user.value && user.value[userFullnameField];
123
+ return adminUser.value && adminUser.value[userFullnameField];
109
124
  })
110
125
 
111
126
 
@@ -119,9 +134,11 @@ export const useCoreStore = defineStore('core', () => {
119
134
  fetchMenuAndResource,
120
135
  fetchRecord,
121
136
  record,
122
- resourceColumns,
123
- fetchColumns,
137
+ fetchResourceFull,
124
138
  resourceColumnsError,
125
- logout
139
+ resourceOptions,
140
+ resource,
141
+ adminUser,
142
+ resourceColumnsWithFilters
126
143
  }
127
144
  })
@@ -1,6 +1,12 @@
1
1
  import { ref } from 'vue'
2
2
  import { defineStore } from 'pinia'
3
- import { callAdminForthApi } from '@/utils';
3
+
4
+ type ModalContentType = {
5
+ title?: string;
6
+ content?: string;
7
+ acceptText?: string;
8
+ cancelText?: string;
9
+ }
4
10
 
5
11
 
6
12
  export const useModalStore = defineStore('modal', () => {
@@ -12,13 +18,17 @@ export const useModalStore = defineStore('modal', () => {
12
18
  });
13
19
  const isOpened = ref(false);
14
20
  const onAcceptFunction: any = ref(()=>{});
21
+ const onCancelFunction: any = ref(()=>{});
15
22
  function togleModal() {
16
23
  isOpened.value = !isOpened.value;
17
24
  }
18
25
  function setOnAcceptFunction(func: Function) {
19
26
  onAcceptFunction.value = func;
20
27
  }
21
- function setModalContent(content: string) {
28
+ function setOnCancelFunction(func: Function) {
29
+ onCancelFunction.value = func;
30
+ }
31
+ function setModalContent(content: ModalContentType) {
22
32
  modalContent.value = content;
23
33
  }
24
34
  function resetmodalState() {
@@ -33,6 +43,6 @@ export const useModalStore = defineStore('modal', () => {
33
43
 
34
44
  }
35
45
 
36
- return {isOpened, setModalContent, togleModal,modalContent, setOnAcceptFunction, onAcceptFunction,resetmodalState}
46
+ return {isOpened, setModalContent,onCancelFunction, togleModal,modalContent, setOnAcceptFunction, onAcceptFunction,resetmodalState,setOnCancelFunction}
37
47
 
38
48
  })