@opengis/cms 0.0.21 → 0.0.22

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 (183) hide show
  1. package/package.json +2 -2
  2. package/src/App.css +52 -0
  3. package/src/App.vue +62 -0
  4. package/src/assets/image.png +0 -0
  5. package/src/assets/main.css +3 -0
  6. package/src/assets/tailwind-3.4.17.js +113 -0
  7. package/src/components/LanguageSwitcher.vue +73 -0
  8. package/src/components/SettingsCard.vue +40 -0
  9. package/src/components/builder/CreateForm.vue +128 -0
  10. package/src/components/builder/formTypeSchema.js +145 -0
  11. package/src/components/builder/tabs/index.ts +9 -0
  12. package/src/components/builder/tabs/vs-builder-edit.vue +133 -0
  13. package/src/components/builder/tabs/vs-builder-monaco.vue +29 -0
  14. package/src/components/builder/tabs/vs-builder-preview.vue +39 -0
  15. package/src/components/builder/vs-builder-datatable-controls.vue +138 -0
  16. package/src/components/builder/vs-builder-datatable-form.vue +80 -0
  17. package/src/components/builder/vs-builder-datatable.vue +191 -0
  18. package/src/components/builder/vs-builder-list-item.vue +110 -0
  19. package/src/components/collections/CollectionsBreadcrumb.vue +52 -0
  20. package/src/components/collections/CollectionsGrid.vue +176 -0
  21. package/src/components/collections/ContentBlock.vue +75 -0
  22. package/src/components/collections/formWrapper.vue +156 -0
  23. package/src/components/dashboard/ContentItem.vue +82 -0
  24. package/src/components/dashboard/DashboardHeader.vue +33 -0
  25. package/src/components/dashboard/QuickActions.vue +84 -0
  26. package/src/components/dashboard/RecentContent.vue +54 -0
  27. package/src/components/dashboard/StatCard.vue +63 -0
  28. package/src/components/dashboard/StatsGrid.vue +28 -0
  29. package/src/components/form-components/MonacoEditor.vue +104 -0
  30. package/src/components/form-components/VsFormMeta.vue +40 -0
  31. package/src/components/form-components/VsFormTags.vue +150 -0
  32. package/src/components/form-components/custom-datatable/vs-form-custom-datatable-add.vue +84 -0
  33. package/src/components/form-components/custom-datatable/vs-form-custom-datatable-controls.vue +106 -0
  34. package/src/components/form-components/index.js +23 -0
  35. package/src/components/form-components/reference/vs-form-reference-add.vue +92 -0
  36. package/src/components/form-components/reference/vs-form-reference-controls.vue +101 -0
  37. package/src/components/form-components/reference-list/referenceOptionList.js +78 -0
  38. package/src/components/form-components/reference-list/vs-form-reference-add.vue +145 -0
  39. package/src/components/form-components/reference-list/vs-form-reference-choce.vue +39 -0
  40. package/src/components/form-components/reference-list/vs-form-reference-controls.vue +110 -0
  41. package/src/components/form-components/reference-skeleton/about-skeleton.vue +37 -0
  42. package/src/components/form-components/reference-skeleton/banner-skeleton.vue +29 -0
  43. package/src/components/form-components/reference-skeleton/body-skeleton.vue +56 -0
  44. package/src/components/form-components/reference-skeleton/cards-skeleton.vue +47 -0
  45. package/src/components/form-components/reference-skeleton/documents-skeleton.vue +64 -0
  46. package/src/components/form-components/reference-skeleton/faq-skeleton.vue +64 -0
  47. package/src/components/form-components/reference-skeleton/form-skeleton.vue +41 -0
  48. package/src/components/form-components/reference-skeleton/index.js +36 -0
  49. package/src/components/form-components/reference-skeleton/infoLine-skeleton.vue +37 -0
  50. package/src/components/form-components/reference-skeleton/news-skeleton.vue +54 -0
  51. package/src/components/form-components/reference-skeleton/slider-skeleton.vue +41 -0
  52. package/src/components/form-components/reference-skeleton/tabs-skeleton.vue +40 -0
  53. package/src/components/form-components/reference-skeleton/team-skeleton.vue +103 -0
  54. package/src/components/form-components/reference-skeleton/usefulLinks-skeleton.vue +52 -0
  55. package/src/components/form-components/reference-skeleton/video-skeleton.vue +36 -0
  56. package/src/components/form-components/testReferenceTypes.js +773 -0
  57. package/src/components/form-components/vs-form-color-picker.vue +29 -0
  58. package/src/components/form-components/vs-form-custom-datatable.vue +214 -0
  59. package/src/components/form-components/vs-form-integer.vue +86 -0
  60. package/src/components/form-components/vs-form-key-value.vue +201 -0
  61. package/src/components/form-components/vs-form-marcdown-md.vue +3 -0
  62. package/src/components/form-components/vs-form-media-select.vue +780 -0
  63. package/src/components/form-components/vs-form-reference-list.vue +97 -0
  64. package/src/components/form-components/vs-form-reference.vue +59 -0
  65. package/src/components/form-components/vs-form-relation.vue +30 -0
  66. package/src/components/form-components/vs-form-reletion-link.vue +34 -0
  67. package/src/components/form-components/vs-form-select-collection.vue +0 -0
  68. package/src/components/form-components/vs-form-slug.vue +72 -0
  69. package/src/components/form-components/vs-form-tiptap.vue +7 -0
  70. package/src/components/form-components/vs-richtext-md.vue +3 -0
  71. package/src/components/icons/BellIcon.vue +17 -0
  72. package/src/components/icons/GlobeIcon.vue +18 -0
  73. package/src/components/icons/KeyIcon.vue +20 -0
  74. package/src/components/icons/PaletteIcon.vue +22 -0
  75. package/src/components/icons/SettingsIcon.vue +19 -0
  76. package/src/components/icons/ShieldIcon.vue +18 -0
  77. package/src/components/icons/UsersIcon.vue +19 -0
  78. package/src/components/icons/icon-chevron-right.vue +16 -0
  79. package/src/components/icons/icon-drag.vue +20 -0
  80. package/src/components/icons/icon-file-text.vue +21 -0
  81. package/src/components/icons/icon-folder.vue +18 -0
  82. package/src/components/icons/icon-grid.vue +17 -0
  83. package/src/components/icons/icon-group.vue +19 -0
  84. package/src/components/icons/icon-home.vue +16 -0
  85. package/src/components/icons/icon-image.vue +18 -0
  86. package/src/components/icons/icon-list.vue +20 -0
  87. package/src/components/icons/icon-more.vue +17 -0
  88. package/src/components/icons/icon-plus.vue +17 -0
  89. package/src/components/icons-types/icon-array.vue +22 -0
  90. package/src/components/icons-types/icon-boolean.vue +18 -0
  91. package/src/components/icons-types/icon-datalist.vue +22 -0
  92. package/src/components/icons-types/icon-date.vue +20 -0
  93. package/src/components/icons-types/icon-datetime.vue +20 -0
  94. package/src/components/icons-types/icon-file.vue +21 -0
  95. package/src/components/icons-types/icon-gallery.vue +18 -0
  96. package/src/components/icons-types/icon-image.vue +19 -0
  97. package/src/components/icons-types/icon-integer.vue +20 -0
  98. package/src/components/icons-types/icon-merkdown.vue +18 -0
  99. package/src/components/icons-types/icon-multiselect.vue +22 -0
  100. package/src/components/icons-types/icon-number.vue +20 -0
  101. package/src/components/icons-types/icon-radio.vue +22 -0
  102. package/src/components/icons-types/icon-reference-list.vue +22 -0
  103. package/src/components/icons-types/icon-reference.vue +20 -0
  104. package/src/components/icons-types/icon-relation.vue +22 -0
  105. package/src/components/icons-types/icon-richtext.vue +18 -0
  106. package/src/components/icons-types/icon-select.vue +22 -0
  107. package/src/components/icons-types/icon-slug.vue +19 -0
  108. package/src/components/icons-types/icon-text.vue +19 -0
  109. package/src/components/icons-types/index.js +43 -0
  110. package/src/components/layout/Layout.vue +67 -0
  111. package/src/components/layout/Sidebar.vue +128 -0
  112. package/src/components/media/FileUploadProgress.vue +29 -0
  113. package/src/components/media/MediaBreadcrumb.vue +42 -0
  114. package/src/components/media/MediaCreateFolder.vue +59 -0
  115. package/src/components/media/MediaFileInfo.vue +148 -0
  116. package/src/components/media/MediaGrid.vue +148 -0
  117. package/src/components/media/MediaList.vue +148 -0
  118. package/src/components/media/MediaViewControls.vue +38 -0
  119. package/src/components/media/TypeTag.vue +23 -0
  120. package/src/components/menu/AddNewItemInTree.vue +75 -0
  121. package/src/components/menu/MenuBody.vue +149 -0
  122. package/src/components/menu/MenuItem.vue +73 -0
  123. package/src/components/menu/MenuList.vue +101 -0
  124. package/src/components/referencec/index.ts +7 -0
  125. package/src/components/referencec/vs-reference-faq.vue +61 -0
  126. package/src/components/referencec/vs-reference-user-card.vue +40 -0
  127. package/src/components/settings/NotificationSettings.vue +32 -0
  128. package/src/components/settings/SettingsTable.vue +50 -0
  129. package/src/components/settings/SettingsTitle.vue +33 -0
  130. package/src/components/settings/SettingsToggleItem.vue +25 -0
  131. package/src/components/sidebar/DropdownMenu.vue +34 -0
  132. package/src/components/sidebar/SettingsSidebar.vue +121 -0
  133. package/src/components/sidebar/SidebarFooter.vue +52 -0
  134. package/src/components/sidebar/SidebarHeader.vue +57 -0
  135. package/src/components/sidebar/SidebarMenu.vue +78 -0
  136. package/src/components/ui/EmptyData.vue +76 -0
  137. package/src/components/ui/UniversalTable.vue +310 -0
  138. package/src/components/ui/UniversalTableFilters.vue +0 -0
  139. package/src/components/ui/UniversalTablePagination.vue +118 -0
  140. package/src/components/ui/VsPreview.vue +75 -0
  141. package/src/composables/useCollectionView.ts +21 -0
  142. package/src/composables/useDebounce.ts +26 -0
  143. package/src/composables/useMonaco.ts +28 -0
  144. package/src/composables/useTheme.ts +40 -0
  145. package/src/content/test-slug/metadata.json +1 -0
  146. package/src/i18n.ts +75 -0
  147. package/src/index.css +3 -0
  148. package/src/locales/en.json +778 -0
  149. package/src/locales/uk.json +797 -0
  150. package/src/main.ts +41 -0
  151. package/src/pages/Dashboard.vue +168 -0
  152. package/src/pages/EmailPage.vue +183 -0
  153. package/src/pages/FeedbackPage.vue +232 -0
  154. package/src/pages/MediaPage.vue +372 -0
  155. package/src/pages/TagsPage.vue +207 -0
  156. package/src/pages/builder/BuilderPage.vue +195 -0
  157. package/src/pages/builder/EditCollectionPage.vue +163 -0
  158. package/src/pages/collections/ArticlesPage.vue +385 -0
  159. package/src/pages/collections/CollectionsPage.vue +146 -0
  160. package/src/pages/collections/SingletonsPage.vue +119 -0
  161. package/src/pages/collections/contentForm.vue +484 -0
  162. package/src/pages/collections/schema/seo.ts +27 -0
  163. package/src/pages/menu/MenuAddPage.vue +123 -0
  164. package/src/pages/menu/MenuItemPage.vue +183 -0
  165. package/src/pages/menu/MenuPage.vue +133 -0
  166. package/src/pages/settings/ApiKeys.vue +75 -0
  167. package/src/pages/settings/Appearance.vue +80 -0
  168. package/src/pages/settings/Logs.vue +260 -0
  169. package/src/pages/settings/PermissionsPage.vue +237 -0
  170. package/src/pages/settings/Settings.vue +186 -0
  171. package/src/pages/settings/Users.vue +109 -0
  172. package/src/pages/settings/general.vue +154 -0
  173. package/src/pages/settings/generalScheme.js +132 -0
  174. package/src/pages/users/AddUser.vue +106 -0
  175. package/src/pages/users/UsersPage.vue +98 -0
  176. package/src/props/builder.ts +67 -0
  177. package/src/props/content.ts +56 -0
  178. package/src/props/media.ts +63 -0
  179. package/src/router/index.ts +181 -0
  180. package/src/types/fastify-auth.d.ts +4 -0
  181. package/src/utils/getField.js +270 -0
  182. package/src/utils/translit.js +19 -0
  183. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <div class="ml-2 border-l border-slate-200 dark:border-slate-600">
