adminforth 1.0.27 → 1.0.29

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 (51) hide show
  1. package/dist/spa/spa/.eslintrc.cjs +14 -0
  2. package/dist/spa/spa/.vscode/extensions.json +6 -0
  3. package/dist/spa/spa/README.md +39 -0
  4. package/dist/spa/spa/env.d.ts +1 -0
  5. package/dist/spa/spa/index.html +23 -0
  6. package/dist/spa/spa/package-lock.json +4158 -0
  7. package/dist/spa/spa/package.json +41 -0
  8. package/dist/spa/spa/postcss.config.js +6 -0
  9. package/dist/spa/spa/public/favicon.ico +0 -0
  10. package/dist/spa/spa/src/App.vue +172 -0
  11. package/dist/spa/spa/src/assets/base.css +0 -0
  12. package/dist/spa/spa/src/assets/logo.svg +1 -0
  13. package/dist/spa/spa/src/components/AcceptModal.vue +52 -0
  14. package/dist/spa/spa/src/components/Breadcrumbs.vue +40 -0
  15. package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +26 -0
  16. package/dist/spa/spa/src/components/CustomDatePicker.vue +161 -0
  17. package/dist/spa/spa/src/components/CustomDateRangePicker.vue +209 -0
  18. package/dist/spa/spa/src/components/Dropdown.vue +154 -0
  19. package/dist/spa/spa/src/components/Filters.vue +157 -0
  20. package/dist/spa/spa/src/components/HelloWorld.vue +17 -0
  21. package/dist/spa/spa/src/components/MenuLink.vue +24 -0
  22. package/dist/spa/spa/src/components/ResourceForm.vue +205 -0
  23. package/dist/spa/spa/src/components/SingleSkeletLoader.vue +13 -0
  24. package/dist/spa/spa/src/components/ValueRenderer.vue +44 -0
  25. package/dist/spa/spa/src/components/icons/IconCalendar.vue +5 -0
  26. package/dist/spa/spa/src/components/icons/IconCommunity.vue +7 -0
  27. package/dist/spa/spa/src/components/icons/IconDocumentation.vue +7 -0
  28. package/dist/spa/spa/src/components/icons/IconEcosystem.vue +7 -0
  29. package/dist/spa/spa/src/components/icons/IconSupport.vue +7 -0
  30. package/dist/spa/spa/src/components/icons/IconTime.vue +5 -0
  31. package/dist/spa/spa/src/components/icons/IconTooling.vue +19 -0
  32. package/dist/spa/spa/src/index.scss +26 -0
  33. package/dist/spa/spa/src/main.ts +18 -0
  34. package/dist/spa/spa/src/router/index.ts +53 -0
  35. package/dist/spa/spa/src/stores/core.ts +138 -0
  36. package/dist/spa/spa/src/stores/modal.ts +38 -0
  37. package/dist/spa/spa/src/utils.ts +44 -0
  38. package/dist/spa/spa/src/views/CreateView.vue +103 -0
  39. package/dist/spa/spa/src/views/EditView.vue +103 -0
  40. package/dist/spa/spa/src/views/HomeView.vue +8 -0
  41. package/dist/spa/spa/src/views/ListView.vue +529 -0
  42. package/dist/spa/spa/src/views/LoginView.vue +122 -0
  43. package/dist/spa/spa/src/views/ResourceParent.vue +18 -0
  44. package/dist/spa/spa/src/views/ShowView.vue +93 -0
  45. package/dist/spa/spa/tailwind.config.js +12 -0
  46. package/dist/spa/spa/tsconfig.app.json +14 -0
  47. package/dist/spa/spa/tsconfig.json +11 -0
  48. package/dist/spa/spa/tsconfig.node.json +19 -0
  49. package/dist/spa/spa/vite.config.ts +43 -0
  50. package/package.json +1 -1
  51. package/servers/express.ts +1 -0
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "spa",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "run-p type-check \"build-only {@}\" --",
9
+ "preview": "vite preview",
10
+ "build-only": "vite build",
11
+ "type-check": "vue-tsc --build --force",
12
+ "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
13
+ },
14
+ "dependencies": {
15
+ "@iconify-prerendered/vue-flowbite": "^0.23.1714023977",
16
+ "dayjs": "^1.11.11",
17
+ "flowbite": "^2.3.0",
18
+ "flowbite-datepicker": "^1.2.6",
19
+ "pinia": "^2.1.7",
20
+ "vue": "^3.4.21",
21
+ "vue-router": "^4.3.0"
22
+ },
23
+ "devDependencies": {
24
+ "@rushstack/eslint-patch": "^1.8.0",
25
+ "@tsconfig/node20": "^20.1.4",
26
+ "@types/node": "^20.12.5",
27
+ "@vitejs/plugin-vue": "^5.0.4",
28
+ "@vue/eslint-config-typescript": "^13.0.0",
29
+ "@vue/tsconfig": "^0.5.1",
30
+ "autoprefixer": "^10.4.19",
31
+ "eslint": "^8.57.0",
32
+ "eslint-plugin-vue": "^9.23.0",
33
+ "npm-run-all2": "^6.1.2",
34
+ "postcss": "^8.4.38",
35
+ "sass": "^1.77.2",
36
+ "tailwindcss": "^3.4.3",
37
+ "typescript": "~5.4.0",
38
+ "vite": "^5.2.12",
39
+ "vue-tsc": "^2.0.11"
40
+ }
41
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
Binary file
@@ -0,0 +1,172 @@
1
+ <template>
2
+ <nav
3
+ v-if="loggedIn"
4
+
5
+ class="fixed h-14 top-0 z-50 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
6
+ <div class="px-3 py-3 lg:px-5 lg:pl-3">
7
+ <div class="flex items-center justify-between">
8
+ <div class="flex items-center justify-start rtl:justify-end">
9
+ <button data-drawer-target="logo-sidebar" data-drawer-toggle="logo-sidebar" aria-controls="logo-sidebar" type="button" class="inline-flex items-center p-2 text-sm text-gray-500 rounded-lg sm:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600">
10
+ <span class="sr-only">Open sidebar</span>
11
+ <svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
12
+ <path clip-rule="evenodd" fill-rule="evenodd" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"></path>
13
+ </svg>
14
+ </button>
15
+ <div class="flex ms-2 md:me-24">
16
+ <img src="https://flowbite.com/docs/images/logo.svg" class="h-8 me-3" alt="FlowBite Logo" />
17
+ <span class="self-center text-xl font-semibold sm:text-2xl whitespace-nowrap dark:text-white">
18
+ {{ coreStore.config?.brandName }}
19
+ </span>
20
+ </div>
21
+ </div>
22
+ <div class="flex items-center">
23
+
24
+
25
+ <div class="flex items-center ms-3">
26
+ <div>
27
+ <button type="button" class="flex text-sm bg-gray-100 rounded-full focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600 dark:bg-gray-700" aria-expanded="false" data-dropdown-toggle="dropdown-user">
28
+ <span class="sr-only">Open user menu</span>
29
+ <svg class="w-8 h-8 text-gray-400 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
30
+ <path fill-rule="evenodd" d="M12 20a7.966 7.966 0 0 1-5.002-1.756l.002.001v-.683c0-1.794 1.492-3.25 3.333-3.25h3.334c1.84 0 3.333 1.456 3.333 3.25v.683A7.966 7.966 0 0 1 12 20ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10c0 5.5-4.44 9.963-9.932 10h-.138C6.438 21.962 2 17.5 2 12Zm10-5c-1.84 0-3.333 1.455-3.333 3.25S10.159 13.5 12 13.5c1.84 0 3.333-1.455 3.333-3.25S13.841 7 12 7Z" clip-rule="evenodd"/>
31
+ </svg>
32
+
33
+ </button>
34
+ </div>
35
+ <div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:bg-gray-700 dark:divide-gray-600 dark:shadow-xl" id="dropdown-user">
36
+ <div class="px-4 py-3" role="none">
37
+ <p class="text-sm text-gray-900 dark:text-white" role="none" v-if="coreStore.userFullname">
38
+ {{ coreStore.userFullname }}
39
+ </p>
40
+ <p class="text-sm font-medium text-gray-900 truncate dark:text-gray-300" role="none">
41
+ {{ coreStore.username }}
42
+ </p>
43
+ </div>
44
+ <ul class="py-1" role="none">
45
+ <li>
46
+ <a href="#" class="block px-4 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">Dashboard</a>
47
+ </li>
48
+ <li >
49
+ <span @click="toggleTheme" class=" cursor-pointer flex items-center gap-1 block px-4 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">
50
+ {{ theme === 'dark' ? 'Light' : 'Dark' }}
51
+ <IconMoonSolid class="w-5 h-5 text-blue-300
52
+ " v-if="theme !== 'dark'"/>
53
+ <IconSunSolid class="w-5 h-5 text-yellow-300
54
+ " v-else/>
55
+ mode
56
+ </span>
57
+ </li>
58
+
59
+ <li>
60
+ <button @click="logout" class="block px-4 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">Sign out</button>
61
+ </li>
62
+ </ul>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </nav>
69
+
70
+ <aside
71
+ v-if="loggedIn"
72
+
73
+ id="logo-sidebar" class="fixed top-0 left-0 z-10 w-64 h-screen pt-20 transition-transform -translate-x-full bg-white border-r border-gray-200 sm:translate-x-0 dark:bg-gray-800 dark:border-gray-700" aria-label="Sidebar">
74
+ <div class="h-full px-3 pb-4 overflow-y-auto bg-white dark:bg-gray-800">
75
+ <ul class="space-y-2 font-medium">
76
+ <template v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
77
+ <div v-if="item.type === 'divider'" class="border-t border-gray-200 dark:border-gray-700"></div>
78
+ <div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
79
+
80
+ <li v-else-if="item.children">
81
+ <button type="button" class="flex items-center w-full p-2 text-base text-gray-900 transition duration-75 rounded-lg group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700"
82
+ :aria-controls="`dropdown-example${i}`"
83
+ :data-collapse-toggle="`dropdown-example${i}`"
84
+ >
85
+ <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>
86
+
87
+ <span class="flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">{{ item.label }}</span>
88
+ <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
89
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
90
+ </svg>
91
+ </button>
92
+
93
+ <ul :id="`dropdown-example${i}`" role="none" class="py-2 space-y-2" :class="{ 'hidden': !item.open }">
94
+ <template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
95
+ <li>
96
+ <MenuLink :item="child" isChild="true" />
97
+ </li>
98
+ </template>
99
+ </ul>
100
+ </li>
101
+ <li v-else>
102
+ <MenuLink :item="item" />
103
+ </li>
104
+ </template>
105
+ </ul>
106
+ </div>
107
+ </aside>
108
+
109
+ <div class="p-4 sm:ml-64 dark:bg-gray-800" v-if="loggedIn">
110
+ <div class="p-0 dark:border-gray-700 mt-14">
111
+ <RouterView/>
112
+ </div>
113
+ </div>
114
+
115
+ <div v-else>
116
+ <RouterView/>
117
+ </div>
118
+ <AcceptModal />
119
+ </template>
120
+
121
+ <style scoped>
122
+ </style>
123
+
124
+ <script setup lang="ts">
125
+ import { computed, onMounted, ref, watch, defineComponent } from 'vue';
126
+ import { RouterLink, RouterView } from 'vue-router';
127
+ import { initFlowbite } from 'flowbite'
128
+ import './index.scss'
129
+ import { useCoreStore } from '@/stores/core';
130
+ import { IconMoonSolid, IconSunSolid } from '@iconify-prerendered/vue-flowbite';
131
+ import AcceptModal from './components/AcceptModal.vue';
132
+ import MenuLink from './components/MenuLink.vue';
133
+ import { useRoute, useRouter } from 'vue-router';
134
+ import { getIcon } from '@/utils';
135
+
136
+
137
+ const route = useRoute();
138
+ const router = useRouter();
139
+
140
+ const routerIsReady = ref(false);
141
+
142
+ const loggedIn = computed(() => route.name !== 'login' && routerIsReady.value);
143
+
144
+ const theme = ref('light');
145
+
146
+ function toggleTheme() {
147
+ theme.value = theme.value === 'light' ? 'dark' : 'light';
148
+ document.documentElement.classList.toggle('dark');
149
+ }
150
+
151
+ async function logout() {
152
+ await coreStore.logout();
153
+ router.push({ name: 'login' });
154
+ }
155
+
156
+ const coreStore = useCoreStore();
157
+
158
+
159
+
160
+ async function initRouter() {
161
+ await router.isReady();
162
+ routerIsReady.value = true;
163
+ }
164
+
165
+ // initialize components based on data attribute selectors
166
+ onMounted(async () => {
167
+ initRouter();
168
+ initFlowbite();
169
+ await coreStore.fetchMenuAndResource();
170
+ })
171
+
172
+ </script>
File without changes
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
@@ -0,0 +1,52 @@
1
+ <script setup>
2
+ import { ref,onMounted } from 'vue'
3
+ import { useModalStore } from '@/stores/modal';
4
+
5
+ const modalStore = useModalStore();
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+ </script>
14
+
15
+ <template>
16
+ <Teleport to="body">
17
+ <div v-if="modalStore.isOpened" class="bg-gray-900/50 dark:bg-gray-900/80 overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
18
+ <div class="relative p-4 w-full max-w-md max-h-full top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 " >
19
+ <div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
20
+ <button type="button"@click="modalStore.togleModal" class="absolute top-3 end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" >
21
+ <svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
22
+ <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"/>
23
+ </svg>
24
+ <span class="sr-only">Close modal</span>
25
+ </button>
26
+ <div class="p-4 md:p-5 text-center">
27
+ <svg class="mx-auto mb-4 text-gray-400 w-12 h-12 dark:text-gray-200" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
28
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
29
+ </svg>
30
+ <h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">{{ modalStore?.modalContent?.content }}</h3>
31
+ <button @click="()=>{modalStore.onAcceptFunction();modalStore.togleModal()}" type="button" class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center">
32
+ {{ modalStore?.modalContent?.acceptText }}
33
+ </button>
34
+ <button @click="modalStore.togleModal" type="button" class="py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 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">{{ modalStore?.modalContent?.cancelText }}</button>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+
40
+ </Teleport>
41
+ </template>
42
+
43
+ <style scoped>
44
+ .modal {
45
+ position: fixed;
46
+ z-index: 999;
47
+ top: 20%;
48
+ left: 50%;
49
+ width: 300px;
50
+ margin-left: -150px;
51
+ }
52
+ </style>
@@ -0,0 +1,40 @@
1
+ <template>
2
+ <nav class="flex" aria-label="Breadcrumb">
3
+ <ol class="inline-flex items-center space-x-1 md:space-x-2 rtl:space-x-reverse">
4
+ <li class="inline-flex items-center">
5
+ <RouterLink :to="{name: 'home'}" class="inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600 dark:text-gray-400 dark:hover:text-white">
6
+ <svg class="w-3 h-3 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
7
+ <path d="m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z"/>
8
+ </svg>
9
+ Home
10
+ </RouterLink>
11
+ </li>
12
+ <li>
13
+ <div class="flex items-center">
14
+ <svg class="rtl:rotate-180 w-3 h-3 text-gray-400 mx-1" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
15
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
16
+ </svg>
17
+ <RouterLink :to="{name: 'resource-list', params: { resourceId: $route.params.resourceId }}" class="text-sm font-medium text-gray-500 md:ms-2 dark:text-gray-400">{{ coreStore.resourceById[$route.params.resourceId]?.label }}</RouterLink>
18
+ </div>
19
+ </li>
20
+
21
+ <li v-if="$route.params.primaryKey">
22
+ <div class="flex items-center">
23
+ <svg class="rtl:rotate-180 w-3 h-3 text-gray-400 mx-1" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
24
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
25
+ </svg>
26
+ <span class="ms-1 text-sm font-medium text-gray-500 md:ms-2 dark:text-gray-400">
27
+ {{ $route.name === 'resource-edit' ? 'Edit' : 'Show' }} {{ coreStore.record?._label }}</span>
28
+ </div>
29
+ </li>
30
+
31
+
32
+ </ol>
33
+ </nav>
34
+ </template>
35
+
36
+ <script setup>
37
+ import { useCoreStore } from '@/stores/core';
38
+
39
+ const coreStore = useCoreStore()
40
+ </script>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <div>
3
+ <div class="flex items-center justify-between mb-3 flex-wrap gap-y-2">
4
+ <Breadcrumbs />
5
+ <div class="flex items-center space-x-1">
6
+ <slot></slot>
7
+ </div>
8
+ </div>
9
+ <div class="flex items-center justify-between mb-3 flex-wrap gap-y-2">
10
+ <div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert"
11
+ v-if="coreStore.resourceColumnsError">
12
+ <span class="font-medium">Error!</span> {{ coreStore.resourceColumnsError }}
13
+ </div>
14
+ </div>
15
+ </div>
16
+
17
+ </template>
18
+
19
+ <script setup>
20
+ import Breadcrumbs from '@/components/Breadcrumbs.vue';
21
+
22
+ import { useCoreStore } from '@/stores/core';
23
+
24
+ const coreStore = useCoreStore();
25
+
26
+ </script>
@@ -0,0 +1,161 @@
1
+ <template>
2
+ <div>
3
+ <div class="mx-auto grid grid-cols-2 gap-4 mb-2">
4
+ <div>
5
+ <label for="start-time" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{ label }}</label>
6
+
7
+ <div class="relative">
8
+ <div class="absolute inset-y-0 end-0 top-0 flex items-center pe-3.5">
9
+ <IconCalendar class="w-4 h-4 text-gray-500 dark:text-gray-400"/>
10
+ </div>
11
+
12
+ <input ref="datepickerStartEl" type="text"
13
+ class="bg-gray-50 border leading-none 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"
14
+ placeholder="Start date">
15
+ </div>
16
+ </div>
17
+ </div>
18
+
19
+ <div>
20
+ <div class="mx-auto grid grid-cols-2 gap-4 mb-2" :class="{hidden: !showTimeInputs}">
21
+ <div>
22
+ <div class="relative">
23
+ <div class="absolute inset-y-0 end-0 top-0 flex items-center pe-3.5 pointer-events-none">
24
+ <IconTime class="w-4 h-4 text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-700"/>
25
+ </div>
26
+
27
+ <input v-model="startTime" type="time" id="start-time" onfocus="this.showPicker()" onclick="this.showPicker()" step="1"
28
+ class="bg-gray-50 border leading-none 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"
29
+ value="00:00" required/>
30
+ </div>
31
+ </div>
32
+ </div>
33
+
34
+ <button type="button"
35
+ class="text-blue-700 dark:text-blue-500 text-base font-medium hover:underline p-0 inline-flex items-center mb-2"
36
+ @click="toggleTimeInputs">{{ showTimeInputs ? 'Hide time' : 'Show time' }}
37
+ <svg class="w-8 h-8 ms-0.5" :class="{'rotate-180': showTimeInputs}" aria-hidden="true"
38
+ xmlns="http://www.w3.org/2000/svg" width="24" height="24"
39
+ fill="none" viewBox="0 0 24 24">
40
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
41
+ d="m8 10 4 4 4-4"/>
42
+ </svg>
43
+ </button>
44
+ </div>
45
+ </div>
46
+ </template>
47
+ <script setup>
48
+ import {ref, computed, onMounted, watch, onBeforeUnmount} from 'vue';
49
+ import dayjs from 'dayjs';
50
+ import utc from 'dayjs/plugin/utc';
51
+
52
+ import {useCoreStore} from '@/stores/core';
53
+
54
+ import Datepicker from "flowbite-datepicker/Datepicker";
55
+ import IconCalendar from "@/components/icons/IconCalendar.vue";
56
+ import IconTime from "@/components/icons/IconTime.vue";
57
+
58
+ const coreStore = useCoreStore();
59
+ dayjs.extend(utc)
60
+
61
+ const props = defineProps({
62
+ valueStart: {
63
+ default: undefined
64
+ },
65
+ column: {
66
+ type: Object,
67
+ },
68
+ label: {
69
+ type: String,
70
+ }
71
+ });
72
+
73
+ const emit = defineEmits(['update:valueStart']);
74
+
75
+ const datepickerStartEl = ref();
76
+
77
+ const showTimeInputs = ref(false);
78
+
79
+ const startDate = ref('');
80
+
81
+ const startTime = ref('');
82
+
83
+ const datepickerObject = ref('')
84
+
85
+ const start = computed(() => {
86
+ if (!startDate.value) {
87
+ return;
88
+ }
89
+
90
+ let date = dayjs(startDate.value);
91
+
92
+ if (startTime.value) {
93
+ date = addTimeToDate(formatTime(startTime.value), date)
94
+ }
95
+
96
+ return date.utc().toISOString();
97
+ })
98
+
99
+ function updateFromProps() {
100
+ if (props.valueStart === undefined) {
101
+ datepickerStartEl.value.value = '';
102
+ startTime.value = '';
103
+ }
104
+ else {
105
+ datepickerObject.value.setDate(dayjs(props.valueStart).format('DD MMM YYYY'));
106
+ startTime.value = dayjs(props.valueStart).format('HH:mm:ss')
107
+ }
108
+ }
109
+
110
+ onMounted(() => {
111
+ updateFromProps();
112
+
113
+ watch(() => [props.valueStart], (value) => {
114
+ updateFromProps();
115
+ });
116
+ })
117
+
118
+ watch(start, () => {
119
+ //console.log('⚡ emit', start.value)
120
+ emit('update:valueStart', start.value)
121
+ })
122
+
123
+ function initDatepickers() {
124
+ datepickerObject.value = new Datepicker(datepickerStartEl.value, { format: 'dd M yyyy' });
125
+
126
+ addChangeDateListener();
127
+ }
128
+
129
+ function addChangeDateListener() {
130
+ datepickerStartEl.value.addEventListener('changeDate', setStartDate)
131
+ }
132
+
133
+ function removeChangeDateListener() {
134
+ datepickerStartEl.value.removeEventListener('changeDate', setStartDate);
135
+ }
136
+
137
+ function setStartDate(event) {
138
+ startDate.value = event.detail.date
139
+ }
140
+
141
+ function formatTime(time) {
142
+ return time.split(':').map(Number).length === 2 ? time + ':00' : time;
143
+ }
144
+
145
+ function addTimeToDate(time, date) {
146
+ const [hours, minutes, seconds] = time.split(':').map(Number)
147
+ return date.hour(hours).minute(minutes).second(seconds)
148
+ }
149
+
150
+ const toggleTimeInputs = () => {
151
+ showTimeInputs.value = !showTimeInputs.value
152
+ }
153
+
154
+ onMounted(() => {
155
+ initDatepickers();
156
+ });
157
+
158
+ onBeforeUnmount(() => {
159
+ removeChangeDateListener();
160
+ })
161
+ </script>