adminforth 1.1.70 → 1.1.71

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 (47) hide show
  1. package/auth.ts +2 -1
  2. package/dist/auth.js +2 -1
  3. package/dist/spa/index.html +4 -4
  4. package/dist/spa/package-lock.json +206 -5
  5. package/dist/spa/package.json +8 -2
  6. package/dist/spa/public/assets/favicon.png +0 -0
  7. package/dist/spa/spa/src/views/LoginView.vue +2 -0
  8. package/dist/spa/src/App.vue +171 -72
  9. package/dist/spa/src/assets/logo.svg +19 -1
  10. package/dist/spa/src/components/AcceptModal.vue +3 -10
  11. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  12. package/dist/spa/src/components/CustomDatePicker.vue +19 -6
  13. package/dist/spa/src/components/CustomDateRangePicker.vue +14 -5
  14. package/dist/spa/src/components/CustomRangePicker.vue +148 -0
  15. package/dist/spa/src/components/Dropdown.vue +19 -5
  16. package/dist/spa/src/components/Filters.vue +91 -37
  17. package/dist/spa/src/components/MenuLink.vue +2 -2
  18. package/dist/spa/src/components/ResourceForm.vue +163 -103
  19. package/dist/spa/src/components/ResourceListTable.vue +419 -0
  20. package/dist/spa/src/components/Toast.vue +66 -0
  21. package/dist/spa/src/components/ValueRenderer.vue +22 -6
  22. package/dist/spa/src/composables/useFrontendApi.ts +26 -0
  23. package/dist/spa/src/composables/useStores.ts +113 -0
  24. package/dist/spa/src/main.ts +1 -1
  25. package/dist/spa/src/router/index.ts +30 -19
  26. package/dist/spa/src/spa_types/core.ts +51 -0
  27. package/dist/spa/src/stores/core.ts +65 -59
  28. package/dist/spa/src/stores/filters.ts +22 -0
  29. package/dist/spa/src/stores/modal.ts +13 -3
  30. package/dist/spa/src/stores/toast.ts +15 -0
  31. package/dist/spa/src/stores/user.ts +54 -0
  32. package/dist/spa/src/utils.ts +65 -8
  33. package/dist/spa/src/views/CreateView.vue +76 -16
  34. package/dist/spa/src/views/EditView.vue +76 -14
  35. package/dist/spa/src/views/ListView.vue +88 -360
  36. package/dist/spa/src/views/LoginView.vue +17 -5
  37. package/dist/spa/src/views/ResourceParent.vue +1 -1
  38. package/dist/spa/src/views/ShowView.vue +114 -19
  39. package/dist/spa/tailwind.config.js +5 -2
  40. package/dist/spa/vite.config.ts +6 -0
  41. package/package.json +1 -1
  42. package/spa/src/views/LoginView.vue +2 -0
  43. package/dist/spa/public/favicon.ico +0 -0
  44. package/dist/spa/spa/public/favicon.ico +0 -0
  45. package/dist/spa/spa/src/views/HomeView.vue +0 -8
  46. package/dist/spa/src/views/HomeView.vue +0 -8
  47. package/dist/types.js +0 -30
@@ -1,12 +1,12 @@
1
1
  <template>
2
- <div>
2
+ <div class="rounded-default">
3
3
 
4
4
  <div
5
- class="relative shadow-md sm:rounded-lg dark:shadow-2xl"
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">
9
- <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
8
+ <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 ">
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">
12
12
  Field
@@ -18,97 +18,114 @@
18
18
  </thead>
19
19
  <tbody>
20
20
  <tr v-for="column, i in editableColumns" :key="column.name"
21
- class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700"
21
+ class="bg-form-view-bg dark:bg-gray-800 border-b dark:border-gray-700"
22
22
  >
23
- <td class="px-6 py-4 whitespace-nowrap">
24
- {{ column.label }}
25
- <span :data-tooltip-target="`tooltip-show-${i}`" class="relative inline-block">
26
- <IconExclamationCircleSolid v-if="column.required[mode]" class="w-4 h-4"
27
- :class="(columnError(column) && validating) ? 'text-red-500 dark:text-red-400' : 'text-gray-400 dark:text-gray-500'"
23
+ <!-- if column is in customComponentsPerColumn, use this component. If not, use this code -->
24
+ <template v-if="customComponentsPerColumn[column.name]">
25
+ <component
26
+ :is="customComponentsPerColumn[column.name]"
27
+ :column="column"
28
+ :value="currentValues[column.name]"
29
+ @update:value="setCurrentValue(column.name, $event)"
28
30
  />
