adata-ui 2.1.40-beta.5 → 2.1.40-beta.7

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.
@@ -42,6 +42,7 @@ const { visibleCount, hiddenCount } = useChipOverflow({
42
42
  container: chipsRow,
43
43
  measure: measureRow,
44
44
  count: itemCount,
45
+ contentKey: computed(() => props.selectedItems?.map(item => item.name).join(' ') ?? ''),
45
46
  gap: 6,
46
47
  })
47
48
 
@@ -101,7 +102,7 @@ defineExpose({ onClear })
101
102
  <span
102
103
  v-for="item in visibleItems"
103
104
  :key="item.id"
104
- class="select-row-v2__chip inline-flex min-w-0 max-w-[180px] items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs dark:border-gray-700 dark:bg-white/[0.06]"
105
+ class="select-row-v2__chip inline-flex min-w-0 items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs dark:border-gray-700 dark:bg-white/[0.06]"
105
106
  @click.stop="deletable ? deleteItem(item) : (isOpen = true)"
106
107
  >
107
108
  <span class="truncate">{{ item.name }}</span>
@@ -123,14 +124,14 @@ defineExpose({ onClear })
123
124
  <span
124
125
  ref="measureRow"
125
126
  aria-hidden="true"
126
- class="pointer-events-none invisible absolute left-0 top-0 flex h-0 items-center gap-1.5"
127
+ class="pointer-events-none invisible absolute left-0 top-0 flex h-0 w-max items-center gap-1.5"
127
128
  >
128
129
  <span
129
130
  v-for="item in selectedItems"
130
131
  :key="`measure-${item.id}`"
131
- class="inline-flex max-w-[180px] items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs"
132
+ class="inline-flex items-center gap-1 whitespace-nowrap rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs"
132
133
  >
133
- <span class="truncate">{{ item.name }}</span>
134
+ <span>{{ item.name }}</span>
134
135
  <span
135
136
  v-if="deletable"
136
137
  class="flex shrink-0 items-center justify-center rounded-full p-0.5"
