adminforth 1.1.53 → 1.1.54

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 (50) hide show
  1. package/dist/modules/styles.js +1 -1
  2. package/dist/spa/index.html +2 -2
  3. package/dist/spa/package-lock.json +17 -4
  4. package/dist/spa/package.json +2 -1
  5. package/dist/spa/spa/src/App.vue +2 -2
  6. package/dist/spa/spa/src/components/ResourceListTable.vue +2 -2
  7. package/dist/spa/spa/src/components/SkeleteLoader.vue +1 -1
  8. package/dist/spa/spa/src/views/ResourceParent.vue +1 -2
  9. package/dist/spa/src/App.vue +125 -45
  10. package/dist/spa/src/assets/logo.svg +19 -1
  11. package/dist/spa/src/components/AcceptModal.vue +2 -9
  12. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  13. package/dist/spa/src/components/CustomDatePicker.vue +16 -3
  14. package/dist/spa/src/components/CustomDateRangePicker.vue +11 -2
  15. package/dist/spa/src/components/Dropdown.vue +6 -1
  16. package/dist/spa/src/components/Filters.vue +40 -19
  17. package/dist/spa/src/components/ResourceForm.vue +26 -24
  18. package/dist/spa/src/components/ResourceListTable.vue +419 -0
  19. package/dist/spa/src/components/Toast.vue +66 -0
  20. package/dist/spa/src/components/ValueRenderer.vue +14 -8
  21. package/dist/spa/src/composables/useFrontendApi.ts +26 -0
  22. package/dist/spa/src/composables/useStores.ts +113 -0
  23. package/dist/spa/src/main.ts +1 -1
  24. package/dist/spa/src/router/index.ts +30 -20
  25. package/dist/spa/src/spa_types/core.ts +51 -0
  26. package/dist/spa/src/stores/core.ts +48 -31
  27. package/dist/spa/src/stores/filters.ts +22 -0
  28. package/dist/spa/src/stores/modal.ts +13 -3
  29. package/dist/spa/src/stores/toast.ts +15 -0
  30. package/dist/spa/src/stores/user.ts +54 -0
  31. package/dist/spa/src/utils.ts +62 -7
  32. package/dist/spa/src/views/CreateView.vue +67 -15
  33. package/dist/spa/src/views/EditView.vue +45 -11
  34. package/dist/spa/src/views/ListView.vue +82 -366
  35. package/dist/spa/src/views/LoginView.vue +16 -4
  36. package/dist/spa/src/views/ShowView.vue +105 -21
  37. package/dist/spa/tailwind.config.js +1 -1
  38. package/dist/spa/vite.config.ts +6 -0
  39. package/modules/styles.ts +2 -1
  40. package/package.json +1 -1
  41. package/spa/src/App.vue +2 -2
  42. package/spa/src/components/ResourceListTable.vue +2 -2
  43. package/spa/src/components/SkeleteLoader.vue +1 -1
  44. package/spa/src/views/ResourceParent.vue +1 -2
  45. package/dist/plugins/AccessControl/index.js +0 -66
  46. package/dist/plugins/AccessControl/types.js +0 -1
  47. package/dist/spa/public/favicon.ico +0 -0
  48. package/dist/spa/spa/public/favicon.ico +0 -0
  49. package/dist/types.js +0 -30
  50. /package/dist/spa/{spa/public → public/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(() => {