29
- </span>
30
- <div :id="`tooltip-show-${i}`"
31
- 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">
32
- Required field
33
- <div class="tooltip-arrow" data-popper-arrow></div>
34
- </div>
35
- </td>
36
- <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative">
37
- <Dropdown
38
- single
39
- v-if="column.enum"
40
- :options="column.enum"
41
- :modelValue="currentValues[column.name]"
42
- @update:modelValue="setCurrentValue(column.name, $event)"
43
- />
44
- <Dropdown
45
- single
46
- v-else-if="column.type === 'boolean'"
47
- :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
48
- :modelValue="currentValues[column.name]"
49
- @update:modelValue="setCurrentValue(column.name, $event)"
50
- />
51
- <input
52
- v-else-if="['integer'].includes(column.type)"
53
- type="number"
54
- step="1"
55
- 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-40 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"
56
- placeholder="0"
57
- :value="currentValues[column.name]"
58
- @input="setCurrentValue(column.name, $event.target.value)"
59
- >
60
- <CustomDatePicker
61
- v-else-if="['datetime'].includes(column.type)"
62
- :column="column"
63
- :valueStart="currentValues[column.name]"
64
- @update:valueStart="setCurrentValue(column.name, $event)"
65
- />
66
- <input
67
- v-else-if="['decimal', 'float'].includes(column.type)"
68
- type="number"
69
- step="0.1"
70
- 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-40 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"
71
- placeholder="0.0"
72
- :value="currentValues[column.name]"
73
- @input="setCurrentValue(column.name, $event.target.value)"
74
- />
75
- <textarea
76
- v-else-if="['text'].includes(column.type)"
77
- 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-full 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"
78
- placeholder="Text"
79
- :value="currentValues[column.name]"
80
- @input="setCurrentValue(column.name, $event.target.value)"
81
- >
82
- </textarea>
83
- <input
84
- v-else
85
- :type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
86
- 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-full 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"
87
- placeholder="Text"
88
- :value="currentValues[column.name]"
89
- @input="setCurrentValue(column.name, $event.target.value)"
90
- autocomplete="false"
91
- data-lpignore="true"
92
- readonly
93
- onfocus="this.removeAttribute('readonly');"
94
- >
95
-
96
- <button
97
- v-if="column.masked"
98
- type="button"
99
- @click="unmasked[column.name] = !unmasked[column.name]"
100
- class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
101
- >
102
- <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
103
- <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
104
- </button>
105
-
106
-
107
-
108
- <div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
109
-
110
- <div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
111
- </td>
31
+ </template>
32
+ <template v-else>
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
+ </td>
46
+ <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative">
47
+ <Dropdown
48
+ single
49
+ v-if="column.foreignResource"
50
+ :options="columnOptions[column.name] || []"
51
+ :placeholder = "columnOptions[column.name]?.length || 'There are no options available'"
52
+ :modelValue="currentValues[column.name]"
53
+ @update:modelValue="setCurrentValue(column.name, $event)"
54
+ ></Dropdown>
55
+ <Dropdown
56
+ single
57
+ v-else-if="column.enum"
58
+ :options="column.enum"
59
+ :modelValue="currentValues[column.name]"
60
+ @update:modelValue="setCurrentValue(column.name, $event)"
61
+ />
62
+ <Dropdown
63
+ single
64
+ v-else-if="column.type === 'boolean'"
65
+ :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
66
+ :modelValue="currentValues[column.name]"
67
+ @update:modelValue="setCurrentValue(column.name, $event)"
68
+ />
69
+ <input
70
+ v-else-if="['integer'].includes(column.type)"
71
+ type="number"
72
+ step="1"
73
+ 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-40 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"
74
+ placeholder="0"
75
+ :value="currentValues[column.name]"
76
+ @input="setCurrentValue(column.name, $event.target.value)"
77
+ >
78
+ <CustomDatePicker
79
+ v-else-if="['datetime'].includes(column.type)"
80
+ :column="column"
81
+ :valueStart="currentValues[column.name]"
82
+ auto-hide
83
+ @update:valueStart="setCurrentValue(column.name, $event)"
84
+ />
85
+ <input
86
+ v-else-if="['decimal', 'float'].includes(column.type)"
87
+ type="number"
88
+ step="0.1"
89
+ 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-40 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"
90
+ placeholder="0.0"
91
+ :value="currentValues[column.name]"
92
+ @input="setCurrentValue(column.name, $event.target.value)"
93
+ />
94
+ <textarea
95
+ v-else-if="['text'].includes(column.type)"
96
+ 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-full 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"
97
+ placeholder="Text"
98
+ :value="currentValues[column.name]"
99
+ @input="setCurrentValue(column.name, $event.target.value)"
100
+ >
101
+ </textarea>
102
+ <input
103
+ v-else
104
+ :type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
105
+ 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-full 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"
106
+ placeholder="Text"
107
+ :value="currentValues[column.name]"
108
+ @input="setCurrentValue(column.name, $event.target.value)"
109
+ autocomplete="false"
110
+ data-lpignore="true"
111
+ readonly
112
+ onfocus="this.removeAttribute('readonly');"
113
+ >
114
+
115
+ <button
116
+ v-if="column.masked"
117
+ type="button"
118
+ @click="unmasked[column.name] = !unmasked[column.name]"
119
+ class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
120
+ >
121
+ <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
122
+ <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
123
+ </button>
124
+ <div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
125
+
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>
127
+ </td>
128
+ </template>
112
129
  </tr>
