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
@@ -1,211 +0,0 @@
1
- <template>
2
- <!-- drawer component -->
3
- <div id="drawer-navigation"
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"
6
-
7
- :class="show ? 'top-0 transform-none' : ''"
8
- tabindex="-1" aria-labelledby="drawer-navigation-label"
9
- :style="{ height: `calc(100vh ` }"
10
- >
11
- <h5 id="drawer-navigation-label" class="text-base font-semibold text-gray-500 uppercase dark:text-gray-400">
12
- Filters
13
-
14
- <button type="button" @click="$emit('hide')" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 absolute end-2.5 inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white" >
15
- <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
16
- <span class="sr-only">Close menu</span>
17
- </button>
18
- </h5>
19
-
20
- <div class="py-4 ">
21
- <ul class="space-y-3 font-medium">
22
- <li v-for="c in columnsWithFilter" :key="c">
23
- <p class="dark:text-gray-400">{{ c.label }}</p>
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'"
33
- :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
34
- @update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
35
- :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
36
- />
37
-
38
- <Dropdown
39
- v-else-if="c.enum"
40
- :options="c.enum"
41
- :allowCustom="c.allowCustom"
42
- @update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
43
- :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
44
- />
45
-
46
- <input
47
- v-else-if="[ 'string', 'text' ].includes(c.type)"
48
- type="text" class="w-full py-1 px-2 border border-gray-300 rounded-md 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"
49
- placeholder="Search"
50
- @input="setFilterItem({ column: c, operator: 'ilike', value: $event.target.value || undefined })"
51
- :value="getFilterItem({ column: c, operator: 'ilike' })"
52
- >
53
-
54
- <CustomDateRangePicker
55
- v-else-if="['datetime'].includes(c.type)"
56
- :column="c"
57
- :valueStart="filtersStore.filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
58
- @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"
60
- @update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
61
- />
62
-
63
- <input
64
- v-else-if="[ 'date', 'time' ].includes(c.type)"
65
- type="text" class="w-full py-1 px-2 border border-gray-300 rounded-md"
66
- placeholder="Search datetime"
67
- @input="setFilterItem({ column: c, operator: 'ilike', value: $event.target.value || undefined })"
68
- :value="getFilterItem({ column: c, operator: 'ilike' })"
69
- >
70
-
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>
97
-
98
- </li>
99
- </ul>
100
- </div>
101
-
102
- <div class="flex justify-end gap-2">
103
- <button
104
- :disabled="!filtersStore.filters.length"
105
- 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"
107
- @click="clear">Clear all</button>
108
-
109
- </div>
110
- </div>
111
-
112
- <div v-if="show" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30"
113
- @click="$emit('hide')">
114
- </div>
115
- </template>
116
-
117
- <script setup>
118
- import { watch, computed, ref, onMounted } from 'vue'
119
- import Dropdown from '@/components/Dropdown.vue';
120
- 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
-
130
- // props: columns
131
- // add support for v-model:filers
132
- const props = defineProps(['columns', 'filters', 'show', 'columnsMinMax']);
133
- const emits = defineEmits(['update:filters', 'hide']);
134
-
135
- const router = useRouter();
136
-
137
-
138
- const columnsWithFilter = computed(
139
- () => props.columns?.filter(column => column.showIn.includes('filter')) || []
140
- );
141
-
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
- // sync 'body' class 'overflow-hidden' with show prop show
170
- watch(() => props.show, (show) => {
171
- if (show) {
172
- document.body.classList.add('overflow-hidden');
173
- } else {
174
- document.body.classList.remove('overflow-hidden');
175
- }
176
- });
177
-
178
- // filters is a array of objects
179
- // {
180
- // field: 'name',
181
- // value: 'John',
182
- // operator: 'like'
183
- // }
184
-
185
- function setFilterItem({ column, operator, value }) {
186
-
187
- const index = filtersStore.filters.findIndex(f => f.field === column.name && f.operator === operator);
188
- if (value === undefined) {
189
- if (index !== -1) {
190
- filtersStore.filters.splice(index, 1);
191
- }
192
- emits('update:filters', [...filtersStore.filters]);
193
- return;
194
- } else {
195
- if (index === -1) {
196
- filtersStore.setFilter({ field: column.name, value, operator });
197
- } else {
198
- filtersStore.setFilters([...filtersStore.filters.slice(0, index), { field: column.name, value, operator }, ...filtersStore.filters.slice(index + 1)])
199
- }
200
- }
201
- emits('update:filters', [...filtersStore.filters]);
202
- }
203
-
204
- function getFilterItem({ column, operator }) {
205
- return filtersStore.filters.find(f => f.field === column.name && f.operator === operator)?.value || '';
206
- }
207
-
208
- async function clear() {
209
- filtersStore.clearFilters();
210
- }
211
- </script>
@@ -1,17 +0,0 @@
1
- <script setup lang="ts">
2
- defineProps<{
3
- msg: string
4
- }>()
5
- </script>
6
-
7
- <template>
8
- <div class="greetings">
9
- <h1 class="green">{{ msg }}</h1>
10
- <h3>
11
- You’ve successfully created a project with
12
- </h3>
13
- </div>
14
- </template>
15
-
16
- <style scoped>
17
- </style>
@@ -1,24 +0,0 @@
1
- <template>
2
- <RouterLink
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"
5
- :class="{
6
- 'px-4': isChild,
7
- 'px-2': !isChild,
8
- 'bg-nav-menu-bg-active dark:bg-gray-700': item.resourceId ?
9
- ($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
10
- ($route.name === item.path)
11
- }"
12
- >
13
- <component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" ></component>
14
- <span class="ms-3">{{ item.label }}</span>
15
- </RouterLink>
16
- </template>
17
-
18
- <script setup lang="ts">
19
- import { getIcon } from '@/utils';
20
-
21
- const props = defineProps(['item', 'isChild']);
22
-
23
-
24
- </script>
@@ -1,265 +0,0 @@
1
- <template>
2
- <div class="rounded-default">
3
-
4
- <div
5
- class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black rounded-default"
6
- >
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">
10
- <tr>
11
- <th scope="col" class="px-6 py-3">
12
- Field
13
- </th>
14
- <th scope="col" class="px-6 py-3 w-4/6">
15
- Value
16
- </th>
17
- </tr>
18
- </thead>
19
- <tbody>
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"
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)"
30
- />
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>
129
- </tr>
130
-
131
- </tbody>
132
- </table>
133
- </form>
134
- </div>
135
- </div>
136
-
137
- </template>
138
-
139
- <script setup>
140
-
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();
152
-
153
- const props = defineProps({
154
- loading: Boolean,
155
- resource: Object,
156
- record: Object,
157
- validating: Boolean,
158
- customComponentsPerColumn: Object,
159
- });
160
-
161
-
162
- const unmasked = ref({});
163
-
164
- const mode = computed(() => props.record && Object.keys(props.record).length ? 'edit' : 'create');
165
-
166
- const emit = defineEmits(['update:record', 'update:isValid']);
167
-
168
- const currentValues = ref({});
169
-
170
-
171
- const columnError = (column) => {
172
- const val = computed(() => {
173
- if ( column.required[mode.value] && (currentValues.value[column.name] === undefined || currentValues.value[column.name] === null || currentValues.value[column.name] === '') ) {
174
- return 'This field is required';
175
- }
176
- if ( column.type === 'string' || column.type === 'text' ) {
177
- if ( column.maxLength && currentValues.value[column.name]?.length > column.maxLength ) {
178
- return `This field must be shorter than ${column.maxLength} characters`;
179
- }
180
- if ( column.minLength && currentValues.value[column.name]?.length < column.minLength ) {
181
- return `This field must be longer than ${column.minLength} characters`;
182
- }
183
- }
184
- if ( ['integer', 'decimal', 'float'].includes(column.type) ) {
185
- if ( column.minValue !== undefined && currentValues.value[column.name] < column.minValue ) {
186
- return `This field must be greater than ${column.minValue}`;
187
- }
188
- if ( column.maxValue !== undefined && currentValues.value[column.name] > column.maxValue ) {
189
- return `This field must be less than ${column.maxValue}`;
190
- }
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
- }
208
- return null;
209
- });
210
- return val.value;
211
- };
212
-
213
-
214
- const setCurrentValue = (key, value) => {
215
- currentValues.value[key] = value;
216
-
217
- emit('update:record', currentValues.value);
218
- };
219
-
220
- onMounted(() => {
221
- Object.keys(props.record).forEach((key) => {
222
- currentValues.value[key] = props.record[key];
223
- });
224
- initFlowbite();
225
-
226
-
227
- });
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
-
250
- const coreStore = useCoreStore();
251
-
252
- const editableColumns = computed(() => {
253
- const mode = props.record ? 'edit' : 'create';
254
- return props.resource?.columns?.filter(column => column.showIn.includes(mode));
255
- });
256
-
257
- const isValid = computed(() => {
258
- return editableColumns.value?.every(column => !columnError(column));
259
- });
260
-
261
- watch(() => isValid.value, (value) => {
262
- emit('update:isValid', value);
263
- });
264
-
265
- </script>
@@ -1,13 +0,0 @@
1
- <template>
2
- <div
3
- role="status" class="max-w-sm animate-pulse"
4
- >
5
- <div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"></div>
6
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px] mb-2.5"></div>
7
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
8
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[330px] mb-2.5"></div>
9
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[300px] mb-2.5"></div>
10
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px]"></div>
11
- <span class="sr-only">Loading...</span>
12
- </div>
13
- </template>
@@ -1,60 +0,0 @@
1
- <template>
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>
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>
17
- </span>
18
- <span v-else-if="column.enum">
19
- {{ checkEmptyValues(column.enum.find(e => e.value === record[column.name])?.label,route.meta.type) }}
20
- </span>
21
- <span v-else-if="column.type === 'datetime'">
22
- {{ checkEmptyValues(formatDate(record[column.name]),route.meta.type) }}
23
-
24
- </span>
25
- <span v-else>
26
- {{ checkEmptyValues(record[column.name],route.meta.type) }}
27
- </span>
28
- </div>
29
- </template>
30
-
31
-
32
- <script setup>
33
-
34
- import dayjs from 'dayjs';
35
- import utc from 'dayjs/plugin/utc';
36
- import timezone from 'dayjs/plugin/timezone';
37
- import {checkEmptyValues} from '@/utils';
38
- import { useRoute, useRouter } from 'vue-router';
39
-
40
-
41
- import { useCoreStore } from '@/stores/core';
42
-
43
- const coreStore = useCoreStore();
44
- const route = useRoute();
45
-
46
-
47
- dayjs.extend(utc);
48
- dayjs.extend(timezone);
49
-
50
- const props = defineProps({
51
- column: Object,
52
- record: Object
53
- });
54
-
55
-
56
- function formatDate(date) {
57
- if (!date) return '';
58
- return dayjs.utc(date).local().format(coreStore.config?.datesFormat || 'YYYY-MM-DD HH:mm:ss');
59
- }
60
- </script>
@@ -1,5 +0,0 @@
1
- <template>
2
- <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
3
- <path d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"/>
4
- </svg>
5
- </template>
@@ -1,7 +0,0 @@
1
- <template>
2
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
3
- <path
4
- d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
5
- />
6
- </svg>
7
- </template>
@@ -1,7 +0,0 @@
1
- <template>
2
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
3
- <path
4
- d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
5
- />
6
- </svg>
7
- </template>
@@ -1,7 +0,0 @@
1
- <template>
2
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
3
- <path
4
- d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
5
- />
6
- </svg>
7
- </template>
@@ -1,7 +0,0 @@
1
- <template>
2
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
3
- <path
4
- d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
5
- />
6
- </svg>
7
- </template>
@@ -1,5 +0,0 @@
1
- <template>
2
- <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24">
3
- <path fill-rule="evenodd" d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm11-4a1 1 0 1 0-2 0v4a1 1 0 0 0 .293.707l3 3a1 1 0 0 0 1.414-1.414L13 11.586V8Z" clip-rule="evenodd"></path>
4
- </svg>
5
- </template>