adminforth 1.1.68 → 1.1.70

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 (44) hide show
  1. package/auth.ts +0 -1
  2. package/dist/spa/index.html +4 -4
  3. package/dist/spa/package-lock.json +5 -206
  4. package/dist/spa/package.json +2 -8
  5. package/dist/spa/public/favicon.ico +0 -0
  6. package/dist/spa/spa/public/favicon.ico +0 -0
  7. package/dist/spa/spa/src/views/HomeView.vue +8 -0
  8. package/dist/spa/src/App.vue +72 -171
  9. package/dist/spa/src/assets/logo.svg +1 -19
  10. package/dist/spa/src/components/AcceptModal.vue +10 -3
  11. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  12. package/dist/spa/src/components/CustomDatePicker.vue +6 -19
  13. package/dist/spa/src/components/CustomDateRangePicker.vue +5 -14
  14. package/dist/spa/src/components/Dropdown.vue +5 -19
  15. package/dist/spa/src/components/Filters.vue +37 -91
  16. package/dist/spa/src/components/MenuLink.vue +2 -2
  17. package/dist/spa/src/components/ResourceForm.vue +103 -163
  18. package/dist/spa/src/components/ValueRenderer.vue +6 -22
  19. package/dist/spa/src/main.ts +1 -1
  20. package/dist/spa/src/router/index.ts +19 -30
  21. package/dist/spa/src/stores/core.ts +59 -65
  22. package/dist/spa/src/stores/modal.ts +3 -13
  23. package/dist/spa/src/utils.ts +8 -65
  24. package/dist/spa/src/views/CreateView.vue +16 -76
  25. package/dist/spa/src/views/EditView.vue +14 -76
  26. package/dist/spa/src/views/HomeView.vue +8 -0
  27. package/dist/spa/src/views/ListView.vue +360 -88
  28. package/dist/spa/src/views/LoginView.vue +5 -17
  29. package/dist/spa/src/views/ResourceParent.vue +1 -1
  30. package/dist/spa/src/views/ShowView.vue +19 -114
  31. package/dist/spa/tailwind.config.js +2 -5
  32. package/dist/spa/vite.config.ts +0 -6
  33. package/dist/types.js +30 -0
  34. package/package.json +1 -1
  35. package/dist/spa/public/assets/favicon.png +0 -0
  36. package/dist/spa/src/components/CustomRangePicker.vue +0 -148
  37. package/dist/spa/src/components/ResourceListTable.vue +0 -419
  38. package/dist/spa/src/components/Toast.vue +0 -66
  39. package/dist/spa/src/composables/useFrontendApi.ts +0 -26
  40. package/dist/spa/src/composables/useStores.ts +0 -113
  41. package/dist/spa/src/spa_types/core.ts +0 -51
  42. package/dist/spa/src/stores/filters.ts +0 -22
  43. package/dist/spa/src/stores/toast.ts +0 -15
  44. package/dist/spa/src/stores/user.ts +0 -54
@@ -2,11 +2,11 @@
2
2
  <!-- drawer component -->
3
3
  <div id="drawer-navigation"
4
4
 
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"
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"
6
6
 
7
7
  :class="show ? 'top-0 transform-none' : ''"
8
8
  tabindex="-1" aria-labelledby="drawer-navigation-label"
9
- :style="{ height: `calc(100vh ` }"
9
+ :style="{ height: `calc(100vh - 3.5rem)` }"
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
@@ -22,17 +22,11 @@
22
22
  <li v-for="c in columnsWithFilter" :key="c">
23
23
  <p class="dark:text-gray-400">{{ c.label }}</p>
24
24
 
25
- <Dropdown
26
- v-if="c.foreignResource"
27
- :options="columnOptions[c.name] || []"
28
- @update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
29
- :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
30
- />
31
- <Dropdown
32
- v-else-if="c.type === 'boolean'"
25
+ <Dropdown
26
+ v-if="c.type === 'boolean'"
33
27
  :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
34
28
  @update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
35
- :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
29
+ :modelValue="filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
36
30
  />
37
31
 
