adminforth 1.2.96 → 1.2.98

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 (101) hide show
  1. package/dist/index.js +2 -1
  2. package/dist/modules/restApi.js +22 -23
  3. package/dist/spa/index.html +4 -4
  4. package/dist/spa/package-lock.json +421 -6
  5. package/dist/spa/package.json +10 -2
  6. package/dist/spa/src/App.vue +236 -94
  7. package/dist/spa/src/assets/base.css +2 -0
  8. package/dist/spa/src/assets/logo.svg +19 -1
  9. package/dist/spa/src/components/AcceptModal.vue +3 -10
  10. package/dist/spa/src/components/Breadcrumbs.vue +3 -2
  11. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
  12. package/dist/spa/src/components/CustomDatePicker.vue +25 -10
  13. package/dist/spa/src/components/CustomDateRangePicker.vue +14 -5
  14. package/dist/spa/src/components/Dropdown.vue +19 -5
  15. package/dist/spa/src/components/Filters.vue +103 -38
  16. package/dist/spa/src/components/MenuLink.vue +3 -3
  17. package/dist/spa/src/components/ResourceForm.vue +205 -116
  18. package/dist/spa/{spa/src → src}/components/ResourceListTable.vue +6 -3
  19. package/dist/spa/{spa/src → src}/components/Toast.vue +9 -3
  20. package/dist/spa/src/components/ValueRenderer.vue +30 -8
  21. package/dist/spa/src/index.scss +2 -1
  22. package/dist/spa/src/main.ts +2 -2
  23. package/dist/spa/src/router/index.ts +29 -19
  24. package/dist/spa/src/stores/core.ts +65 -59
  25. package/dist/spa/src/stores/modal.ts +13 -3
  26. package/dist/spa/src/types/AdminForthConfig.ts +1477 -0
  27. package/dist/spa/src/types/FrontendAPI.ts +121 -0
  28. package/dist/spa/src/utils.ts +69 -10
  29. package/dist/spa/src/views/CreateView.vue +69 -16
  30. package/dist/spa/src/views/EditView.vue +68 -14
  31. package/dist/spa/src/views/ListView.vue +100 -371
  32. package/dist/spa/src/views/LoginView.vue +67 -29
  33. package/dist/spa/src/views/ResourceParent.vue +1 -2
  34. package/dist/spa/src/views/ShowView.vue +116 -25
  35. package/dist/spa/tailwind.config.js +8 -3
  36. package/dist/spa/vite.config.ts +13 -0
  37. package/index.ts +2 -1
  38. package/modules/restApi.ts +27 -26
  39. package/package.json +1 -1
  40. package/dist/spa/.vscode/extensions.json +0 -6
  41. package/dist/spa/public/favicon.ico +0 -0
  42. package/dist/spa/spa/.eslintrc.cjs +0 -14
  43. package/dist/spa/spa/.vscode/extensions.json +0 -6
  44. package/dist/spa/spa/README.md +0 -39
  45. package/dist/spa/spa/env.d.ts +0 -1
  46. package/dist/spa/spa/index.html +0 -23
  47. package/dist/spa/spa/package-lock.json +0 -4573
  48. package/dist/spa/spa/package.json +0 -49
  49. package/dist/spa/spa/postcss.config.js +0 -6
  50. package/dist/spa/spa/public/favicon.ico +0 -0
  51. package/dist/spa/spa/src/App.vue +0 -314
  52. package/dist/spa/spa/src/assets/base.css +0 -0
  53. package/dist/spa/spa/src/assets/logo.svg +0 -19
  54. package/dist/spa/spa/src/components/AcceptModal.vue +0 -45
  55. package/dist/spa/spa/src/components/Breadcrumbs.vue +0 -40
  56. package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  57. package/dist/spa/spa/src/components/CustomDatePicker.vue +0 -174
  58. package/dist/spa/spa/src/components/CustomDateRangePicker.vue +0 -218
  59. package/dist/spa/spa/src/components/Dropdown.vue +0 -168
  60. package/dist/spa/spa/src/components/Filters.vue +0 -222
  61. package/dist/spa/spa/src/components/HelloWorld.vue +0 -17
  62. package/dist/spa/spa/src/components/MenuLink.vue +0 -24
  63. package/dist/spa/spa/src/components/ResourceForm.vue +0 -289
  64. package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
  65. package/dist/spa/spa/src/components/ValueRenderer.vue +0 -66
  66. package/dist/spa/spa/src/components/icons/IconCalendar.vue +0 -5
  67. package/dist/spa/spa/src/components/icons/IconCommunity.vue +0 -7
  68. package/dist/spa/spa/src/components/icons/IconDocumentation.vue +0 -7
  69. package/dist/spa/spa/src/components/icons/IconEcosystem.vue +0 -7
  70. package/dist/spa/spa/src/components/icons/IconSupport.vue +0 -7
  71. package/dist/spa/spa/src/components/icons/IconTime.vue +0 -5
  72. package/dist/spa/spa/src/components/icons/IconTooling.vue +0 -19
  73. package/dist/spa/spa/src/index.scss +0 -27
  74. package/dist/spa/spa/src/main.ts +0 -18
  75. package/dist/spa/spa/src/router/index.ts +0 -63
  76. package/dist/spa/spa/src/stores/core.ts +0 -144
  77. package/dist/spa/spa/src/stores/modal.ts +0 -48
  78. package/dist/spa/spa/src/utils.ts +0 -103
  79. package/dist/spa/spa/src/views/CreateView.vue +0 -156
  80. package/dist/spa/spa/src/views/EditView.vue +0 -157
  81. package/dist/spa/spa/src/views/HomeView.vue +0 -8
  82. package/dist/spa/spa/src/views/ListView.vue +0 -258
  83. package/dist/spa/spa/src/views/LoginView.vue +0 -143
  84. package/dist/spa/spa/src/views/ResourceParent.vue +0 -17
  85. package/dist/spa/spa/src/views/ShowView.vue +0 -184
  86. package/dist/spa/spa/tailwind.config.js +0 -17
  87. package/dist/spa/spa/tsconfig.app.json +0 -14
  88. package/dist/spa/spa/tsconfig.json +0 -11
  89. package/dist/spa/spa/tsconfig.node.json +0 -19
  90. package/dist/spa/spa/vite.config.ts +0 -56
  91. package/dist/spa/src/views/HomeView.vue +0 -8
  92. package/dist/types.js +0 -30
  93. /package/dist/spa/{spa/public → public}/assets/favicon.png +0 -0
  94. /package/dist/spa/{spa/src → src}/components/CustomRangePicker.vue +0 -0
  95. /package/dist/spa/{spa/src → src}/components/SkeleteLoader.vue +0 -0
  96. /package/dist/spa/{spa/src → src}/composables/useFrontendApi.ts +0 -0
  97. /package/dist/spa/{spa/src → src}/composables/useStores.ts +0 -0
  98. /package/dist/spa/{spa/src → src}/spa_types/core.ts +0 -0
  99. /package/dist/spa/{spa/src → src}/stores/filters.ts +0 -0
  100. /package/dist/spa/{spa/src → src}/stores/toast.ts +0 -0
  101. /package/dist/spa/{spa/src → src}/stores/user.ts +0 -0