@@ -182,9 +182,10 @@ const valueToShow = computed<T | T[] | null>(() => {
182
182
  if (!Array.isArray(modelValue.value) || modelValue.value.length === 0) return null
183
183
  if (props.getOnlyKey) {
184
184
  const keys = modelValue.value as any[]
185
- const found = props.options.filter(opt =>
186
- keys.some(k => isEqual(k, opt[props.keyToSelect as keyof T])),
187
- )
185
+ // map over model keys (not options) so chips keep the selection order
186
+ const found = keys
187
+ .map(k => props.options.find(opt => isEqual(k, opt[props.keyToSelect as keyof T])))
188
+ .filter((opt): opt is T => opt != null)
188
189
  return found.length ? found : null
189
190
  }
190
191
  return modelValue.value as T[]
@@ -249,8 +250,11 @@ function onClear() {
249
250
  emits('onClear')
250
251
  }
251
252
 
252
- function onClearFromMulti(index: number) {
253
+ function onClearFromMulti(item: T) {
253
254
  if (!Array.isArray(modelValue.value)) return
255
+ const key = selectedKey(item)
256
+ const index = modelValue.value.findIndex(el => isEqual(el, key))
257
+ if (index === -1) return
254
258
  const next = [...modelValue.value]
255
259
  next.splice(index, 1)
256
260
  modelValue.value = next
@@ -267,6 +271,10 @@ const { visibleCount: visibleChipCount, hiddenCount: hiddenChipCount } = useChip
267
271
  container: valueColumn,
268
272
  measure: measureRow,
269
273
  count: selectedCount,
274
+ contentKey: computed(() => {
275
+ if (!Array.isArray(valueToShow.value)) return ''
276
+ return (valueToShow.value as any[]).map(item => item?.[props.keyToShow]).join(' ')
277
+ }),
270
278
  gap: 6,
271
279
  })
272
280
 
@@ -332,8 +340,8 @@ const isDefaultSelection = computed(() => {
332
340
  <span
333
341
  v-for="(item, index) in (valueToShow as any[]).slice(0, visibleChipCount)"
334
342
  :key="index"
335
- class="select-v2__chip inline-flex min-w-0 max-w-[180px] items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs dark:border-gray-700 dark:bg-white/[0.06]"
336
- @click.stop="canChipRemove ? onClearFromMulti(index) : (isOpen = true)"
343
+ class="select-v2__chip inline-flex min-w-0 items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs dark:border-gray-700 dark:bg-white/[0.06]"
344
+ @click.stop="canChipRemove ? onClearFromMulti(item) : (isOpen = true)"
337
345
  >
338
346
  <span class="truncate">{{ item[keyToShow] }}</span>
339
347
  <button
@@ -354,14 +362,14 @@ const isDefaultSelection = computed(() => {
354
362
  <span
355
363
  ref="measureRow"
356
364
  aria-hidden="true"
357
- class="pointer-events-none invisible absolute left-0 top-0 flex h-0 items-center gap-1.5"
365
+ class="pointer-events-none invisible absolute left-0 top-0 flex h-0 w-max items-center gap-1.5"
358
366
  >
359
367
  <span
360
368
  v-for="(item, index) in (valueToShow as any[])"
361
369
  :key="`measure-${index}`"
362
- class="inline-flex max-w-[180px] items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs"
370
+ class="inline-flex items-center gap-1 whitespace-nowrap rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs"
363
371
  >
364
- <span class="truncate">{{ item[keyToShow] }}</span>
372
+ <span>{{ item[keyToShow] }}</span>
365
373
  <span
366
374
  v-if="canChipRemove"
367
375
  class="flex shrink-0 items-center justify-center rounded-full p-0.5"
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
3
2
  import IdOtpInput from '#adata-ui/components/modals/id/IdOtpInput.vue'
3
+ import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
4
+ import { writeAccessTokenCookie } from '#adata-ui/composables/useAccessTokenCookie'
4
5
 
5
6
  const { $toast } = useNuxtApp()
6
7
  const { t, locale } = useI18n()
@@ -40,17 +41,11 @@ async function loginUser() {
40
41
  },
41
42
  })
42
43
  const { data: cookiesData } = await response.json()
43
- if (cookiesData) {
44
+ if (cookiesData?.access_token) {
44
45
  const { access_token, expire_in } = cookiesData
45
- const hostname = location.hostname.split('.').reverse()
46
46
  useCookie('autoLogout').value = true
47
47
 
48
- useCookie('accessToken', {
49
- maxAge: expire_in,
50
- domain: `.${hostname[1]}.${hostname[0]}`,
51
- path: '/',
52
- secure: true,
53
- }).value = access_token
48
+ await writeAccessTokenCookie(access_token, expire_in)
54
49
  }
55
50
  }
56
51
 
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
3
+ import { writeAccessTokenCookie } from '#adata-ui/composables/useAccessTokenCookie'
3
4
  import { navigateToLocalizedPage } from '#adata-ui/utils/localizedNavigation'
4
5
  import * as z from 'zod'
5
6
  import { useUrls } from '#adata-ui/composables/useUrls'
@@ -127,17 +128,11 @@ async function submit() {
127
128
  },
128
129
  })
129
130
  const { data: cookiesData } = await response.json()
130
- if (cookiesData) {
131
+ if (cookiesData?.access_token) {
131
132
  const { access_token, expire_in } = cookiesData
132
- const hostname = location.hostname.split('.').reverse()
133
133
  useCookie('autoLogout').value = true
134
134
 
135
- useCookie('accessToken', {
136
- maxAge: expire_in,
137
- domain: COOKIE_DOMAIN,
138
- path: '/',
139
- secure: true,
140
- }).value = access_token
135
+ await writeAccessTokenCookie(access_token, expire_in)
141
136
  }
142
137
  $toast.success(t('reuse.successfully'))
143
138
 
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
2
+ import IdAutoLogoutModal from '#adata-ui/components/modals/id/IdAutoLogoutModal.vue'
3
3
  import IdConfirmAccountOtpModal from '#adata-ui/components/modals/id/IdConfirmAccountOtpModal.vue'
4
4
  import IdConfirmSuccessfulModal from '#adata-ui/components/modals/id/IdConfirmSuccessfulModal.vue'
5
5
  import IdLoginModal from '#adata-ui/components/modals/id/IdLoginModal.vue'
@@ -9,7 +9,8 @@ import IdRecoveryModal from '#adata-ui/components/modals/id/IdRecoveryModal.vue'
9
9
  import IdRegistrationModal from '#adata-ui/components/modals/id/IdRegistrationModal.vue'
10
10
  import IdResetPasswordOtpModal from '#adata-ui/components/modals/id/IdResetPasswordOtpModal.vue'
11
11
  import IdTwoFactorModal from '#adata-ui/components/modals/id/IdTwoFactorModal.vue'
12
- import IdAutoLogoutModal from '#adata-ui/components/modals/id/IdAutoLogoutModal.vue'
12
+ import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
13
+ import { writeAccessTokenCookie } from '#adata-ui/composables/useAccessTokenCookie'
13
14
 
14
15
  const emit = defineEmits<{
15
16
  (e: 'closed', value: boolean): void
@@ -35,6 +36,10 @@ const authApiURL = commonAuth.authApiURL
35
36
  const accessToken = useCookie('accessToken')
36
37
  const isAuthenticated = computed(() => !!accessToken.value)
37
38
 
39
+ // one-shot флаг: обмен по auth-сессии перезагружает страницу максимум один раз,
40
+ // иначе нечитаемая кука accessToken зацикливает reload
41
+ const AUTH_RELOAD_FLAG = 'auth-exchange-reloaded'
42
+
38
43
  onMounted(async () => {
39
44
  const response = await fetch(`${removeTrailingSlash(authApiURL)}/access/cookie`, {
40
45
  method: 'GET',
@@ -46,17 +51,25 @@ onMounted(async () => {
46
51
  const { data: cookiesData } = await response.json()
47
52
  if (cookiesData?.access_token && !accessToken.value) {
48
53
  const { access_token, expire_in } = cookiesData
49
- const hostname = location.hostname.split('.').reverse()
50
54
  useCookie('autoLogout').value = true
51
55
 
52
- useCookie('accessToken', {
53
- maxAge: expire_in,
54
- domain: `.${hostname[1]}.${hostname[0]}`,
55
- path: '/',
56
- secure: true,
57
- }).value = access_token
56
+ const persisted = await writeAccessTokenCookie(access_token, expire_in)
58
57
 
59
- window.location.reload()
58
+ if (persisted && !sessionStorage.getItem(AUTH_RELOAD_FLAG)) {
59
+ sessionStorage.setItem(AUTH_RELOAD_FLAG, '1')
60
+ window.location.reload()
61
+ }
62
+ else {
63
+ sessionStorage.removeItem(AUTH_RELOAD_FLAG)
64
+ console.warn(
65
+ persisted
66
+ ? '[auth] accessToken cookie is erased between page loads (SSR resetTokens?), reload skipped to avoid a loop'
67
+ : '[auth] accessToken cookie was not persisted (domain/secure/maxAge rejected by browser), reload skipped',
68
+ )
69
+ }
70
+ }
71
+ else {
72
+ sessionStorage.removeItem(AUTH_RELOAD_FLAG)
60
73
  }
61
74
  })
62
75
 
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
3
2
  import IdOtpInput from '#adata-ui/components/modals/id/IdOtpInput.vue'
3
+ import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
4
+ import { writeAccessTokenCookie } from '#adata-ui/composables/useAccessTokenCookie'
4
5
  import { useUrls } from '#adata-ui/composables/useUrls'
5
6
 
6
7
  const { $toast } = useNuxtApp()
@@ -54,15 +55,9 @@ async function onConfirm() {
54
55
  const { data: cookiesData } = await response.json()
55
56
  if (cookiesData?.access_token) {
56
57
  const { access_token, expire_in } = cookiesData
57
- const hostname = location.hostname.split('.').reverse()
58
58
  useCookie('autoLogout').value = true
59
59
 
60
- useCookie('accessToken', {
61
- maxAge: expire_in,
62
- domain: `.${hostname[1]}.${hostname[0]}`,
63
- path: '/',
64
- secure: true,
65
- }).value = access_token
60
+ await writeAccessTokenCookie(access_token, expire_in)
66
61
  }
67
62
  $toast.success(t('login.successfully'))
68
63
  twoFactorModal.value = false
@@ -22,18 +22,18 @@ interface HeaderNav {
22
22
 
23
23
  const navs: HeaderNav[] = [
24
24
  { label: 'header.navs.products', dataAttribute: 'header-products-and-solutions-button', menu: ProductMenu },
25
- { label: 'header.navs.contacts', dataAttribute: 'header-contacts-button', menu: ContactMenu },
25
+ { label: 'header.navs.contacts', dataAttribute: 'header-contacts-button' },
26
26
  { label: 'header.navs.tariffs', dataAttribute: 'header-tariffs-button' },
27
27
  ]
28
28
 
29
- const OPEN_DELAY = 500 // hover dwell before opening, so a pass-through doesn't trigger it
30
- const SWITCH_DELAY = 80 // snappier when moving between items with the menu already open
29
+ const OPEN_DELAY = 500
30
+ const SWITCH_DELAY = 80
31
31
 
32
32
  const menu = ref<HTMLElement | null>(null)
33
33
  const show = ref(false)
34
- const locked = ref(false) // a click pins the menu; hover can no longer close it
34
+ const locked = ref(false)
35
35
  const currentIndex = ref(0)
36
- const swapName = ref('swap-fade') // panel transition: directional slide on switch, fade on fresh open
36
+ const swapName = ref('swap-fade')
37
37
  const hoverTimer = ref<ReturnType<typeof setTimeout> | null>(null)
38
38
 
39
39
  const currentMenu = computed(() => navs[currentIndex.value]?.menu ?? null)
@@ -42,8 +42,6 @@ function isActive(index: number) {
42
42
  return show.value && currentIndex.value === index
43
43
  }
44
44
 
45
- // Open `index`'s panel. Slide toward the direction of travel when switching
46
- // between already-open panels; fade (no sideways shift) on a fresh open.
47
45
  function openMenu(index: number) {
48
46
  swapName.value = show.value && index !== currentIndex.value
49
47
  ? (index > currentIndex.value ? 'slide-next' : 'slide-prev')
@@ -57,6 +55,10 @@ function closeMenu() {
57
55
  locked.value = false
58
56
  }
59
57
 
58
+ function goToContacts() {
59
+ navigateToLocalizedPage({ locale, projectUrl: landing, path: PAGES.contacts, target: '_blank' })
60
+ }
61
+
60
62
  function goToTariffs() {
61
63
  const host = useRequestURL().hostname
62
64
  const codeMap: Record<string, string> = {
@@ -73,11 +75,15 @@ function goToTariffs() {
73
75
  navigateToLocalizedPage({ locale, projectUrl: landing, path: PAGES.tariffs + code, target: '_blank' })
74
76
  }
75
77
 
76
- // Click pins the menu open; items without a menu (Tariffs) navigate instead.
77
78
  function toggleMenu(index: number) {
78
79
  const nav = navs[index]
79
80
  if (!nav?.menu) {
80
- goToTariffs()
81
+ if (index === 1) {
82
+ goToContacts()
83
+ }
84
+ else if (index === 2) {
85
+ goToTariffs()
86
+ }
81
87
  return
82
88
  }
83
89
  if (show.value && currentIndex.value === index) {
@@ -95,7 +101,6 @@ function clearHoverTimer() {
95
101
  }
96
102
  }
97
103
 
98
- // Hover opens menu items; hovering Tariffs dismisses an unpinned menu.
99
104
  function scheduleOpen(index: number) {
100
105
  clearHoverTimer()
101
106
  hoverTimer.value = setTimeout(() => {
@@ -104,14 +109,10 @@ function scheduleOpen(index: number) {
104
109
  }, show.value ? SWITCH_DELAY : OPEN_DELAY)
105
110
  }
106
111
 
107
- // Overlay closes on click regardless of the pin; on hover only while unpinned.
108
112
  function onOverlayHover() {
109
113
  if (!locked.value) closeMenu()
110
114
  }
111
115
 
112
- // Lock body scroll while the mega-menu is open. `scrollbar-gutter: stable` is set
113
- // globally, so we don't reserve the gap to avoid a double-compensated page shift.
114
- // The panel is mounted via v-if, so wait a tick for the ref on open.
115
116
  watch(show, async (open) => {
116
117
  if (open) {
117
118
  await nextTick()
@@ -157,7 +158,6 @@ onBeforeUnmount(() => {
157
158
  </li>
158
159
  </ul>
159
160
 
160
- <!-- One transition drives the whole panel's open/close (appear + disappear). -->
161
161
  <transition name="menu">
162
162
  <div
163
163
  v-if="show"
@@ -165,14 +165,12 @@ onBeforeUnmount(() => {
165
165
  class="fixed inset-x-0 z-20"
166
166
  :style="{ top: `${menuTop}px`, height: `calc(100dvh - ${menuTop}px)` }"
167
167
  >
168
- <!-- Full-page dimmed blur backdrop behind the panel. -->
169
168
  <div
170
169
  class="absolute inset-0 bg-gray-900/30 backdrop-blur dark:bg-black/50"
171
170
  @click="closeMenu"
172
171
  @mouseover="onOverlayHover"
173
172
  />
174
173
 
175
- <!-- Panel on top; slides left/right when switching between menu items. -->
176
174
  <transition
177
175
  :name="swapName"
178
176
  mode="out-in"
@@ -181,7 +179,6 @@ onBeforeUnmount(() => {
181
179
  :is="currentMenu"
182
180
  :key="currentIndex"
183
181
  class="relative"
184
- :url="buildLocalizedUrl(locale, landing, PAGES.contacts)"
185
182
  />
186
183
  </transition>
187
184
  </div>
@@ -193,7 +190,6 @@ onBeforeUnmount(() => {
193
190
  $ease: cubic-bezier(0.22, 1, 0.36, 1);
194
191
  $shift: 24px;
195
192
 
196
- // Whole menu: open and close (vertical fade, no horizontal motion).
197
193
  .menu-enter-active,
198
194
  .menu-leave-active {
199
195
  transition:
@@ -207,7 +203,6 @@ $shift: 24px;
207
203
  transform: translateY(-12px);
208
204
  }
209
205
 
210
- // Switching panels: slide toward the direction of travel (out-in, so no overlap).
211
206
  .slide-next-enter-active,
212
207
  .slide-next-leave-active,
213
208
  .slide-prev-enter-active,
@@ -239,7 +234,6 @@ $shift: 24px;
239
234
  transform: translateX($shift);
240
235
  }
241
236
 
242
- // Fresh open (no switch): fade only, so the panel never slides sideways.
243
237
  .swap-fade-enter-from,
244
238
  .swap-fade-leave-to {
245
239
  opacity: 0;
@@ -27,19 +27,14 @@ function isActive(itemPath: string) {
27
27
  </script>
28
28
 
29
29
  <template>
30
- <section
31
- class="group/section overflow-hidden rounded-xl border p-3 shadow-sm transition-colors"
32
- :class="currentModule
33
- ? 'border-blue-700/20 bg-blue-50 hover:border-blue-700/40 dark:border-blue-500/20 dark:bg-blue-500/5 dark:hover:border-blue-500/40'
34
- : 'border-gray-200 bg-white hover:border-blue-700/40 dark:border-gray-800 dark:bg-gray-950 dark:hover:border-blue-500/40'"
35
- >
30
+ <section class="group/section overflow-hidden flex flex-col ">
36
31
  <div class="mb-3 flex items-start justify-between gap-2">
37
32
  <nuxt-link-locale
38
33
  :to="link"
39
- class="group/title flex min-w-0 items-center gap-2 text-sm font-bold text-deepblue-900 transition-colors hover:text-blue-700 dark:text-gray-100 dark:hover:text-blue-500"
34
+ class="group/title flex min-w-0 items-center gap-2 text-base font-semibold text-deepblue-900 transition-colors hover:text-blue-700 dark:text-gray-100 dark:hover:text-blue-500"
40
35
  >
41
36
  <span
42
- class="flex size-8 shrink-0 items-center justify-center rounded-lg bg-blue-700/10 text-blue-700 transition-colors group-hover/title:bg-blue-700 group-hover/title:text-white dark:bg-blue-500/10 dark:text-blue-500">
37
+ class="flex size-8 shrink-0 items-center justify-center rounded-lg bg-blue-50 text-blue-500 transition-colors group-hover/title:bg-blue-700 group-hover/title:text-white dark:bg-blue-500/10 dark:text-blue-500 dark:group-hover/title:bg-blue-500">
43
38
  <component
44
39
  :is="icon"
45
40
  class="size-4"
@@ -47,15 +42,6 @@ function isActive(itemPath: string) {
47
42
  </span>
48
43
  <span class="block truncate normal-case">{{ title }}</span>
49
44
  </nuxt-link-locale>
50
-
51
- <div class="flex shrink-0 items-center gap-1">
52
- <span
53
- v-if="badge"
54
- class="rounded-full bg-blue-700/10 px-1.5 py-0.5 text-[10px] font-bold uppercase leading-none text-blue-700 dark:bg-blue-500/10 dark:text-blue-500"
55
- >NEW</span>
56
- <a-icon-arrow-side-up
57
- class="size-4 text-gray-500 transition-colors group-hover/section:text-blue-700 dark:group-hover/section:text-blue-500"/>
58
- </div>
59
45
  </div>
60
46
 
61
47
  <ul class="grid gap-1">
@@ -67,24 +53,12 @@ function isActive(itemPath: string) {
67
53
  v-if="item.to"
68
54
  :to="item.to"
69
55
  itemprop="name"
70
- class="group/item flex items-center gap-2 rounded-lg px-2 py-1.5 transition-colors hover:bg-blue-100 dark:hover:bg-gray-200/10"
56
+ class="group/item flex items-center gap-2 rounded-lg px-2 py-1.5 transition-colors hover:bg-gray-50 dark:hover:bg-gray-200/10"
71
57
  :class="{ 'bg-blue-100 dark:bg-gray-200/10': isActive(item.to) }"
72
58
  >
59
+ <a-icon-chevron-right class="size-[14px] shrink-0 text-gray-300 group-hover/item:text-blue-500 dark:group-hover/item:text-blue-700" />
73
60
  <span
74
- class="flex size-7 shrink-0 items-center justify-center rounded-md transition-colors group-hover/item:bg-blue-700/10 group-hover/item:text-blue-700 dark:group-hover/item:text-blue-500"
75
- :class="{
76
- 'bg-blue-700/10 text-blue-700 dark:bg-blue-500/10 dark:text-blue-500': isActive(item.to),
77
- 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-300': !isActive(item.to),
78
- // 'bg-white text-gray-600 dark:bg-white dark:text-gray-300': currentModule,
79
- }"
80
- >
81
- <component
82
- :is="item.icon"
83
- class="size-[14px]"
84
- />
85
- </span>
86
- <span
87
- class="truncate text-sm font-semibold normal-case transition-colors group-hover/item:text-deepblue-900 dark:group-hover/item:text-gray-100"
61
+ class="min-w-0 text-sm normal-case transition-colors group-hover/item:text-deepblue-900 group-hover/item:font-semibold dark:group-hover/item:text-gray-100"
88
62
  :class="isActive(item.to)
89
63
  ? 'text-blue-700 dark:text-blue-500'
90
64
  : 'text-gray-600 dark:text-gray-300'"
@@ -92,5 +66,7 @@ function isActive(itemPath: string) {
92
66
  </nuxt-link-locale>
93
67
  </li>
94
68
  </ul>
69
+
70
+ <slot name="bottom" />
95
71
  </section>
96
72
  </template>
@@ -1,20 +1,21 @@
1
1
  <script setup lang="ts">
2
- import CardGallery from '#adata-ui/components/navigation/header/CardGallery.vue'
3
2
  import NavList from '#adata-ui/components/navigation/header/NavList.vue'
4
3
  import { useActiveNavigation } from '#adata-ui/composables/useActiveNavigation'
5
4
  import { useHeaderNavigationLinks } from '#adata-ui/composables/useHeaderNavigationLinks'
5
+ import { PAGES } from '#adata-ui/shared/constans/pages'
6
+ import { useUrls } from '#adata-ui/composables/useUrls'
7
+ import AIconArrowRight from '#adata-ui/icons/arrow/arrow-right.vue'
8
+ import AIconDownloadRounded from '#adata-ui/icons/download-rounded.vue'
6
9
 
7
10
  defineProps<{ url?: string }>()
8
11
  defineEmits(['outerClick', 'mouseOver'])
9
12
 
10
- const { t } = useI18n()
13
+ const urls = useUrls()
14
+ const { t, locale } = useI18n()
11
15
  const modules = useHeaderNavigationLinks()
12
16
  const { isActiveModule } = useActiveNavigation()
13
17
 
14
- // Fixed EDO promo card on the left (like edo-editor); EDO is excluded from the catalog grid below.
15
- const edoModule = computed(() => modules.value.find(module => module.key === 'edo') ?? null)
16
-
17
- const COLUMN_KEYS = [['pk', 'fines', 'work'], ['fea', 'tenders'], ['compliance', 'analytics']]
18
+ const COLUMN_KEYS = [['edo'], ['pk'], ['compliance', 'fines'], ['tenders', 'analytics'], ['fea', 'work']]
18
19
  const catalogColumns = computed(() =>
19
20
  COLUMN_KEYS.map(keys =>
20
21
  keys
@@ -22,6 +23,14 @@ const catalogColumns = computed(() =>
22
23
  .filter((module): module is NonNullable<typeof module> => module !== undefined),
23
24
  ),
24
25
  )
26
+
27
+ function handleExportClick() {
28
+ navigateToLocalizedPage({
29
+ locale: locale.value,
30
+ projectUrl: urls.pk,
31
+ path: PAGES.pk.unload,
32
+ })
33
+ }
25
34
  </script>
26
35
 
27
36
  <template>
@@ -29,52 +38,12 @@ const catalogColumns = computed(() =>
29
38
  <div class="relative bg-white dark:bg-gray-950">
30
39
  <div class="a-container max-h-[calc(100dvh-4rem)] overflow-y-auto overscroll-contain py-8">
31
40
  <div class="flex justify-between gap-5">
32
- <!-- Left sidebar: EDO promo card (edo-editor style) + promo carousel. -->
33
- <aside class="flex w-[320px] shrink-0 flex-col gap-5">
34
- <nuxt-link-locale
35
- v-if="edoModule"
36
- :to="edoModule.link"
37
- class="group block overflow-hidden rounded-xl border border-blue-700/20 p-4 shadow-sm transition-colors hover:border-blue-700/40 hover:bg-blue-100 dark:border-blue-500/20 dark:hover:border-blue-500/40 dark:hover:bg-blue-500/10"
38
- :data-test-id="edoModule.data_attribute"
39
- >
40
- <div class="flex items-start gap-3">
41
- <div
42
- class="flex size-10 shrink-0 items-center justify-center rounded-lg bg-blue-700 text-white shadow-sm">
43
- <component
44
- :is="edoModule.icon"
45
- class="size-5"
46
- />
47
- </div>
48
- <div class="min-w-0">
49
- <div class="flex items-center justify-between gap-2">
50
- <h3 class="text-base font-bold normal-case text-deepblue-900 dark:text-gray-100">
51
- {{ edoModule.name }}
52
- </h3>
53
- <div class="flex items-center gap-1 justify-end">
54
- <span
55
- v-if="edoModule.is_new"
56
- class="rounded-full bg-blue-700/10 px-1.5 py-0.5 text-[10px] font-bold uppercase leading-none text-blue-700 dark:bg-blue-500/10 dark:text-blue-500"
57
- >NEW</span>
58
- <a-icon-arrow-side-up
59
- class="size-4 text-gray-500 transition group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-blue-700 dark:text-gray-400 dark:group-hover:text-blue-500"/>
60
- </div>
61
- </div>
62
- <p class="mt-1 text-sm normal-case leading-snug text-gray-600 dark:text-gray-300">
63
- {{ t('header.products.edo.heroSubtitle') }}
64
- </p>
65
- </div>
66
- </div>
67
- </nuxt-link-locale>
68
-
69
- <card-gallery class="hidden lg:block"/>
70
- </aside>
71
-
72
- <!-- Catalog grid: products as bordered section cards (EDO lives in the hero). -->
73
- <div class="grid min-w-0 flex-1 grid-cols-3 gap-3">
41
+ <div class="grid min-w-0 flex-1 grid-cols-5 gap-x-3 gap-y-3">
74
42
  <div
75
43
  v-for="(column, index) in catalogColumns"
76
44
  :key="index"
77
- class="flex flex-col gap-3"
45
+ class="flex flex-col gap-[22px]"
46
+ :class="index > 0 ? 'border-l border-gray-100 pl-3 dark:border-gray-800' : ''"
78
47
  >
79
48
  <nav-list
80
49
  v-for="module in column"
@@ -88,7 +57,40 @@ const catalogColumns = computed(() =>
88
57
  :icon="module.icon"
89
58
  :badge="module.is_new"
90
59
  :data-test-id="module.data_attribute"
91
- />
60
+ >
61
+ <template
62
+ v-if="module.key === 'edo'"
63
+ #bottom
64
+ >
65
+ <div class="mt-4 flex flex-col gap-4 rounded-xl bg-blue-700 dark:bg-blue-500 p-4 text-white normal-case">
66
+ <div class="flex items-center gap-3">
67
+ <span class="p-[10px] bg-white rounded-[10px] dark:bg-gray-950">
68
+ <AIconDownloadRounded class="size-5 text-blue-700 dark:text-blue-500" />
69
+ </span>
70
+ <span class="font-semibold text-white dark:text-gray-950">
71
+ {{ t('header.products.exportCard.title') }}
72
+ </span>
73
+ </div>
74
+
75
+ <div class="text-xs text-white dark:text-gray-950">
76
+ {{ t('header.products.exportCard.subTitle') }}
77
+ </div>
78
+
79
+ <a-button-v2
80
+ view="soft"
81
+ class="!h-9 w-fit !bg-white dark:!bg-gray-950 text-blue-700 dark:text-blue-500"
82
+ size="sm"
83
+ :icon="AIconArrowRight"
84
+ icon-right
85
+ @click="handleExportClick"
86
+ >
87
+ <span class="text-sm">
88
+ {{ t('header.products.exportCard.receive') }}
89
+ </span>
90
+ </a-button-v2>
91
+ </div>
92
+ </template>
93
+ </nav-list>
92
94
  </div>
93
95
  </div>
94
96
  </div>
@@ -0,0 +1,25 @@
1
+ import { useUrls } from '#adata-ui/composables/useUrls'
2
+
3
+ /**
4
+ * Единая точка записи куки accessToken (логин, 2FA, OTP, обмен по auth-сессии).
5
+ * Домен должен совпадать с доменом удаления в resetTokens (adata-common-auth),
6
+ * иначе кука не находится на следующей загрузке и логин-флоу зацикливается на reload.
7
+ */
8
+ export async function writeAccessTokenCookie(accessToken: string, expireIn?: number): Promise<boolean> {
9
+ const { mode } = useUrls()
10
+ const sharedDomain = `${mode}.kz`
11
+ const onSharedDomain = location.hostname === sharedDomain || location.hostname.endsWith(`.${sharedDomain}`)
12
+ const maxAge = Number(expireIn) > 0 ? Number(expireIn) : undefined
13
+
14
+ useCookie('accessToken', {
15
+ maxAge,
16
+ // на localhost/превью-хостах атрибут domain невалиден — пишем host-only куку
17
+ domain: onSharedDomain ? `.${sharedDomain}` : undefined,
18
+ path: '/',
19
+ secure: location.protocol === 'https:',
20
+ }).value = accessToken
21
+
22
+ // useCookie пишет document.cookie на следующем flush, не синхронно
23
+ await nextTick()
24
+ return document.cookie.split('; ').some(part => part.startsWith('accessToken='))
25
+ }
@@ -4,6 +4,7 @@ interface UseChipOverflowOptions {
4
4
  container: Ref<HTMLElement | null>
5
5
  measure: Ref<HTMLElement | null>
6
6
  count: Ref<number>
7
+ contentKey?: Ref<unknown>
7
8
  gap?: number
8
9
  }
9
10
 
@@ -13,11 +14,17 @@ interface UseChipOverflow {
13
14
  }
14
15
 
15
16
  export function useChipOverflow(options: UseChipOverflowOptions): UseChipOverflow {
16
- const { container, measure, count, gap = 6 } = options
17
+ const { container, measure, count, contentKey, gap = 6 } = options
17
18
 
18
19
  const availableWidth = ref(0)
19
20
  const visibleCount = ref(0)
20
21
 
22
+ // Ceil so a chip is only counted as fitting when it fully fits;
23
+ // sub-pixel underestimates would let flexbox squeeze visible chips into truncation.
24
+ function chipWidth(el: HTMLElement): number {
25
+ return Math.ceil(el.getBoundingClientRect().width)
26
+ }
27
+
21
28
  function recompute() {
22
29
  const total = count.value
23
30
  if (total <= 0) {
@@ -38,7 +45,7 @@ export function useChipOverflow(options: UseChipOverflowOptions): UseChipOverflo
38
45
  let used = 0
39
46
  let fit = 0
40
47
  for (let i = 0; i < chips.length; i++) {
41
- const width = chips[i].offsetWidth + (i > 0 ? gap : 0)
48
+ const width = chipWidth(chips[i]) + (i > 0 ? gap : 0)
42
49
  if (used + width > avail) break
43
50
  used += width
44
51
  fit++
@@ -50,9 +57,9 @@ export function useChipOverflow(options: UseChipOverflowOptions): UseChipOverflo
50
57
  }
51
58
 
52
59
  const badge = root.children[total] as HTMLElement | undefined
53
- const badgeWidth = (badge?.offsetWidth ?? 0) + gap
60
+ const badgeWidth = (badge ? chipWidth(badge) : 0) + gap
54
61
  while (fit > 1 && used + badgeWidth > avail) {
55
- used -= chips[fit - 1].offsetWidth + gap
62
+ used -= chipWidth(chips[fit - 1]) + gap
56
63
  fit--
57
64
  }
58
65
 
@@ -63,9 +70,9 @@ export function useChipOverflow(options: UseChipOverflowOptions): UseChipOverflo
63
70
 
64
71
  onMounted(() => {
65
72
  if (container.value) {
66
- availableWidth.value = container.value.clientWidth
73
+ availableWidth.value = Math.floor(container.value.clientWidth)
67
74
  resizeObserver = new ResizeObserver((entries) => {
68
- availableWidth.value = entries[0]?.contentRect.width ?? 0
75
+ availableWidth.value = Math.floor(entries[0]?.contentRect.width ?? 0)
69
76
  })
70
77
  resizeObserver.observe(container.value)
71
78
  }
@@ -74,7 +81,10 @@ export function useChipOverflow(options: UseChipOverflowOptions): UseChipOverflo
74
81
 
75
82
  onUnmounted(() => resizeObserver?.disconnect())
76
83
 
77
- watch([availableWidth, count], () => nextTick(recompute))
84
+ watch(
85
+ contentKey ? [availableWidth, count, contentKey] : [availableWidth, count],
86
+ () => nextTick(recompute),
87
+ )
78
88
 
79
89
  const hiddenCount = computed(() => Math.max(0, count.value - visibleCount.value))
80
90
 
@@ -9,12 +9,9 @@ import IconBlock from '#adata-ui/icons/block.vue'
9
9
  import IconDollar from '#adata-ui/icons/currency/currency-dollar.vue'
10
10
  import IconWork from '#adata-ui/icons/work-bag.vue'
11
11
  import IconDocument from '#adata-ui/icons/hdocument.vue'
12
- import IconCheck from '#adata-ui/icons/hcheck.vue'
13
- import IconFile from '#adata-ui/icons/file/file.vue'
14
12
  import IconCheckCircle from '#adata-ui/icons/check/check-circle.vue'
15
13
  import IconCar from '#adata-ui/icons/car.vue'
16
14
  import IconTruck from '#adata-ui/icons/truck.vue'
17
- import IconClipboardText from '#adata-ui/icons/clipboard-text.vue'
18
15
  import IconArrowGraph from '#adata-ui/icons/arrow/arrow-graph-up.vue'
19
16
  import IconMedal from '#adata-ui/icons/medal.vue'
20
17
  import IconCompany from '#adata-ui/icons/company.vue'
@@ -26,24 +23,27 @@ import { buildLocalizedUrl } from '#adata-ui/utils/localizedNavigation'
26
23
  import IconLock from '#adata-ui/icons/lock.vue'
27
24
  import IconReceipt from '#adata-ui/icons/receipt.vue'
28
25
  import IconFavorite from '#adata-ui/icons/bookmark/bookmark.vue'
29
- // import IconReport from '#adata-ui/icons/file/file.vue'
30
26
  import IconHistory from '#adata-ui/icons/history.vue'
31
27
  import IconMessage from '#adata-ui/icons/message/message.vue'
28
+ import AIconBriefcase from '#adata-ui/icons/briefcase.vue'
29
+ import AIconCarSide from '#adata-ui/icons/car-side.vue'
30
+ import AIconShieldVerified from '#adata-ui/icons/shield-verified.vue'
31
+ import AIconCart from '#adata-ui/icons/cart.vue'
32
32
  import {
33
- AIconWorkBag,
34
33
  AIconUsersThree,
35
- AIconReceipt,
36
- AIconCar,
37
34
  AIconArrowGraphUp,
38
35
  AIconGlobe,
39
- AIconId,
40
- AIconFiles,
41
- AIconArrowSquareDown, AIconExpandWindow, AIconUsers, AIconWarningTriangle, AIconSearch, AIconAkimat, AIconCurrencyTenge, AIconExcelIcon
36
+ AIconArrowSquareDown,
37
+ AIconExpandWindow,
38
+ AIconUsers,
39
+ AIconWarningTriangle,
40
+ AIconSearch,
41
+ AIconAkimat,
42
+ AIconCurrencyTenge,
43
+ AIconExcelIcon,
42
44
  } from '#components'
43
45
  import MapPinRect from '#adata-ui/icons/map/map-pin-rect.vue'
44
46
  import MapPaper from '#adata-ui/icons/map-paper.vue'
45
- import ChartBar from '#adata-ui/icons/chart-bar.vue'
46
- import Bank from '#adata-ui/icons/bank.vue'
47
47
  import { useUrls } from '#adata-ui/composables/useUrls'
48
48
  import AiAssistant from '#adata-ui/icons/ai-assistant.vue'
49
49
  import Tasks from '#adata-ui/icons/tasks.vue'
@@ -61,7 +61,7 @@ export const useHeaderNavigationLinks = () => {
61
61
  key: 'pk',
62
62
  name: t('header.products.counterparties.label'),
63
63
  link: buildLocalizedUrl(locale, urls.pk, PAGES.pk.main),
64
- icon: AIconUsersThree,
64
+ icon: AIconUsers,
65
65
  to: urls.pk,
66
66
  data_attribute: 'navigation-menu-counterparty-block',
67
67
  items: [
@@ -115,7 +115,7 @@ export const useHeaderNavigationLinks = () => {
115
115
  key: 'fines',
116
116
  name: t('header.products.fines.label'),
117
117
  link: buildLocalizedUrl(locale, urls.avto, PAGES.fines.main),
118
- icon: AIconCar,
118
+ icon: AIconCarSide,
119
119
  data_attribute: 'navigation-menu-fines-block',
120
120
  items: [
121
121
  {
@@ -142,7 +142,7 @@ export const useHeaderNavigationLinks = () => {
142
142
  key: 'work',
143
143
  name: t('header.products.jobs.label'),
144
144
  link: urls.work,
145
- icon: AIconWorkBag,
145
+ icon: AIconBriefcase,
146
146
  data_attribute: 'navigation-menu-work-block',
147
147
  items: [
148
148
  {
@@ -216,7 +216,7 @@ export const useHeaderNavigationLinks = () => {
216
216
  key: 'tenders',
217
217
  name: t('header.products.tenders.label'),
218
218
  link: urls.zakupki,
219
- icon: AIconReceipt,
219
+ icon: AIconCart,
220
220
  is_new: true,
221
221
  to: urls.tender,
222
222
  data_attribute: 'navigation-menu-zakupki-block',
@@ -264,15 +264,39 @@ export const useHeaderNavigationLinks = () => {
264
264
  is_new: true,
265
265
  name: t('header.products.edo.label'),
266
266
  link: buildLocalizedUrl(locale, urls.edo_docs , ''),
267
- icon: AIconFiles,
267
+ icon: AIconUsers,
268
268
  data_attribute: 'navigation-menu-edo-block',
269
269
  items: [
270
270
  {
271
- title: t('header.products.edo.items.l.t'),
272
- subtitle: t('header.products.edo.items.l.t'),
273
- icon: AIconFiles,
274
- to: buildLocalizedUrl(locale, urls.edo_docs , '')
275
- }
271
+ title: t('header.products.edo.items.docSign.t'),
272
+ subtitle: t('header.products.edo.items.docSign.st'),
273
+ icon: AIconUsers,
274
+ to: buildLocalizedUrl(locale, urls.edo_docs , PAGES.edo.docSign)
275
+ },
276
+ {
277
+ title: t('header.products.edo.items.docTemplate.t'),
278
+ subtitle: t('header.products.edo.items.docTemplate.st'),
279
+ icon: AIconUsers,
280
+ to: buildLocalizedUrl(locale, urls.edo_docs , PAGES.edo.docTemplate)
281
+ },
282
+ {
283
+ title: t('header.products.edo.items.docRisk.t'),
284
+ subtitle: t('header.products.edo.items.docRisk.st'),
285
+ icon: AIconUsers,
286
+ to: buildLocalizedUrl(locale, urls.edo_docs , PAGES.edo.docRisk)
287
+ },
288
+ {
289
+ title: t('header.products.edo.items.docCompare.t'),
290
+ subtitle: t('header.products.edo.items.docCompare.st'),
291
+ icon: AIconUsers,
292
+ to: buildLocalizedUrl(locale, urls.edo_docs , PAGES.edo.docCompare)
293
+ },
294
+ {
295
+ title: t('header.products.edo.items.docAi.t'),
296
+ subtitle: t('header.products.edo.items.docAi.st'),
297
+ icon: AIconUsers,
298
+ to: buildLocalizedUrl(locale, urls.edo_docs , PAGES.edo.docAi)
299
+ },
276
300
  ]
277
301
  },
278
302
  {
@@ -280,7 +304,7 @@ export const useHeaderNavigationLinks = () => {
280
304
  is_new: true,
281
305
  name: t('header.products.compliance.label'),
282
306
  link: buildLocalizedUrl(locale, urls.compliance, PAGES.compliance.l),
283
- icon: AIconId,
307
+ icon: AIconShieldVerified,
284
308
  data_attribute: 'navigation-menu-compliance-block',
285
309
  items: [
286
310
  {
@@ -352,6 +376,12 @@ export const useHeaderNavigationLinks = () => {
352
376
  subtitle: t('header.products.analytics.items.subjects.subtitle'),
353
377
  icon: IconCompany,
354
378
  to: buildLocalizedUrl(locale, urls.analytics, PAGES.analytics.business)
379
+ },
380
+ {
381
+ title: t('header.products.analytics.items.demography.title'),
382
+ subtitle: t('header.products.analytics.items.demography.subtitle'),
383
+ icon: IconCompany,
384
+ to: buildLocalizedUrl(locale, urls.analytics, PAGES.analytics.demography)
355
385
  }
356
386
  ]
357
387
  },
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+
3
+ </script>
4
+
5
+ <template>
6
+ <svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
7
+ <path d="M3.125 7.5H11.875" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
8
+ <path d="M7.5 3.125L11.875 7.5L7.5 11.875" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
9
+ </svg>
10
+ </template>
11
+
12
+ <style scoped>
13
+
14
+ </style>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+
3
+ </script>
4
+
5
+ <template>
6
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
7
+ <path d="M15 5.25H3C2.17157 5.25 1.5 5.92157 1.5 6.75V14.25C1.5 15.0784 2.17157 15.75 3 15.75H15C15.8284 15.75 16.5 15.0784 16.5 14.25V6.75C16.5 5.92157 15.8284 5.25 15 5.25Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
8
+ <path d="M12 15.75V3.75C12 3.35218 11.842 2.97064 11.5607 2.68934C11.2794 2.40804 10.8978 2.25 10.5 2.25H7.5C7.10218 2.25 6.72064 2.40804 6.43934 2.68934C6.15804 2.97064 6 3.35218 6 3.75V15.75" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
9
+ </svg>
10
+ </template>
11
+
12
+ <style scoped>
13
+
14
+ </style>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+
3
+ </script>
4
+
5
+ <template>
6
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
7
+ <path d="M14.25 12.75H15.75C16.2 12.75 16.5 12.45 16.5 12V9.75C16.5 9.075 15.975 8.475 15.375 8.325C14.025 7.95 12 7.5 12 7.5C12 7.5 11.025 6.45 10.35 5.775C9.975 5.475 9.525 5.25 9 5.25H3.75C3.3 5.25 2.925 5.55 2.7 5.925L1.65 8.1C1.55068 8.38967 1.5 8.69378 1.5 9V12C1.5 12.45 1.8 12.75 2.25 12.75H3.75" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
8
+ <path d="M5.25 14.25C6.07843 14.25 6.75 13.5784 6.75 12.75C6.75 11.9216 6.07843 11.25 5.25 11.25C4.42157 11.25 3.75 11.9216 3.75 12.75C3.75 13.5784 4.42157 14.25 5.25 14.25Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
9
+ <path d="M6.75 12.75H11.25" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
10
+ <path d="M12.75 14.25C13.5784 14.25 14.25 13.5784 14.25 12.75C14.25 11.9216 13.5784 11.25 12.75 11.25C11.9216 11.25 11.25 11.9216 11.25 12.75C11.25 13.5784 11.9216 14.25 12.75 14.25Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
11
+ </svg>
12
+ </template>
13
+
14
+ <style scoped>
15
+
16
+ </style>
package/icons/cart.vue ADDED
@@ -0,0 +1,15 @@
1
+ <script setup lang="ts">
2
+
3
+ </script>
4
+
5
+ <template>
6
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
7
+ <path d="M6 16.5C6.41421 16.5 6.75 16.1642 6.75 15.75C6.75 15.3358 6.41421 15 6 15C5.58579 15 5.25 15.3358 5.25 15.75C5.25 16.1642 5.58579 16.5 6 16.5Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
8
+ <path d="M14.25 16.5C14.6642 16.5 15 16.1642 15 15.75C15 15.3358 14.6642 15 14.25 15C13.8358 15 13.5 15.3358 13.5 15.75C13.5 16.1642 13.8358 16.5 14.25 16.5Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
9
+ <path d="M1.53748 1.53748H3.03748L5.03248 10.8525C5.10566 11.1936 5.29548 11.4986 5.56926 11.7149C5.84304 11.9312 6.18366 12.0452 6.53248 12.0375H13.8675C14.2089 12.0369 14.5398 11.9199 14.8057 11.7059C15.0717 11.4918 15.2566 11.1934 15.33 10.86L16.5675 5.28748H3.83998" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
10
+ </svg>
11
+ </template>
12
+
13
+ <style scoped>
14
+
15
+ </style>
@@ -0,0 +1,15 @@
1
+ <script setup lang="ts">
2
+
3
+ </script>
4
+
5
+ <template>
6
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
7
+ <path d="M17.5 12.5V15.8333C17.5 16.2754 17.3244 16.6993 17.0118 17.0118C16.6993 17.3244 16.2754 17.5 15.8333 17.5H4.16667C3.72464 17.5 3.30072 17.3244 2.98816 17.0118C2.67559 16.6993 2.5 16.2754 2.5 15.8333V12.5" stroke="currentColor" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
8
+ <path d="M5.83334 8.33337L10 12.5L14.1667 8.33337" stroke="currentColor" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
9
+ <path d="M10 12.5V2.5" stroke="currentColor" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
10
+ </svg>
11
+ </template>
12
+
13
+ <style scoped>
14
+
15
+ </style>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+
3
+ </script>
4
+
5
+ <template>
6
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
7
+ <path d="M15 9.74997C15 13.5 12.375 15.375 9.255 16.4625C9.09162 16.5178 8.91415 16.5152 8.7525 16.455C5.625 15.375 3 13.5 3 9.74997V4.49997C3 4.30106 3.07902 4.11029 3.21967 3.96964C3.36032 3.82899 3.55109 3.74997 3.75 3.74997C5.25 3.74997 7.125 2.84997 8.43 1.70997C8.58889 1.57422 8.79102 1.49963 9 1.49963C9.20898 1.49963 9.41111 1.57422 9.57 1.70997C10.8825 2.85747 12.75 3.74997 14.25 3.74997C14.4489 3.74997 14.6397 3.82899 14.7803 3.96964C14.921 4.11029 15 4.30106 15 4.49997V9.74997Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
8
+ <path d="M6.75 9L8.25 10.5L11.25 7.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
9
+ </svg>
10
+ </template>
11
+
12
+ <style scoped>
13
+
14
+ </style>
package/lang/en.ts CHANGED
@@ -191,7 +191,7 @@ const EnLocale: RuLocale = {
191
191
  subtitle: 'Evaluate the volume of competitors\' procurements over a certain period',
192
192
  },
193
193
  activity: {
194
- title: 'Business activity index',
194
+ title: 'Activity index',
195
195
  subtitle: 'Assessment of market risk of current business activity',
196
196
  },
197
197
  subjects: {
@@ -202,13 +202,17 @@ const EnLocale: RuLocale = {
202
202
  title: 'Taxpayer ranking',
203
203
  subtitle: 'Taxpayers who regularly pay taxes',
204
204
  },
205
+ demography: {
206
+ title: 'Demography',
207
+ subtitle: '',
208
+ }
205
209
  },
206
210
  },
207
211
  fea: {
208
212
  label: 'Foreign Economic Activity',
209
213
  items: {
210
214
  i: {
211
- t: 'Foreign trade in goods',
215
+ t: 'Tender search',
212
216
  st: 'Information service of foreign trade turnover by foreign economic activity participants',
213
217
  },
214
218
  o: {
@@ -224,7 +228,7 @@ const EnLocale: RuLocale = {
224
228
  st: 'Calculation of Customs Payments. Customs Duties. Customs Fees',
225
229
  },
226
230
  tr: {
227
- t: 'International market and tariffs',
231
+ t: 'Market and tariffs',
228
232
  st: 'Customs Tariffs and Quotas. Measures for Protecting the Domestic Market. Regulatory Requirements',
229
233
  },
230
234
  importerMap: {
@@ -232,7 +236,7 @@ const EnLocale: RuLocale = {
232
236
  st: '',
233
237
  },
234
238
  containers: {
235
- t: 'Container Tracking',
239
+ t: 'Containers',
236
240
  st: '',
237
241
  },
238
242
  },
@@ -277,6 +281,27 @@ const EnLocale: RuLocale = {
277
281
  items: {
278
282
  l: {
279
283
  t: 'Document Flow',
284
+ st: '',
285
+ },
286
+ docSign: {
287
+ t: 'Document signing',
288
+ st: '',
289
+ },
290
+ docTemplate: {
291
+ t: 'Document templates',
292
+ st: '',
293
+ },
294
+ docRisk: {
295
+ t: 'Document risk check',
296
+ st: '',
297
+ },
298
+ docCompare: {
299
+ t: 'Document comparison',
300
+ st: '',
301
+ },
302
+ docAi: {
303
+ t: 'AI chat for documents',
304
+ st: '',
280
305
  },
281
306
  },
282
307
  },
@@ -295,6 +320,11 @@ const EnLocale: RuLocale = {
295
320
  'Automated compliance risk assessment: real-time business protection',
296
321
  },
297
322
  },
323
+ exportCard: {
324
+ title: 'Data export',
325
+ subTitle: 'Find companies and sole proprietors using the specified filters and receive a list of potential clients',
326
+ receive: 'Get',
327
+ },
298
328
  },
299
329
  contacts: {
300
330
  almaty: {
package/lang/kk.ts CHANGED
@@ -191,7 +191,7 @@ const KkLocale: RuLocale = {
191
191
  subtitle: 'Бәсекелестердің белгілі бір кезеңдегі сатып алу көлемін бағалау',
192
192
  },
193
193
  activity: {
194
- title: 'Іскерлік белсенділік индексі',
194
+ title: 'Белсенділік индексі',
195
195
  subtitle: 'Қазіргі іскерлік белсенділіктің нарықтық тәуекелін бағалау',
196
196
  },
197
197
  subjects: {
@@ -202,13 +202,17 @@ const KkLocale: RuLocale = {
202
202
  title: 'Салық төлеушілер рейтингі',
203
203
  subtitle: 'Салықтарды тұрақты түрде төлейтін салық төлеушілер',
204
204
  },
205
+ demography: {
206
+ title: 'Демография',
207
+ subtitle: '',
208
+ }
205
209
  },
206
210
  },
207
211
  fea: {
208
212
  label: 'СЭҚ',
209
213
  items: {
210
214
  i: {
211
- t: 'Тауарлардың сыртқы саудасы',
215
+ t: 'Тендерлерді іздеу',
212
216
  st: 'Сыртқы экономикалық қызметке қатысушылардың сыртқы сауда айналымының ақпараттық қызметі',
213
217
  },
214
218
  o: {
@@ -224,7 +228,7 @@ const KkLocale: RuLocale = {
224
228
  st: 'Кедендік төлемдерді есептеу. Кедендік баждар. Кедендік алымдар',
225
229
  },
226
230
  tr: {
227
- t: 'Халықаралық нарық және тарифтер',
231
+ t: 'Нарық және тарифтер',
228
232
  st: 'Кедендік тарифтер мен квоталар. Ішкі нарықты қорғау шаралары. Нормативтік талаптар',
229
233
  },
230
234
  importerMap: {
@@ -232,7 +236,7 @@ const KkLocale: RuLocale = {
232
236
  st: '',
233
237
  },
234
238
  containers: {
235
- t: 'Контейнерлерді бақылау',
239
+ t: 'Контейнерлер',
236
240
  st: '',
237
241
  },
238
242
  },
@@ -277,6 +281,27 @@ const KkLocale: RuLocale = {
277
281
  items: {
278
282
  l: {
279
283
  t: 'Құжат айналымы',
284
+ st: '',
285
+ },
286
+ docSign: {
287
+ t: 'Құжаттарға қол қою',
288
+ st: '',
289
+ },
290
+ docTemplate: {
291
+ t: 'Құжат үлгілері',
292
+ st: '',
293
+ },
294
+ docRisk: {
295
+ t: 'Құжатты тәуекелдерге тексеру',
296
+ st: '',
297
+ },
298
+ docCompare: {
299
+ t: 'Құжаттарды салыстыру',
300
+ st: '',
301
+ },
302
+ docAi: {
303
+ t: 'Құжаттар бойынша AI-чат',
304
+ st: '',
280
305
  },
281
306
  },
282
307
  },
@@ -295,6 +320,11 @@ const KkLocale: RuLocale = {
295
320
  'Комплаенс тәуекелдерін автоматты түрде тексеру: нақты уақыт режимінде бизнесіңізді қорғаңыз',
296
321
  },
297
322
  },
323
+ exportCard: {
324
+ title: 'Деректерді жүктеу',
325
+ subTitle: 'Берілген сүзгілер бойынша компаниялар мен ЖК табыңыз және әлеуетті клиенттердің жүктемесін алыңыз',
326
+ receive: 'Алу',
327
+ },
298
328
  },
299
329
  contacts: {
300
330
  almaty: {
package/lang/ru.ts CHANGED
@@ -190,12 +190,12 @@ const RuLocale = {
190
190
  'Оцените объем закупок конкурентов за определенный период',
191
191
  },
192
192
  activity: {
193
- title: 'Индекс деловой активности',
193
+ title: 'Индекс активности',
194
194
  subtitle:
195
195
  'Оценка рыночного риска текущей деловой активности',
196
196
  },
197
197
  subjects: {
198
- title: 'Субьекты бизнеса',
198
+ title: 'Субъекты бизнеса',
199
199
  subtitle:
200
200
  'Изменение числа бизнес-субъектов в отраслях и секторах экономики',
201
201
  },
@@ -203,13 +203,17 @@ const RuLocale = {
203
203
  title: 'Рейтинг налогоплательщиков',
204
204
  subtitle: 'Налогоплательщики, регулярно уплачивающие налоги',
205
205
  },
206
+ demography: {
207
+ title: 'Демография',
208
+ subtitle: '',
209
+ }
206
210
  },
207
211
  },
208
212
  fea: {
209
213
  label: 'ВЭД',
210
214
  items: {
211
215
  i: {
212
- t: 'Внешняя торговля товарами',
216
+ t: 'Поиск тендеров',
213
217
  st: 'Информационный сервис внешнеторгового оборота в разрезе участников ВЭД',
214
218
  },
215
219
  o: {
@@ -225,15 +229,15 @@ const RuLocale = {
225
229
  st: 'Расчет таможенных платежей. Таможенные пошлины. Таможенные сборы',
226
230
  },
227
231
  tr: {
228
- t: 'Международный рынок и тарифы',
232
+ t: 'Рынок и тарифы',
229
233
  st: 'Таможенные тарифы и квоты. Меры защиты внутреннего рынка. Нормативные требования',
230
234
  },
231
235
  importerMap: {
232
- t: 'Карта импортера',
236
+ t: 'Карта импортёра',
233
237
  st: '',
234
238
  },
235
239
  containers: {
236
- t: 'Отслеживание контейнеров',
240
+ t: 'Контейнеры',
237
241
  st: '',
238
242
  },
239
243
  },
@@ -278,6 +282,27 @@ const RuLocale = {
278
282
  items: {
279
283
  l: {
280
284
  t: 'Документооборот',
285
+ st: '',
286
+ },
287
+ docSign: {
288
+ t: 'Подписание документов',
289
+ st: '',
290
+ },
291
+ docTemplate: {
292
+ t: 'Шаблоны документов',
293
+ st: '',
294
+ },
295
+ docRisk: {
296
+ t: 'Проверка документа на риски',
297
+ st: '',
298
+ },
299
+ docCompare: {
300
+ t: 'Сравнение документов',
301
+ st: '',
302
+ },
303
+ docAi: {
304
+ t: 'AI-чат по документам',
305
+ st: '',
281
306
  },
282
307
  },
283
308
  },
@@ -295,6 +320,11 @@ const RuLocale = {
295
320
  subtitle: 'Автопроверка комплаенс-рисков: защита бизнеса в режиме реального времени',
296
321
  },
297
322
  },
323
+ exportCard: {
324
+ title: 'Выгрузка данных',
325
+ subTitle: 'Найдите компании и ИП по заданным фильтрам и получите выгрузку потенциальных клиентов',
326
+ receive: 'Получить',
327
+ },
298
328
  },
299
329
  contacts: {
300
330
  almaty: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adata-ui",
3
3
  "type": "module",
4
- "version": "2.1.40-beta.5",
4
+ "version": "2.1.40-beta.7",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "dev": "nuxi dev .playground",
@@ -62,7 +62,8 @@ export const PAGES = {
62
62
  analysis: '/purchasing-analysis',
63
63
  index: '/business-activity',
64
64
  rating: '/tax-payment',
65
- business: '/business-entities'
65
+ business: '/business-entities',
66
+ demography: '/demography',
66
67
  },
67
68
  fea: {
68
69
  main: '/',
@@ -85,6 +86,11 @@ export const PAGES = {
85
86
  sb: '/sb'
86
87
  },
87
88
  edo: {
88
- l: '/'
89
+ l: '/',
90
+ docSign: '/create?step=2&source=upload',
91
+ docTemplate: '/create?step=2&source=template',
92
+ docRisk: '/create?step=2&source=import&postImportAction=risks',
93
+ docCompare: '/create?step=2&source=import',
94
+ docAi: '/create?step=3&source=blank',
89
95
  }
90
96
  }