38
32
  <Dropdown
@@ -40,7 +34,7 @@
40
34
  :options="c.enum"
41
35
  :allowCustom="c.allowCustom"
42
36
  @update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
43
- :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
37
+ :modelValue="filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
44
38
  />
45
39
 
46
40
  <input
@@ -54,9 +48,9 @@
54
48
  <CustomDateRangePicker
55
49
  v-else-if="['datetime'].includes(c.type)"
56
50
  :column="c"
57
- :valueStart="filtersStore.filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
51
+ :valueStart="filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
58
52
  @update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
59
- :valueEnd="filtersStore.filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
53
+ :valueEnd="filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
60
54
  @update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
61
55
  />
62
56
 
@@ -68,32 +62,22 @@
68
62
  :value="getFilterItem({ column: c, operator: 'ilike' })"
69
63
  >
70
64
 
71
- <CustomRangePicker
72
- v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
73
- :min="c.min"
74
- :max="c.max"
75
- :valueStart="getFilterItem({ column: c, operator: 'gte' })"
76
- @update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
77
- :valueEnd="getFilterItem({ column: c, operator: 'lte' })"
78
- @update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
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>
65
+ <div v-else-if="['integer', 'decimal', 'float'].includes(c.type)" class="flex gap-2">
66
+ <input
67
+ type="number" aria-describedby="helper-text-explanation"
68
+ 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"
69
+ placeholder="From"
70
+ @input="setFilterItem({ column: c, operator: 'gte', value: $event.target.value || undefined })"
71
+ :value="getFilterItem({ column: c, operator: 'gte' })"
72
+ >
73
+ <input
74
+ type="number" aria-describedby="helper-text-explanation"
75
+ 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"
76
+ placeholder="To"
77
+ @input="setFilterItem({ column: c, operator: 'lte', value: $event.target.value || undefined})"
78
+ :value="getFilterItem({ column: c, operator: 'lte' })"
79
+ >
80
+ </div>
97
81
 
98
82
  </li>
99
83
  </ul>
@@ -101,71 +85,33 @@
101
85
 
102
86
  <div class="flex justify-end gap-2">
103
87
  <button
104
- :disabled="!filtersStore.filters.length"
88
+ :disabled="!filters.length"
105
89
  type="button"
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
+ 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"
107
91
  @click="clear">Clear all</button>
108
92
 
109
93
  </div>
110
94
  </div>
111
95
 
112
- <div v-if="show" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30"
96
+ <div v-if="show" drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30"
113
97
  @click="$emit('hide')">
114
98
  </div>
115
99
  </template>
116
100
 
117
101
  <script setup>
118
- import { watch, computed, ref, onMounted } from 'vue'
102
+ import { watch, computed } from 'vue'
119
103
  import Dropdown from '@/components/Dropdown.vue';
120
104
  import CustomDateRangePicker from '@/components/CustomDateRangePicker.vue';
121
- import { callAdminForthApi } from '@/utils';
122
- import { useRouter } from 'vue-router';
123
- import { computedAsync } from '@vueuse/core'
124
- import CustomRangePicker from "@/components/CustomRangePicker.vue";
125
- import { useFiltersStore } from '@/stores/filters';
126
-
127
- const filtersStore = useFiltersStore();
128
-
129
105
 
130
106
  // props: columns
131
107
  // add support for v-model:filers
132
108
  const props = defineProps(['columns', 'filters', 'show', 'columnsMinMax']);
133
109
  const emits = defineEmits(['update:filters', 'hide']);
134
110
 