@@ -1,113 +1,137 @@
1
1
  <template>
2
- <div>
3
-
2
+ <div class="rounded-default">
4
3
  <div
5
- class="relative shadow-md sm:rounded-lg dark:shadow-2xl"
4
+ class="relative shadow-resourseFormShadow dark:shadow-darkResourseFormShadow sm:rounded-lg dark:shadow-2xl rounded-default"
6
5
  >
7
6
  <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">
7
+ <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 ">
8
+ <thead class="text-xs text-gray-700 uppercase bg-lightFormHeading dark:bg-gray-700 dark:text-gray-400 block md:table-row-group">
10
9
  <tr>
11
- <th scope="col" class="px-6 py-3">
10
+ <th scope="col" class="px-6 py-3 hidden md:table-cell">
12
11
  Field
13
12
  </th>
14
- <th scope="col" class="px-6 py-3 w-4/6">
13
+ <th scope="col" class="px-6 py-3 w-5/6 hidden md:table-cell">
15
14
  Value
16
15
  </th>
16
+
17
+
17
18
  </tr>
18
19
  </thead>
19
20
  <tbody>
20
21
  <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"
22
+ v-if="currentValues !== null"
23
+ class="bg-ligftForm dark:bg-gray-800 border-b dark:border-gray-700 block md:table-row"
22
24
  >
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'"
28
- />
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>
25
+ <td class="px-6 py-4 sm:pb-0 whitespace-nowrap flex items-center block md:table-cell"> <!--align-top-->
26
+ {{ column.label }}
27
+ <span :data-tooltip-target="`tooltip-show-${i}`" class="ml-1 relative inline-block">
28
+ <IconExclamationCircleSolid v-if="column.required[mode]" class="w-4 h-4"
29
+ :class="(columnError(column) && validating) ? 'text-red-500 dark:text-red-400' : 'text-gray-400 dark:text-gray-500'"
30
+ />
31
+ </span>
32
+ <div :id="`tooltip-show-${i}`"
33
+ role="tooltip"
34
+ class="ml-1 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">
35
+ Required field
36
+ <div class="tooltip-arrow" data-popper-arrow></div>
37
+ </div>
38
+ </td>
39
+ <td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative block md:table-cell">
40
+ <template v-if="column?.components?.[props.source]?.file">
41
+ <component
42
+ :is="getCustomComponent(column.components[props.source])"
43
+ :column="column"
44
+ :value="currentValues[column.name]"
45
+ @update:value="setCurrentValue(column.name, $event)"
46
+ :meta="column.components[props.source].meta"
47
+ :record="currentValues"
48
+ @update:inValidity="customComponentsInValidity[column.name] = $event"
49
+ @update:emptiness="customComponentsEmptiness[column.name] = $event"
50
+ />
51
+ </template>
52
+ <template v-else>
53
+ <Dropdown
54
+ single
55
+ v-if="column.foreignResource"
56
+ :options="columnOptions[column.name] || []"
57
+ :placeholder = "columnOptions[column.name]?.length ?'Select...': 'There are no options available'"
58
+ :modelValue="currentValues[column.name]"
59
+ @update:modelValue="setCurrentValue(column.name, $event)"
60
+ ></Dropdown>
61
+ <Dropdown
62
+ single
63
+ v-else-if="column.enum"
64
+ :options="column.enum"
65
+ :modelValue="currentValues[column.name]"
66
+ @update:modelValue="setCurrentValue(column.name, $event)"
67
+ />
68
+ <Dropdown
69
+ single
70
+ v-else-if="column.type === 'boolean'"
71
+ :options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
72
+ :modelValue="currentValues[column.name]"
73
+ @update:modelValue="setCurrentValue(column.name, $event)"
74
+ />
75
+ <input
76
+ v-else-if="['integer'].includes(column.type)"
77
+ type="number"
78
+ step="1"
79
+ 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"
80
+ placeholder="0"
81
+ :value="currentValues[column.name]"
82
+ @input="setCurrentValue(column.name, $event.target.value)"
83
+ >
84
+ <CustomDatePicker
85
+ v-else-if="['datetime'].includes(column.type)"
86
+ :column="column"
87
+ :valueStart="currentValues[column.name]"
88
+ auto-hide
89
+ @update:valueStart="setCurrentValue(column.name, $event)"
90
+ />
91
+ <input
92
+ v-else-if="['decimal', 'float'].includes(column.type)"
93
+ type="number"
94
+ step="0.1"
95
+ 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"
96
+ placeholder="0.0"
97
+ :value="currentValues[column.name]"
98
+ @input="setCurrentValue(column.name, $event.target.value)"
99
+ />
100
+ <textarea
101
+ v-else-if="['text', 'richtext'].includes(column.type)"
102
+ 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"
103
+ placeholder="Text"
104
+ :value="currentValues[column.name]"
105
+ @input="setCurrentValue(column.name, $event.target.value)"
106
+ >
107
+ </textarea>
108
+ <input
109
+ v-else
110
+ :type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
111
+ 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"
112
+ placeholder="Text"
113
+ :value="currentValues[column.name]"
114
+ @input="setCurrentValue(column.name, $event.target.value)"
115
+ autocomplete="false"
116
+ data-lpignore="true"
117
+ readonly
118
+ onfocus="this.removeAttribute('readonly');"
119
+ >
120
+
121
+ <button
122
+ v-if="column.masked"
123
+ type="button"
124
+ @click="unmasked[column.name] = !unmasked[column.name]"
125
+ class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
126
+ >
127
+ <IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
128
+ <IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
129
+ </button>
130
+ </template>
131
+ <div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
132
+
133
+ <div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
134
+
111
135
  </td>