3
+ <div v-for="item in navigationItems" :key="item.id">
4
+ <router-link
5
+ class="w-full flex items-center space-x-3 px-6 py-2.5 mb-1 rounded-lg text-left transition-all duration-200 group border-0 text-sm"
6
+ :class="[
7
+ currentPath === item.path
8
+ ? 'bg-blue-50 text-blue-700 border border-blue-200 dark:bg-slate-700 dark:text-slate-100'
9
+ : 'bg-white text-slate-600 hover:bg-slate-50 hover:text-slate-800 dark:bg-transparent dark:text-slate-300 dark:hover:bg-slate-700 dark:hover:text-slate-100'
10
+ ]"
11
+ :to="item.path"
12
+ >
13
+ <component :is="item.icon" class="w-5 h-5 shrink-0" />
14
+ <span class="font-medium flex-1" v-if="item.label">{{ $t(item.label) }}</span>
15
+ <span class="font-medium flex-1" v-else>{{ item.title }}</span>
16
+ <div class="rounded-md border font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 hover:bg-secondary/80 text-xs px-2 py-0.5 min-w-[20px] h-5 flex items-center justify-center bg-blue-100 text-blue-700 border-blue-200 dark:bg-slate-600 dark:text-slate-200 dark:border-slate-500">
17
+ {{ item.entries }}
18
+ </div>
19
+ </router-link>
20
+ </div>
21
+ </div>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { defineProps, ref, computed } from 'vue';
26
+ import { useRoute } from 'vue-router';
27
+
28
+ defineProps<{
29
+ navigationItems: any[];
30
+ }>();
31
+
32
+ const route = useRoute();
33
+ const currentPath = computed(() => route.path);
34
+ </script>
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <aside
3
+ class="flex-col hidden w-64 transition-colors duration-200 bg-white border-r border-gray-200 md:flex dark:border-gray-700 dark:bg-gray-800"
4
+ >
5
+ <div class="p-3 border-b border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 flex-shrink-0">
6
+ <div class="flex items-center justify-between">
7
+ <div class="w-full flex items-center justify-between">
8
+ <router-link
9
+ to="/"
10
+ class="inline-flex items-center justify-center whitespace-nowrap transition-colors rounded-md h-8 px-2 bg-white text-slate-600 border-0 text-sm font-medium
11
+ hover:bg-slate-50 hover:text-slate-800 dark:bg-slate-800 dark:hover:bg-slate-700 dark:text-slate-300 dark:hover:text-slate-100"
12
+ >
13
+ <ArrowLeft class="w-4 h-4 mr-2" />
14
+ <span>{{ $t("navigation.back") }}</span>
15
+ </router-link>
16
+ <div class="ml-auto">
17
+ <LanguageSwitcher />
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
22
+
23
+ <router-link to="/settings" class="p-3 border-b border-slate-100 dark:border-slate-700 bg-white dark:bg-slate-800 flex-shrink-0">
24
+ <h2 class="text-lg font-semibold text-slate-800 dark:text-slate-100">
25
+ {{ $t("navigation.settings") }}
26
+ </h2>
27
+ <p class="text-sm text-slate-500 dark:text-slate-400">
28
+ {{ $t("navigation.settingsDescription") }}
29
+ </p>
30
+ </router-link>
31
+
32
+ <nav class="flex-1 p-2 space-y-1 overflow-y-auto">
33
+ <template v-for="item in navigationItems" :key="item.id">
34
+ <router-link
35
+ v-if="!item.children"
36
+ :to="item.path"
37
+ class="w-full flex items-center space-x-3 px-3 py-2.5 mb-1 rounded-lg text-left transition-all duration-200 group border-0"
38
+ :class="[
39
+ currentPath === item.path
40
+ ? 'bg-blue-50 text-blue-700 border border-blue-200 dark:bg-slate-700 dark:text-slate-100'
41
+ : 'bg-white text-slate-600 hover:bg-slate-50 hover:text-slate-800 dark:bg-transparent dark:text-slate-300 dark:hover:bg-slate-700 dark:hover:text-slate-100'
42
+ ]"
43
+ >
44
+ <component :is="item.icon" class="w-5 h-5 mr-3" />
45
+ <span class="font-medium flex-1">{{ $t(item.label) }}</span>
46
+ </router-link>
47
+ </template>
48
+ </nav>
49
+ <SidebarFooter />
50
+ </aside>
51
+ </template>
52
+
53
+ <script setup lang="ts">
54
+ import { computed } from "vue";
55
+ import {
56
+ User as UserIcon,
57
+ LogOut,
58
+ Settings,
59
+ Users,
60
+ ArrowLeft,
61
+ Key,
62
+ Palette,
63
+ TableCellsMerge,
64
+ Shield,
65
+ Layers
66
+ } from "lucide-vue-next";
67
+ import LanguageSwitcher from "../LanguageSwitcher.vue";
68
+ import { useRoute } from "vue-router";
69
+ import SidebarFooter from "./SidebarFooter.vue";
70
+
71
+ const route = useRoute();
72
+
73
+ const currentPath = computed(() => route.path);
74
+
75
+ const navigationItems = [
76
+ {
77
+ id: "settings",
78
+ path: "/settings/general",
79
+ label: "navigation.general",
80
+ icon: Settings,
81
+ },
82
+ {
83
+ id: "collections",
84
+ path: "/settings/collections",
85
+ label: "navigation.builder",
86
+ icon: Layers,
87
+ },
88
+ // {
89
+ // id: "settings",
90
+ // path: "/settings/users",
91
+ // label: "navigation.users",
92
+ // icon: Users,
93
+ // },
94
+ // {
95
+ // id: "settings",
96
+ // path: "/settings/api-keys",
97
+ // label: "navigation.apiKeys",
98
+ // icon: Key,
99
+ // },
100
+ // {
101
+ // id: "appearance",
102
+ // path: "/settings/appearance",
103
+ // label: "navigation.appearance",
104
+ // icon: Palette,
105
+ // },
106
+ {
107
+ id: "logs",
108
+ path: "/settings/logs",
109
+ label: "navigation.logs",
110
+ icon: TableCellsMerge
111
+ },
112
+ // {
113
+ // id: "permissions",
114
+ // path: "/settings/permissions",
115
+ // label: "navigation.permissions",
116
+ // icon: Shield,
117
+ // },
118
+ ];
119
+
120
+
121
+ </script>
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <div class="border-t border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 flex-shrink-0">
3
+ <div class="p-3 bg-white dark:bg-slate-800">
4
+ <div class="w-full flex items-center space-x-3 p-2 rounded-lg bg-white hover:bg-slate-50 transition-colors group text-slate-700 hover:text-slate-900 border-0 dark:bg-slate-800 dark:hover:bg-slate-700 dark:text-slate-200 dark:hover:text-slate-100" type="button" id="radix-:r0:" aria-haspopup="menu" aria-expanded="false" data-state="closed">
5
+ <div class="relative">
6
+ <span class="relative flex shrink-0 overflow-hidden rounded-full w-9 h-9 border-2 border-white dark:border-slate-600 shadow-sm">
7
+ <div
8
+ class="flex items-center justify-center w-8 h-8 text-white rounded-full bg-sky-500"
9
+ >
10
+ <UserIcon class="w-4 h-4" />
11
+ </div>
12
+ </span>
13
+
14
+ <div class="absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2 border-white dark:border-slate-600 bg-green-500">
15
+ </div>
16
+ </div>
17
+ <div class="flex-1 text-left">
18
+ <p class="font-semibold whitespace-nowrap text-slate-800 dark:text-slate-100 text-sm group-hover:text-slate-900 dark:group-hover:text-slate-50">
19
+ Softpro User
20
+ </p>
21
+ <p class="text-xs text-slate-500 dark:text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300">
22
+ Admin
23
+ </p>
24
+ </div>
25
+ <button
26
+ class="inline-flex items-center justify-center whitespace-nowrap font-medium rounded-md text-xs h-8 w-8 p-0 ml-auto bg-transparent text-slate-600 transition-all duration-200 border-0
27
+ hover:bg-slate-100 hover:text-slate-800
28
+ dark:hover:bg-slate-600 dark:text-slate-300 dark:hover:text-white"
29
+ @click="toggleTheme"
30
+ >
31
+ <Moon v-if="theme !== 'dark'" class="w-5 h-5" />
32
+ <Sun v-else class="w-5 h-5" />
33
+ </button>
34
+ <div class="flex items-center space-x-2 ml-auto">
35
+ <a
36
+ href="/logout"
37
+ class="relative p-2 text-gray-500 transition-colors rounded-full hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700"
38
+ >
39
+ <LogOut class="w-5 h-5" />
40
+ </a>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </template>
46
+
47
+ <script setup lang="ts">
48
+ import { Moon, Sun, LogOut, User as UserIcon } from "lucide-vue-next";
49
+ import { useTheme } from "../../composables/useTheme";
50
+
51
+ const { toggleTheme, theme } = useTheme();
52
+ </script>
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <div
3
+ class="flex items-center h-16 px-6 border-b gap-2 border-gray-200 dark:border-gray-700"
4
+ >
5
+ <router-link
6
+ to="/"
7
+ class="flex items-center text-xl font-semibold text-gray-900 dark:text-white"
8
+ >
9
+ <img
10
+ v-if="logoCms"
11
+ :src="logoCms"
12
+ :alt="siteName || 'CMS'"
13
+ class="w-auto h-10 mr-2 text-sky-500"
14
+ />
15
+ <img
16
+ v-else
17
+ src="/public/images/logo.png"
18
+ alt="Keystatic"
19
+ class="w-auto h-10 mr-2 text-sky-500"
20
+ />
21
+ <!-- <span v-if="siteName">{{ siteName }}</span> -->
22
+ </router-link>
23
+ <a :href="`https://cms.opengis.info/${locale}/`" target="_blank" :title="$t('guide.help')">
24
+ <HelpCircle class="w-5 h-5" />
25
+ </a>
26
+ <div class="flex items-center space-x-2 ml-auto">
27
+ <LanguageSwitcher />
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+ <script setup lang="ts">
33
+ import { ref, onMounted } from "vue";
34
+ import LanguageSwitcher from "../LanguageSwitcher.vue";
35
+ import { HelpCircle } from "lucide-vue-next";
36
+ import { useI18n } from "vue-i18n";
37
+
38
+ const { locale } = useI18n();
39
+ const siteName = ref<string>("");
40
+ const logoCms = ref<string>("");
41
+
42
+ const fetchSettings = async () => {
43
+ try {
44
+ const response = await fetch("/api/settings");
45
+ const data = await response.json();
46
+ const siteInfo = data?.settings?.site_info || {};
47
+ siteName.value = siteInfo.name || "";
48
+ logoCms.value = siteInfo["logo-cms"] || "";
49
+ } catch (error) {
50
+ console.error("Error fetching settings:", error);
51
+ }
52
+ };
53
+
54
+ onMounted(() => {
55
+ fetchSettings();
56
+ });
57
+ </script>
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <template v-for="item in navigationItems" :key="item.id">
3
+ <router-link
4
+ v-if="!item.children"
5
+ :to="item.path"
6
+ class="w-full flex items-center space-x-3 px-3 py-2.5 mb-1 rounded-lg text-left transition-all duration-200 group border-0"
7
+ :class="[
8
+ isActive(item)
9
+ ? 'bg-blue-50 text-blue-700 border border-blue-200 dark:bg-slate-700 dark:text-slate-100'
10
+ : 'bg-white text-slate-600 hover:bg-slate-50 hover:text-slate-800 dark:bg-transparent dark:text-slate-300 dark:hover:bg-slate-700 dark:hover:text-slate-100'
11
+ ]"
12
+ >
13
+ <component :is="item.icon" class="w-5 h-5" />
14
+ <span class="font-medium flex-1" v-if="item.label">{{ $t(item.label) }}</span>
15
+ <span class="font-medium flex-1" v-else>{{ item.title }}</span>
16
+ </router-link>
17
+
18
+ <button
19
+ v-else
20
+ @click="toggleDropdown(item.id)"
21
+ class="w-full flex items-center space-x-3 px-3 py-2.5 mb-1 rounded-lg text-left transition-all duration-200 group border-0"
22
+ :class="[
23
+ isActive(item)
24
+ ? 'bg-blue-50 text-blue-700 border border-blue-200 dark:bg-slate-700 dark:text-slate-100'
25
+ : 'bg-white text-slate-600 hover:bg-slate-50 hover:text-slate-800 dark:bg-transparent dark:text-slate-300 dark:hover:bg-slate-700 dark:hover:text-slate-100'
26
+ ]"
27
+ >
28
+ <component :is="item.icon" class="w-5 h-5" />
29
+ <span class="font-medium flex-1" v-if="item.label">{{ $t(item.label) }}</span>
30
+ <span class="font-medium flex-1" v-else>{{ item.title }}</span>
31
+ <component
32
+ :is="openDropdowns.includes(item.id) ? ChevronUp : ChevronDown"
33
+ class="w-4 h-4 text-slate-400 dark:text-slate-500"
34
+ />
35
+ </button>
36
+
37
+ <transition name="slide-fade">
38
+ <DropdownMenu
39
+ v-if="item.children"
40
+ v-show="openDropdowns.includes(item.id)"
41
+ :navigation-items="item.children || []"
42
+ />
43
+ </transition>
44
+ </template>
45
+ </template>
46
+
47
+ <script setup lang="ts">
48
+ import { ChevronDown, ChevronUp } from 'lucide-vue-next';
49
+ import { defineProps, ref, computed } from 'vue';
50
+ import { useRoute } from 'vue-router';
51
+ import DropdownMenu from './DropdownMenu.vue';
52
+
53
+ defineProps<{
54
+ navigationItems: any[];
55
+ }>();
56
+
57
+ const route = useRoute();
58
+ const openDropdowns = ref<string[]>([]);
59
+ const currentPath = computed(() => route.path);
60
+
61
+ const isActive = (item: any) => {
62
+ if (item.path === '/collections') {
63
+ // Підсвічуємо "Керування" для всіх сторінок колекцій
64
+ return route.path === '/collections' || route.path.startsWith('/collections/');
65
+ }
66
+ return currentPath.value === item.path;
67
+ };
68
+
69
+ const toggleDropdown = (id: string) => {
70
+ const index = openDropdowns.value.indexOf(id);
71
+
72
+ if (index > -1) {
73
+ openDropdowns.value.splice(index, 1);
74
+ } else {
75
+ openDropdowns.value.push(id);
76
+ }
77
+ };
78
+ </script>
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <div class="rounded-xl text-card-foreground shadow-lg bg-white dark:bg-slate-800 backdrop-blur-sm border-2 border-dashed border-slate-300 dark:border-slate-600">
3
+ <div class="p-12 text-center">
4
+ <svg
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ width="24"
7
+ height="24"
8
+ viewBox="0 0 24 24"
9
+ fill="none"
10
+ stroke="currentColor"
11
+ stroke-width="2"
12
+ stroke-linecap="round"
13
+ stroke-linejoin="round"
14
+ class="lucide lucide-layers w-12 h-12 mx-auto mb-4 text-slate-400 dark:text-slate-500"
15
+ >
16
+ <path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"></path>
17
+ <path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"></path>
18
+ <path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"></path>
19
+ </svg>
20
+
21
+ <h3 class="text-lg font-medium text-slate-600 dark:text-slate-300 mb-2">
22
+ {{ $t(title) }}
23
+ </h3>
24
+
25
+ <p class="text-slate-500 dark:text-slate-400 mb-6">
26
+ {{ $t(description) }}
27
+ </p>
28
+
29
+ <button
30
+ v-if="showButton"
31
+ @click="$emit('action')"
32
+ class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 shadow h-9 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white"
33
+ >
34
+ <svg
35
+ xmlns="http://www.w3.org/2000/svg"
36
+ width="24"
37
+ height="24"
38
+ viewBox="0 0 24 24"
39
+ fill="none"
40
+ stroke="currentColor"
41
+ stroke-width="2"
42
+ stroke-linecap="round"
43
+ stroke-linejoin="round"
44
+ class="lucide lucide-plus w-4 h-4 mr-2"
45
+ >
46
+ <path d="M5 12h14"></path>
47
+ <path d="M12 5v14"></path>
48
+ </svg>
49
+ {{ $t(buttonText) }}
50
+ </button>
51
+ </div>
52
+ </div>
53
+ </template>
54
+
55
+ <script setup>
56
+ defineProps({
57
+ title: {
58
+ type: String,
59
+ default: () => 'emptyData.title'
60
+ },
61
+ description: {
62
+ type: String,
63
+ default: () => 'emptyData.description'
64
+ },
65
+ buttonText: {
66
+ type: String,
67
+ default: () => 'emptyData.buttonText'
68
+ },
69
+ showButton: {
70
+ type: Boolean,
71
+ default: true
72
+ }
73
+ })
74
+
75
+ defineEmits(['action'])
76
+ </script>