135
- const router = useRouter();
136
-
137
-
138
- const columnsWithFilter = computed(
111
+ const columnsWithFilter = computed(
139
112
  () => props.columns?.filter(column => column.showIn.includes('filter')) || []
140
113
  );
141
114
 
142
- const columnOptions = computedAsync(async () => {
143
- const ret = {};
144
- if (!props.columns) {
145
- return ret;
146
- }
147
- await Promise.all(
148
- Object.values(props.columns).map(async (column) => {
149
- if (column.foreignResource) {
150
- const list = await callAdminForthApi({
151
- method: 'POST',
152
- path: `/get_resource_foreign_data`,
153
- body: {
154
- resourceId: router.currentRoute.value.params.resourceId,
155
- column: column.name,
156
- limit: 1000,
157
- offset: 0,
158
- },
159
- });
160
- ret[column.name] = list.items;
161
- }
162
- })
163
- );
164
-
165
- return ret;
166
- }, {});
167
-
168
-
169
115
  // sync 'body' class 'overflow-hidden' with show prop show
170
116
  watch(() => props.show, (show) => {
171
117
  if (show) {
@@ -184,28 +130,28 @@ watch(() => props.show, (show) => {
184
130
 
185
131
  function setFilterItem({ column, operator, value }) {
186
132
 
187
- const index = filtersStore.filters.findIndex(f => f.field === column.name && f.operator === operator);
133
+ const index = props.filters.findIndex(f => f.field === column.name && f.operator === operator);
188
134
  if (value === undefined) {
189
135
  if (index !== -1) {
190
- filtersStore.filters.splice(index, 1);
136
+ props.filters.splice(index, 1);
191
137
  }
192
- emits('update:filters', [...filtersStore.filters]);
138
+ emits('update:filters', [...props.filters]);
193
139
  return;
194
140
  } else {
195
141
  if (index === -1) {
196
- filtersStore.setFilter({ field: column.name, value, operator });
142
+ props.filters.push({ field: column.name, value, operator });
197
143
  } else {
198
- filtersStore.setFilters([...filtersStore.filters.slice(0, index), { field: column.name, value, operator }, ...filtersStore.filters.slice(index + 1)])
144
+ props.filters[index].value = value;
199
145
  }
200
146
  }
201
- emits('update:filters', [...filtersStore.filters]);
147
+ emits('update:filters', [...props.filters]);
202
148
  }
203
149
 
204
150
  function getFilterItem({ column, operator }) {
205
- return filtersStore.filters.find(f => f.field === column.name && f.operator === operator)?.value || '';
151
+ return props.filters.find(f => f.field === column.name && f.operator === operator)?.value || '';
206
152
  }
207
153
 
208
154
  async function clear() {
209
- filtersStore.clearFilters();
155
+ emits('update:filters', []);
210
156
  }
211
157
  </script>
@@ -1,11 +1,11 @@
1
1
  <template>
2
2
  <RouterLink
3
3
  :to="{name: item.resourceId ? 'resource-list' : item.path, params: item.resourceId ? { resourceId: item.resourceId }: {}}"
4
- class="flex items-center py-2 text-nav-menu-text rounded-default hover:bg-nav-menu-bg-hover active:bg-nav-menu-bg-active" role="menuitem"
4
+ class="flex items-center py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white" role="menuitem"
5
5
  :class="{
6
6
  'px-4': isChild,
7
7
  'px-2': !isChild,
8
- 'bg-nav-menu-bg-active dark:bg-gray-700': item.resourceId ?
8
+ 'bg-blue-100 dark:bg-gray-700': item.resourceId ?
9
9
  ($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
10
10
  ($route.name === item.path)
11
11
  }"
@@ -1,12 +1,12 @@
1
1
  <template>
2
- <div class="rounded-default">
2
+ <div>
3
3
 
4
4
  <div
5
- class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black rounded-default"
5
+ class="relative shadow-md sm:rounded-lg dark:shadow-2xl"
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-form-view-heading 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-gray-50 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,114 +18,97 @@
18
18
  </thead>
19
19
  <tbody>
20
20
  <tr v-for="column, i in editableColumns" :key="column.name"
21
- class="bg-form-view-bg dark:bg-gray-800 border-b dark:border-gray-700"
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"
22
22
  >
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)"
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'"
30
28
  />
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>
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>
129
112
  </tr>
130
113
 
131
114
  </tbody>
@@ -138,24 +121,19 @@
138
121
 
139
122
  <script setup>
140
123
 
141
- import CustomDatePicker from "@/components/CustomDatePicker.vue";
142
- import Dropdown from '@/components/Dropdown.vue';
124
+ import { ref, computed, onMounted, watch } from 'vue';
143
125
  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();
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
+ import CustomDatePicker from "@/components/CustomDatePicker.vue";
152
131
 
153
132
  const props = defineProps({
154
133
  loading: Boolean,
155
- resource: Object,
134
+ resourceColumns: Object,
156
135
  record: Object,
157
136
  validating: Boolean,
158
- customComponentsPerColumn: Object,
159
137
  });
160
138
 
161
139
 
@@ -167,7 +145,6 @@ const emit = defineEmits(['update:record', 'update:isValid']);
167
145
 
168
146
  const currentValues = ref({});
169
147
 
170
-
171
148
  const columnError = (column) => {
172
149
  const val = computed(() => {
173
150
  if ( column.required[mode.value] && (currentValues.value[column.name] === undefined || currentValues.value[column.name] === null || currentValues.value[column.name] === '') ) {
@@ -189,28 +166,14 @@ const columnError = (column) => {
189
166
  return `This field must be less than ${column.maxValue}`;
190
167
  }
191
168
  }
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
- }
208
169
  return null;
209
170
  });
210
171
  return val.value;
211
172
  };
212
173
 
213
174
 
175
+
176
+
214
177
  const setCurrentValue = (key, value) => {
215
178
  currentValues.value[key] = value;
216
179
 
@@ -222,36 +185,13 @@ onMounted(() => {
222
185
  currentValues.value[key] = props.record[key];
223
186
  });
224
187
  initFlowbite();
225
-
226
-
227
188
  });
228
189
 
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
-
250
190
  const coreStore = useCoreStore();
251
191
 
252
192
  const editableColumns = computed(() => {
253
193
  const mode = props.record ? 'edit' : 'create';
254
- return props.resource?.columns?.filter(column => column.showIn.includes(mode));
194
+ return props.resourceColumns?.filter(column => column.showIn.includes(mode));
255
195
  });
256
196
 
257
197
  const isValid = computed(() => {
@@ -1,29 +1,18 @@
1
1
  <template>
2
2
  <div>
3
-
4
- <span v-if="column.foreignResource">
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 }}
8
- </RouterLink>
9
- <div v-else>
10
- <span class="text-gray-400">-</span>
11
- </div>
12
- </span>
13
-
14
- <span v-else-if="column.type === 'boolean'">
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>
3
+ <span v-if="column.type === 'boolean'">
4
+ <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>
16
5
  <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>
17
6
  </span>
18
7
  <span v-else-if="column.enum">
19
- {{ checkEmptyValues(column.enum.find(e => e.value === record[column.name])?.label,route.meta.type) }}
8
+ {{ column.enum.find(e => e.value === row[column.name])?.label }}
20
9
  </span>
21
10
  <span v-else-if="column.type === 'datetime'">
22
- {{ checkEmptyValues(formatDate(record[column.name]),route.meta.type) }}
11
+ {{ formatDate(row[column.name]) }}
23
12
 
24
13
  </span>
25
14
  <span v-else>
26
- {{ checkEmptyValues(record[column.name],route.meta.type) }}
15
+ {{ row[column.name] }}
27
16
  </span>
28
17
  </div>
29
18
  </template>
@@ -34,22 +23,17 @@
34
23
  import dayjs from 'dayjs';
35
24
  import utc from 'dayjs/plugin/utc';
36
25
  import timezone from 'dayjs/plugin/timezone';
37
- import {checkEmptyValues} from '@/utils';
38
- import { useRoute, useRouter } from 'vue-router';
39
-
40
26
 
41
27
  import { useCoreStore } from '@/stores/core';
42
28
 
43
29
  const coreStore = useCoreStore();
44
- const route = useRoute();
45
-
46
30
 
47
31
  dayjs.extend(utc);
48
32
  dayjs.extend(timezone);
49
33
 
50
34
  const props = defineProps({
51
35
  column: Object,
52
- record: Object
36
+ row: Object
53
37
  });
54
38
 
55
39
 
@@ -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