112
136
  </tr>
113
137
 
@@ -121,33 +145,55 @@
121
145
 
122
146
  <script setup>
123
147
 
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
148
  import CustomDatePicker from "@/components/CustomDatePicker.vue";
149
+ import Dropdown from '@/components/Dropdown.vue';
150
+ import { useCoreStore } from '@/stores/core';
151
+ import { callAdminForthApi, getCustomComponent } from '@/utils';
152
+ import { IconExclamationCircleSolid, IconEyeSlashSolid, IconEyeSolid } from '@iconify-prerendered/vue-flowbite';
153
+ import { computedAsync } from '@vueuse/core';
154
+ import { initFlowbite } from 'flowbite';
155
+ import { computed, onMounted, ref, watch } from 'vue';
156
+ import { useRouter } from 'vue-router';
157
+
158
+ const router = useRouter();
131
159
 
132
160
  const props = defineProps({
133
161
  loading: Boolean,
134
- resourceColumns: Object,
162
+ resource: Object,
135
163
  record: Object,
136
164
  validating: Boolean,
165
+ source: String,
137
166
  });
138
167
 
139
-
140
168
  const unmasked = ref({});
141
169
 
142
170
  const mode = computed(() => props.record && Object.keys(props.record).length ? 'edit' : 'create');