113
130
 
114
131
  </tbody>
@@ -121,19 +138,24 @@
121
138
 
122
139
  <script setup>
123
140
 
124
- import { ref, computed, onMounted, watch } from 'vue';
125
- import { useCoreStore } from '@/stores/core';
126
- import Dropdown from '@/components/Dropdown.vue';
127
- import { IconExclamationCircleSolid } from '@iconify-prerendered/vue-flowbite';
128
- import { initFlowbite } from 'flowbite'
129
- import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
130
141
  import CustomDatePicker from "@/components/CustomDatePicker.vue";
142
+ import Dropdown from '@/components/Dropdown.vue';
143
+ import { useCoreStore } from '@/stores/core';
144
+ import { callAdminForthApi } from '@/utils';
145
+ import { IconExclamationCircleSolid, IconEyeSlashSolid, IconEyeSolid } from '@iconify-prerendered/vue-flowbite';
146
+ import { computedAsync } from '@vueuse/core';
147
+ import { initFlowbite } from 'flowbite';
148
+ import { computed, onMounted, ref, watch } from 'vue';
149
+ import { useRouter } from 'vue-router';
150
+
151
+ const router = useRouter();
131
152
 
132
153
  const props = defineProps({
133
154
  loading: Boolean,
134
- resourceColumns: Object,
155
+ resource: Object,
135
156
  record: Object,
136
157
  validating: Boolean,
158
+ customComponentsPerColumn: Object,
137
159
  });
138
160
 
139
161
 
@@ -145,6 +167,7 @@ const emit = defineEmits(['update:record', 'update:isValid']);
145
167
 
146
168
  const currentValues = ref({});
147
169
 
170
+
148
171
  const columnError = (column) => {
149
172
  const val = computed(() => {
150
173
  if ( column.required[mode.value] && (currentValues.value[column.name] === undefined || currentValues.value[column.name] === null || currentValues.value[column.name] === '') ) {
@@ -166,14 +189,28 @@ const columnError = (column) => {
166
189
  return `This field must be less than ${column.maxValue}`;
167
190
  }
168
191
  }
192
+ if ( column.validation && column.validation.length ){
193
+ const validationArray = column.validation;
194
+ for (let i = 0; i < validationArray.length; i++) {
195
+ if (validationArray[i].regExp) {
196
+ const regExp = new RegExp(validationArray[i].regExp);
197
+ let value = currentValues.value[column.name];
198
+ if (value === undefined || value === null) {
199
+ value = '';
200
+ }
201
+ if (!regExp.test(value)) {
202
+ return validationArray[i].message;
203
+ }
204
+ }
205
+ }
206
+
207
+ }
169
208
  return null;
170
209
  });
171
210
  return val.value;
172
211
  };
173
212
 
174
213
 
175
-
176
-
177
214
  const setCurrentValue = (key, value) => {
178
215
  currentValues.value[key] = value;
179
216
 
@@ -185,13 +222,36 @@ onMounted(() => {
185
222
  currentValues.value[key] = props.record[key];
186
223
  });
187
224
  initFlowbite();
225
+
226
+
188
227
  });
189
228
 
229
+ const columnOptions = computedAsync(async () => {
230
+ return (await Promise.all(
231
+ Object.values(props.resource.columns).map(async (column) => {
232
+ if (column.foreignResource) {
233
+ const list = await callAdminForthApi({
234
+ method: 'POST',
235
+ path: `/get_resource_foreign_data`,
236
+ body: {
237
+ resourceId: router.currentRoute.value.params.resourceId,
238
+ column: column.name,
239
+ limit: 1000,
240
+ offset: 0,
241
+ },
242
+ });
243
+ return { [column.name]: list.items };
244
+ }
245
+ })
246
+ )).reduce((acc, val) => Object.assign(acc, val), {})
247
+
248
+ }, {});
249
+
190
250
  const coreStore = useCoreStore();
191
251
 
192
252
  const editableColumns = computed(() => {
193
253
  const mode = props.record ? 'edit' : 'create';
194
- return props.resourceColumns?.filter(column => column.showIn.includes(mode));
254
+ return props.resource?.columns?.filter(column => column.showIn.includes(mode));
195
255
  });
196
256
 
197
257
  const isValid = computed(() => {