143
171
 
144
172
  const emit = defineEmits(['update:record', 'update:isValid']);
145
173
 
146
- const currentValues = ref({});
174
+ const currentValues = ref(null);
175
+
176
+ const customComponentsInValidity = ref({});
177
+ const customComponentsEmptiness = ref({});
178
+
147
179
 
148
180
  const columnError = (column) => {
149
181
  const val = computed(() => {
150
- if ( column.required[mode.value] && (currentValues.value[column.name] === undefined || currentValues.value[column.name] === null || currentValues.value[column.name] === '') ) {
182
+ if (!currentValues.value) {
183
+ return null;
184
+ }
185
+ if (customComponentsInValidity.value[column.name]) {
186
+ return customComponentsInValidity.value[column.name];
187
+ }
188
+
189
+ if (
190
+ column.required[mode.value] &&
191
+ (currentValues.value[column.name] === undefined || currentValues.value[column.name] === null || currentValues.value[column.name] === '') &&
192
+ // if component is custum it might tell other criteria for emptiness by emitting 'update:emptiness'
193
+ // components which do not emit 'update:emptiness' will have undefined value in customComponentsEmptiness
194
+ (customComponentsEmptiness.value[column.name] !== false)
195
+
196
+ ) {
151
197
  return 'This field is required';
152
198
  }
153
199
  if ( column.type === 'string' || column.type === 'text' ) {
@@ -159,39 +205,82 @@ const columnError = (column) => {
159
205
  }
160
206
  }
161
207
  if ( ['integer', 'decimal', 'float'].includes(column.type) ) {
162
- if ( column.minValue !== undefined && currentValues.value[column.name] < column.minValue ) {
208
+ if ( column.minValue !== undefined
209
+ && currentValues.value[column.name] !== null
210
+ && currentValues.value[column.name] < column.minValue
211
+ ) {
163
212
  return `This field must be greater than ${column.minValue}`;
164
213
  }
165
214
  if ( column.maxValue !== undefined && currentValues.value[column.name] > column.maxValue ) {
166
215
  return `This field must be less than ${column.maxValue}`;
167
216
  }
168
217
  }
218
+ if ( column.validation && column.validation.length ) {
219
+ const validationArray = column.validation;
220
+ for (let i = 0; i < validationArray.length; i++) {
221
+ if (validationArray[i].regExp) {
222
+ const regExp = new RegExp(validationArray[i].regExp);
223
+ let value = currentValues.value[column.name];
224
+ if (value === undefined || value === null) {
225
+ value = '';
226
+ }
227
+ if (!regExp.test(value)) {
228
+ return validationArray[i].message;
229
+ }
230
+ }
231
+ }
232
+
233
+ }
169
234
  return null;
170
235
  });
171
236
  return val.value;
172
237
  };
173
238
 
174
239
 
175
-
176
-
177
240
  const setCurrentValue = (key, value) => {
178
- currentValues.value[key] = value;
179
-
241
+ const col = props.resource.columns.find((column) => column.name === key);
242
+ if (['integer', 'float'].includes(col.type) && (value || value === 0)) {
243
+ currentValues.value[key] = +value;
244
+ } else {
245
+ currentValues.value[key] = value;
246
+ }
247
+ currentValues.value = { ...currentValues.value };
248
+ console.log('3️⃣ setCurrentValue', key, value);
180
249
  emit('update:record', currentValues.value);
181
250
  };
182
251
 
183
252
  onMounted(() => {
184
- Object.keys(props.record).forEach((key) => {
185
- currentValues.value[key] = props.record[key];
186
- });
253
+ currentValues.value = Object.assign({}, props.record);
254
+ console.log('2️⃣ currentValues', JSON.stringify(currentValues.value));
187
255
  initFlowbite();
256
+ emit('update:isValid', isValid.value);
188
257
  });
189
258
 
190
- const coreStore = useCoreStore();
259
+ const columnOptions = computedAsync(async () => {
260
+ return (await Promise.all(
261
+ Object.values(props.resource.columns).map(async (column) => {
262
+ if (column.foreignResource) {
263
+ const list = await callAdminForthApi({
264
+ method: 'POST',
265
+ path: `/get_resource_foreign_data`,
266
+ body: {
267
+ resourceId: router.currentRoute.value.params.resourceId,
268
+ column: column.name,
269
+ limit: 1000,
270
+ offset: 0,
271
+ },
272
+ });
273
+ return { [column.name]: list.items };
274
+ }
275
+ })
276
+ )).reduce((acc, val) => Object.assign(acc, val), {})
277
+
278
+ }, {});
279
+
191
280
 
192
281
  const editableColumns = computed(() => {
193
282
  const mode = props.record ? 'edit' : 'create';
194
- return props.resourceColumns?.filter(column => column.showIn.includes(mode));
283
+ return props.resource?.columns?.filter(column => column.showIn.includes(mode));
195
284
  });
196
285
 
197
286
  const isValid = computed(() => {
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <!-- table -->
3
- <div class="relative overflow-x-auto shadow-listTableShadow dark:shadow-black overflow-y-hidden rounded-default">
3
+ <div class="relative overflow-x-auto shadow-listTableShadow dark:shadow-darkListTableShadow overflow-y-hidden rounded-default">
4
4
 
5
5
  <!-- skelet loader -->
6
6
  <div role="status" v-if="!resource || !resource.columns"
@@ -55,7 +55,7 @@
55
55
  </thead>
56
56
  <tbody>
57
57
  <SkeleteLoader v-if="!rows" :columns="3" :rows="resource?.columns.length + 2"/>
58
- <tr v-else-if="rows.length === 0" class="bg-lightListTable border-b dark:bg-darkListTable dark:border-darkListTableBorder">
58
+ <tr v-else-if="rows.length === 0" class="bg-lightListTable dark:bg-darkListTable dark:border-darkListTableBorder">
59
59
  <td :colspan="resource?.columns.length + 2">
60
60
 
61
61
  <div id="toast-simple"
@@ -69,7 +69,9 @@
69
69
  </tr>
70
70
 
71
71
  <tr @click="onClick($event,row)" v-else v-for="(row, rowI) in rows" :key="row.id"
72
- class="bg-lightListTable border-b dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover cursor-pointer">
72
+ class="bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover cursor-pointer"
73
+ :class="{'border-b': rowI !== rows.length - 1}"
74
+ >
73
75
  <td class="w-4 p-4 cursor-default" @click="(e)=>{e.stopPropagation()}">
74
76
  <div class="flex items center ">
75
77
  <input
@@ -375,6 +377,7 @@ async function deleteRecord(row) {
375
377
  primaryKey: row._primaryKeyValue,
376
378
  }
377
379
  });
380
+ console.log('safsa', res)
378
381
  if (!res.error){
379
382
  emits('update:records', true)
380
383
  showSuccesTost('Record deleted successfully')
@@ -1,7 +1,13 @@
1
1
  <template>
2
2
 
3
3
 
4
- <div id="toast-default" class="flex items-center w-full p-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800" role="alert">
4
+ <div id="toast-default" class="flex items-center w-full p-4 text-gray-500 rounded-lg shadow dark:text-gray-400 dark:bg-gray-800" role="alert"
5
+ :class="
6
+ {
7
+ 'danger': 'bg-red-100',
8
+ }[toast.variant]
9
+ "
10
+ >
5
11
  <div v-if="toast.variant == 'info'" class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-blue-500 bg-blue-100 rounded-lg dark:bg-blue-800 dark:text-blue-200">
6
12
  <svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
7
13
  <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.147 15.085a7.159 7.159 0 0 1-6.189 3.307A6.713 6.713 0 0 1 3.1 15.444c-2.679-4.513.287-8.737.888-9.548A4.373 4.373 0 0 0 5 1.608c1.287.953 6.445 3.218 5.537 10.5 1.5-1.122 2.706-3.01 2.853-6.14 1.433 1.049 3.993 5.395 1.757 9.117Z"/>
@@ -27,8 +33,8 @@
27
33
  <span class="sr-only">Check icon</span>
28
34
  </div>
29
35
 
30
- <div class="ms-3 text-sm font-normal max-w-xs" v-if="toast.messageHtml" v-html="toast.messageHtml"></div>
31
- <div class="ms-3 text-sm font-normal max-w-xs" v-else>{{toast.message}}</div>
36
+ <div class="ms-3 text-sm font-normal max-w-xs pr-2" v-if="toast.messageHtml" v-html="toast.messageHtml"></div>
37
+ <div class="ms-3 text-sm font-normal max-w-xs pr-2" v-else>{{toast.message}}</div>
32
38
  <button @click="closeToast" type="button" class="ms-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700" >
33
39
  <svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
34
40
  <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
@@ -1,18 +1,30 @@
1
1
  <template>
2
2
  <div>
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>
3
+ <span @click="(e)=>{e.stopPropagation()}" v-if="column.foreignResource">
4
+ <RouterLink v-if="record[column.name]" class="font-medium text-blue-600 dark:text-blue-500 hover:brightness-110"
5
+ :to="{ name: 'resource-show', params: { resourceId: column.foreignResource.resourceId, primaryKey: record[column.name].pk } }">
6
+ {{ record[column.name].label }}
7
+ </RouterLink>
8
+ <div v-else>
9
+ <span class="text-gray-400">-</span>
10
+ </div>
11
+ </span>
12
+
13
+ <span v-else-if="column.type === 'boolean'">
14
+ <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>
5
15
  <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>
6
16
  </span>
7
17
  <span v-else-if="column.enum">
8
- {{ column.enum.find(e => e.value === row[column.name])?.label }}
18
+ {{ checkEmptyValues(column.enum.find(e => e.value === record[column.name])?.label,route.meta.type) }}
19
+ </span>
20
+ <span v-else-if="column.type === 'datetime'" class="whitespace-nowrap">
21
+ {{ checkEmptyValues(formatDate(record[column.name]),route.meta.type) }}
9
22
  </span>
10
- <span v-else-if="column.type === 'datetime'">
11
- {{ formatDate(row[column.name]) }}
12
-
23
+ <span v-else-if="column.type === 'richtext'">
24
+ <div v-html="protectAgainstXSS(record[column.name])"></div>
13
25
  </span>
14
26
  <span v-else>
15
- {{ row[column.name] }}
27
+ {{ checkEmptyValues(record[column.name],route.meta.type) }}
16
28
  </span>
17
29
  </div>
18
30
  </template>
@@ -23,19 +35,29 @@
23
35
  import dayjs from 'dayjs';
24
36
  import utc from 'dayjs/plugin/utc';
25
37
  import timezone from 'dayjs/plugin/timezone';
38
+ import {checkEmptyValues} from '@/utils';
39
+ import { useRoute, useRouter } from 'vue-router';
40
+ import sanitizeHtml from 'sanitize-html';
41
+
26
42
 
27
43
  import { useCoreStore } from '@/stores/core';
28
44
 
29
45
  const coreStore = useCoreStore();
46
+ const route = useRoute();
47
+
30
48
 
31
49
  dayjs.extend(utc);
32
50
  dayjs.extend(timezone);
33
51
 
34
52
  const props = defineProps({
35
53
  column: Object,
36
- row: Object
54
+ record: Object
37
55
  });
38
56
 
57
+ function protectAgainstXSS(value) {
58
+ return sanitizeHtml(value);
59
+ }
60
+
39
61
 
40
62
  function formatDate(date) {
41
63
  if (!date) return '';
@@ -23,4 +23,5 @@
23
23
  // ::-webkit-scrollbar-thumb:hover {
24
24
  // @apply bg-gray-700
25
25
  // }
26
- // }
26
+ // }
27
+
@@ -6,10 +6,10 @@ import { createPinia } from 'pinia'
6
6
  import App from './App.vue'
7
7
  import router from './router'
8
8
 
9
- const app = createApp(App)
9
+ export const app = createApp(App)
10
10
  /* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */
11
11
 
12
-
12
+
13
13
  app.use(createPinia())
14
14
  app.use(router)
15
15
 
@@ -1,23 +1,23 @@
1
1
  import { createRouter, createWebHistory } from 'vue-router'
2
- import HomeView from '../views/HomeView.vue'
3
2
  import ResourceParent from '@/views/ResourceParent.vue'
4
- import ListView from '@/views/ListView.vue'
5
- import ShowView from '@/views/ShowView.vue'
6
- import EditView from '@/views/EditView.vue'
7
- import CreateView from '@/views/CreateView.vue'
3
+ import { useUserStore } from '@/stores/user'
4
+ /* IMPORTANT:ADMINFORTH ROUTES IMPORTS */
8
5
 
9
6
  const router = createRouter({
10
7
  history: createWebHistory(import.meta.env.BASE_URL),
11
8
  routes: [
12
- {
13
- path: '/',
14
- name: 'home',
15
- component: HomeView
16
- },
17
9
  {
18
10
  path: '/login',
19
11
  name: 'login',
20
- component: () => import('@/views/LoginView.vue')
12
+ component: () => import('@/views/LoginView.vue'),
13
+ meta: { title: 'login' },
14
+ beforeEnter: async (to, from, next) => {
15
+ if(localStorage.getItem('isAuthorized') === 'true'){
16
+ next({name: 'home'})
17
+ } else {
18
+ next()
19
+ }
20
+ }
21
21
  },
22
22
  {
23
23
  path: '/resource/:resourceId',
@@ -26,23 +26,29 @@ const router = createRouter({
26
26
  children: [
27
27
  {
28
28
  path: '',
29
- component: ListView,
30
- name: 'resource-list'
29
+ component: () => import('@/views/ListView.vue'),
30
+ name: 'resource-list',
31
+ meta: { title: 'list',type: 'list' }
31
32
  },
32
33
  {
33
34
  path: 'show/:primaryKey',
34
- component: ShowView,
35
- name: 'resource-show'
35
+ component: () => import('@/views/ShowView.vue'),
36
+ name: 'resource-show',
37
+ meta: { title: 'show', type: 'show'}
38
+
36
39
  },
37
40
  {
38
41
  path: 'edit/:primaryKey',
39
- component: EditView,
40
- name: 'resource-edit'
42
+ component: () => import('@/views/EditView.vue'),
43
+ name: 'resource-edit',
44
+ meta: { title: 'edit', type: 'edit'}
41
45
  },
42
46
  {
43
47
  path: 'create',
44
- component: CreateView,
45
- name: 'resource-create'
48
+ component: () => import('@/views/CreateView.vue'),
49
+ name: 'resource-create',
50
+ meta: { title: 'create', type: 'create'}
51
+
46
52
  },
47
53
  ]
48
54
  },
@@ -50,4 +56,8 @@ const router = createRouter({
50
56
  ]
51
57
  })
52
58
 
59
+
60
+
61
+
62
+
53
63
  export default router