adata-ui 2.1.41-beta.23 → 2.1.41-beta.24

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.
@@ -21,31 +21,37 @@ const socialMedia = [
21
21
  icon: LinkedinIcon,
22
22
  link: 'https://www.linkedin.com/company/adata-kz/?viewAsMember=true',
23
23
  name: 'Linkedin',
24
+ dataTestId: 'footer-linkedin-link',
24
25
  },
25
26
  {
26
27
  icon: YouTubeIcon,
27
28
  link: 'https://www.youtube.com/channel/UCPkbtgwgTZbMJXjmTi3R8Uw/featured',
28
29
  name: 'YouTube',
30
+ dataTestId: 'footer-youtube-link',
29
31
  },
30
32
  {
31
33
  icon: InstagramIcon,
32
34
  link: 'https://www.instagram.com/adata.kz/?igshid=253i4qxg3els',
33
35
  name: 'Instagram',
36
+ dataTestId: 'footer-instagram-link',
34
37
  },
35
38
  {
36
39
  icon: TelegramIcon,
37
40
  link: 'https://t.me/adatakz_official',
38
41
  name: 'Telegram',
42
+ dataTestId: 'footer-telegram-link',
39
43
  },
40
44
  {
41
45
  icon: FaceBookIcon,
42
46
  link: 'https://www.facebook.com/adata.kz',
43
47
  name: 'Facebook',
48
+ dataTestId: 'footer-facebook-link',
44
49
  },
45
50
  {
46
51
  icon: TikTokIcon,
47
52
  link: 'https://www.tiktok.com/@adata.kz?lang=ru-RU',
48
53
  name: 'Tiktok',
54
+ dataTestId: 'footer-tiktok-link',
49
55
  },
50
56
  ]
51
57
 
@@ -53,28 +59,34 @@ const mainLinks = computed(() => ({
53
59
  services: {
54
60
  title: t('footer.productsAndSolutions'),
55
61
  link: buildLocalizedUrl(locale.value, urls.landing, PAGES.allServices),
62
+ dataTestId: 'footer-products-solutions-link',
56
63
  items: [
57
64
  [
58
65
  {
59
66
  title: t('footer.counterparties.title'),
60
67
  link: buildLocalizedUrl(locale.value, urls.pk, ''),
68
+ dataTestId: 'footer-counterparties-link',
61
69
  },
62
70
  {
63
71
  title: t('footer.fines.title'),
64
- link: buildLocalizedUrl(locale.value, urls.avto, PAGES.fines.main),
72
+ link: buildLocalizedUrl(locale.value, urls.avto, PAGES.fines.fines),
73
+ dataTestId: 'footer-fines-link',
65
74
  },
66
75
  {
67
76
  title: t('footer.work.title'),
68
77
  link: urls.work + PAGES.work.vacancy,
78
+ dataTestId: 'footer-work-link',
69
79
  },
70
80
  {
71
81
  title: t('footer.analytics.title'),
72
82
  link: buildLocalizedUrl(locale.value, urls.analytics, PAGES.analytics.main),
83
+ dataTestId: 'footer-analytics-link',
73
84
  },
74
85
  {
75
86
  title: t('footer.zakupki.title'),
76
87
  link: urls.zakupki + PAGES.tender.main,
77
88
  isNew: true,
89
+ dataTestId: 'footer-purchases-link',
78
90
  },
79
91
  ],
80
92
  [
@@ -82,16 +94,19 @@ const mainLinks = computed(() => ({
82
94
  title: t('footer.compliance.title'),
83
95
  link: buildLocalizedUrl(locale.value, urls.compliance, PAGES.compliance.l),
84
96
  isNew: true,
97
+ dataTestId: 'footer-compliance-link',
85
98
  },
86
99
  {
87
100
  title: t('header.products.edo.label'),
88
101
  link: urls.edo_docs,
89
102
  isNew: true,
103
+ dataTestId: 'footer-edo-link',
90
104
  },
91
105
  {
92
106
  title: t('footer.fea.title'),
93
107
  link: buildLocalizedUrl(locale.value, urls.tnved, PAGES.fea.main),
94
108
  isNew: true,
109
+ dataTestId: 'footer-tnved-link',
95
110
  },
96
111
  ],
97
112
  ],
@@ -99,31 +114,38 @@ const mainLinks = computed(() => ({
99
114
  useful: {
100
115
  title: t('footer.useful'),
101
116
  link: buildLocalizedUrl(locale.value, urls.landing, PAGES.usefulMain),
117
+ dataTestId: 'footer-useful-link',
102
118
  items: [
103
119
  [
104
120
  {
105
121
  title: t('footer.info.api'),
106
122
  link: buildLocalizedUrl(locale.value, urls.landing, PAGES.apiDescription),
123
+ dataTestId: 'footer-api-description-link',
107
124
  },
108
125
  {
109
126
  title: t('footer.info.userAgreement'),
110
127
  link: buildLocalizedUrl(locale.value, urls.landing, PAGES.userAgreement),
128
+ dataTestId: 'footer-user-agreement-link',
111
129
  },
112
130
  {
113
131
  title: t('footer.info.privacyPolicy'),
114
132
  link: buildLocalizedUrl(locale.value, urls.landing, PAGES.privacy),
133
+ dataTestId: 'footer-privacy-policy-link',
115
134
  },
116
135
  {
117
136
  title: t('footer.info.vacancy'),
118
137
  link: buildLocalizedUrl(locale.value, urls.landing, PAGES.vacancy),
138
+ dataTestId: 'footer-vacancies-link',
119
139
  },
120
140
  {
121
141
  title: t('footer.info.counterparty'),
122
142
  link: buildLocalizedUrl(locale.value, urls.pk, PAGES.pk.counterparty),
143
+ dataTestId: 'footer-company-catalog-link',
123
144
  },
124
145
  {
125
146
  title: t('footer.info.onlineServices'),
126
147
  link: buildLocalizedUrl(locale.value, urls.pk, `${PAGES.services}/taxpayer`),
148
+ dataTestId: 'footer-online-services-link',
127
149
  },
128
150
  ],
129
151
  ],
@@ -133,24 +155,28 @@ const mainLinks = computed(() => ({
133
155
  const contactLinks = computed(() => ({
134
156
  title: t('footer.contacts.title'),
135
157
  link: buildLocalizedUrl(locale.value, urls.landing, PAGES.contacts),
158
+ dataTestId: 'footer-contacts-link',
136
159
  items: [
137
160
  {
138
161
  title: t('footer.contacts.address'),
139
162
  link: 'https://go.2gis.com/30j2f',
140
163
  startIcon: AIconLocation,
141
164
  endIcon: null,
165
+ dataTestId: 'footer-address-link',
142
166
  },
143
167
  {
144
168
  title: '+7 (747) 120 34 67',
145
169
  link: 'tel:+77471203467',
146
170
  startIcon: AIconPhone,
147
171
  endIcon: null,
172
+ dataTestId: 'footer-phone-link',
148
173
  },
149
174
  {
150
175
  title: 'info@adata.kz',
151
176
  link: 'mailto:info@adata.kz',
152
177
  startIcon: AIconMail,
153
178
  endIcon: null,
179
+ dataTestId: 'footer-email-link',
154
180
  },
155
181
  {
156
182
  title: t('footer.contacts.goToChat'),
@@ -158,6 +184,7 @@ const contactLinks = computed(() => ({
158
184
  startIcon: null,
159
185
  endIcon: AIconExpandWindow,
160
186
  class: 'font-bold',
187
+ dataTestId: 'footer-support-chat-link',
161
188
  },
162
189
  ],
163
190
  }))
@@ -166,6 +193,7 @@ const contactLinks = computed(() => ({
166
193
  <template>
167
194
  <footer
168
195
  id="footer"
196
+ data-test-id="footer-container"
169
197
  class="rounded-t-[32px] bg-secondary-500 py-4 lg:py-5 dark:bg-black"
170
198
  >
171
199
  <div class="a-container mobile-padding flex flex-col justify-between gap-8 py-5 text-white lg:flex-row">
@@ -188,6 +216,7 @@ const contactLinks = computed(() => ({
188
216
  target="_blank"
189
217
  :to="social.link"
190
218
  :aria-label="social.name"
219
+ :data-test-id="social.dataTestId"
191
220
  >
192
221
  <span class="visually-hidden"> {{ social.name }}</span>
193
222
  <component
@@ -215,6 +244,7 @@ const contactLinks = computed(() => ({
215
244
  :label="item.title"
216
245
  :link="item?.link?.replace(/\/$/, '')"
217
246
  :content="item.items"
247
+ :data-test-id="item.dataTestId"
218
248
  />
219
249
  </div>
220
250
  </div>
@@ -223,6 +253,7 @@ const contactLinks = computed(() => ({
223
253
  <nuxt-link
224
254
  class="cursor-pointer border-b-[0.5px] border-current pr-4 pb-3 text-body-md-strong"
225
255
  :to="contactLinks.link?.replace(/\/$/, '')"
256
+ :data-test-id="contactLinks.dataTestId"
226
257
  >
227
258
  {{ contactLinks.title }}
228
259
  </nuxt-link>
@@ -237,6 +268,7 @@ const contactLinks = computed(() => ({
237
268
  class="flex cursor-pointer items-center gap-2 text-body-sm-medium"
238
269
  :class="link.class"
239
270
  :to="link.link?.replace(/\/$/, '')"
271
+ :data-test-id="link.dataTestId"
240
272
  >
241
273
  <component
242
274
  :is="link.startIcon"
@@ -265,6 +297,7 @@ const contactLinks = computed(() => ({
265
297
  target="_blank"
266
298
  :to="social.link"
267
299
  :aria-label="social.name"
300
+ :data-test-id="social.dataTestId"
268
301
  >
269
302
  <component
270
303
  :is="social.icon"
@@ -0,0 +1,187 @@
1
+ <script lang="ts" setup>
2
+ import type { ProjectKeys } from '#adata-ui/components/navigation/header/types'
3
+ import HeaderAccount from '#adata-ui/components/navigation/header/HeaderAccount.vue'
4
+ import { useCurrentModule } from '#adata-ui/composables/projectState'
5
+ import DoubleChevronRight from '#adata-ui/icons/chevron/double-chevron-right.vue'
6
+ import Logo from '#adata-ui/icons/logo.vue'
7
+ import { twMerge } from '#adata-ui/utils/twMerge'
8
+ import { useElementBounding } from '@vueuse/core'
9
+ import HeaderLink from './HeaderLink.vue'
10
+
11
+ /**
12
+ * Шапка оболочки с боковым меню: ВЭД `layouts/app.vue`, Комплаенс
13
+ * `pages/main.vue`.
14
+ *
15
+ * Отдельный компонент, а не режим `a-header`: с шапкой лендинга у неё общая
16
+ * только правая группа (`header-account`), которая и вынесена. Левые зоны
17
+ * разные целиком — здесь кнопка сворачивания сайдбара, там логотип с мега-меню
18
+ * и поиском; пропы и события тоже не пересекаются.
19
+ *
20
+ * Системного баннера (`system-notification`) здесь нет намеренно: это баннер
21
+ * публичных страниц, за оболочкой он никогда не показывался. Понадобится —
22
+ * ставится потребителем над шапкой.
23
+ *
24
+ * Проектные различия — слотами: `actions` (иконки перед профилем — настройки,
25
+ * уведомления Комплаенса) и `profile-menu` (своё меню).
26
+ */
27
+ defineOptions({ name: 'AAppHeader' })
28
+
29
+ const props = withDefaults(defineProps<Props>(), {
30
+ limitRemaining: 0,
31
+ daysRemaining: 0,
32
+ maxLimit: 0,
33
+ balance: 0,
34
+ rate: 'Базовый',
35
+ module: 'pk',
36
+ collapsed: false,
37
+ peeking: false,
38
+ mobileLogo: false,
39
+ showDays: true,
40
+ showProfileOnMobile: false,
41
+ headerClass: undefined,
42
+ })
43
+
44
+ /* `toggleCollapsed` в camelCase по правилу eslint; в шаблонах потребителей —
45
+ `@toggle-collapsed`, Vue нормализует. */
46
+ const emit = defineEmits(['logout', 'toggleCollapsed'])
47
+
48
+ defineSlots<{
49
+ /** Иконки-действия между переключателями и меню профиля (настройки, уведомления). */
50
+ 'actions'?: () => any
51
+ /** Своё содержимое дропдауна профиля. `close` закрывает дропдаун. */
52
+ 'profile-menu'?: (props: { close: () => void }) => any
53
+ }>()
54
+
55
+ interface Props {
56
+ email: string | undefined
57
+ isAuthenticated: boolean
58
+ rate?: string
59
+ replenish?: string
60
+ daysRemaining?: number
61
+ limitRemaining?: number
62
+ maxLimit?: number
63
+ balance?: number
64
+ module?: ProjectKeys
65
+ oldVersion?: string
66
+ /** Состояние бокового меню: свёрнуто ли. */
67
+ collapsed?: boolean
68
+ /** Свёрнутое меню выехало поверх контента — кнопка означает «закрепить». */
69
+ peeking?: boolean
70
+ /** Компактный знак логотипа ниже `lg` — когда сайдбара на телефоне нет. */
71
+ mobileLogo?: boolean
72
+ /** Блок «остаток дней» в счётчике лимита. */
73
+ showDays?: boolean
74
+ /** Кнопка профиля ниже `lg`. По умолчанию скрыта: профиль живёт в нижней панели. */
75
+ showProfileOnMobile?: boolean
76
+ /**
77
+ * Классы поверх `<header>`, мержатся через twMerge. Высота задана по
78
+ * брейкпоинтам (`h-[62px] lg:h-[66px]`), поэтому свою задавать тоже с
79
+ * брейкпоинтом: `header-class="h-18 lg:h-18"`.
80
+ */
81
+ headerClass?: string
82
+ }
83
+
84
+ const { t } = useI18n()
85
+
86
+ useCurrentModule().value = props.module
87
+
88
+ const headerRef = ref<HTMLElement | null>(null)
89
+ const { bottom: headerBottom } = useElementBounding(headerRef)
90
+
91
+ /* На телефоне шапка оболочки ниже: 62px против 66 — четыре пикселя заметны,
92
+ когда весь экран 812. На широком экране 66 — с ней совпадает строка логотипа
93
+ бокового меню, и линия под ними идёт одной. */
94
+ const headerClasses = computed(() => twMerge(
95
+ 'relative h-[62px] shrink-0 border-b border-default bg-main/10 backdrop-blur-lg backdrop-saturate-140 lg:h-[66px]',
96
+ props.headerClass,
97
+ ))
98
+
99
+ /*
100
+ Подпись кнопки сворачивания. Пока меню выехало поверх контента, оно всё ещё
101
+ считается свёрнутым, поэтому кнопка означает «закрепить», а не «развернуть».
102
+ */
103
+ const collapseLabel = computed(() => {
104
+ if (!props.collapsed) return t('header.shell.collapse')
105
+ return props.peeking ? t('header.shell.pin') : t('header.shell.expand')
106
+ })
107
+ </script>
108
+
109
+ <template>
110
+ <header
111
+ ref="headerRef"
112
+ :class="headerClasses"
113
+ data-variant="app"
114
+ >
115
+ <div class="flex size-full items-center justify-between gap-2 px-3 sm:px-4">
116
+ <nav class="flex min-w-0 grow items-center gap-2">
117
+ <!-- Ниже lg бокового меню может не быть — тогда логотип переезжает в
118
+ шапку знаком, чтобы не пропасть совсем. Полное начертание съедало
119
+ бы треть строки, а место нужно кнопкам. -->
120
+ <nuxt-link-locale
121
+ v-if="mobileLogo"
122
+ to="/"
123
+ data-test-id="header-logo-compact"
124
+ :title="t('header.shell.home')"
125
+ :aria-label="t('header.shell.home')"
126
+ class="mr-1 flex shrink-0 items-center text-primary transition-opacity hover:opacity-80 lg:hidden"
127
+ >
128
+ <logo />
129
+ </nuxt-link-locale>
130
+
131
+ <!-- Кнопка сворачивания живёт в шапке, а не в самом меню: она не должна
132
+ уезжать вместе с панелью, которой управляет. Ниже lg панели нет —
133
+ и кнопки тоже. -->
134
+ <button
135
+ type="button"
136
+ data-test-id="collapse-toggle"
137
+ :aria-label="collapseLabel"
138
+ :aria-expanded="!collapsed"
139
+ :title="collapseLabel"
140
+ class="hidden size-9 shrink-0 cursor-pointer items-center justify-center rounded-md text-icon-default transition-colors hover:bg-action-secondary-hover hover:text-icon-strong lg:flex"
141
+ @click="emit('toggleCollapsed')"
142
+ >
143
+ <!-- Двойной шеврон смотрит в сторону, куда уедет панель. -->
144
+ <double-chevron-right
145
+ class="size-5 transition-transform"
146
+ :class="{ 'rotate-180': !collapsed }"
147
+ />
148
+ </button>
149
+
150
+ <!-- Верхняя навигация — только с xl: на планшете ей не хватает места
151
+ рядом со счётчиками. -->
152
+ <div class="hidden xl:block">
153
+ <header-link :menu-top="headerBottom || 66" />
154
+ </div>
155
+ </nav>
156
+
157
+ <header-account
158
+ divider
159
+ :is-authenticated="isAuthenticated"
160
+ :email="email"
161
+ :rate="rate"
162
+ :replenish="replenish"
163
+ :balance="balance"
164
+ :days-remaining="daysRemaining"
165
+ :limit-remaining="limitRemaining"
166
+ :max-limit="maxLimit"
167
+ :show-days="showDays"
168
+ :show-profile-on-mobile="showProfileOnMobile"
169
+ :old-version="oldVersion"
170
+ @logout="emit('logout')"
171
+ >
172
+ <template #actions>
173
+ <slot name="actions" />
174
+ </template>
175
+ <template
176
+ v-if="$slots['profile-menu']"
177
+ #profile-menu="{ close }"
178
+ >
179
+ <slot
180
+ name="profile-menu"
181
+ :close="close"
182
+ />
183
+ </template>
184
+ </header-account>
185
+ </div>
186
+ </header>
187
+ </template>
@@ -0,0 +1,159 @@
1
+ <script lang="ts" setup>
2
+ import AColorMode from '#adata-ui/components/features/color-mode/AColorMode.client.vue'
3
+ import ADropdownV2 from '#adata-ui/components/features/dropdown/ADropdownV2.vue'
4
+ import LangSwitcher from '#adata-ui/components/features/lang-switcher/lang-switcher.vue'
5
+ import AChangeVersion from '#adata-ui/components/features/сhange-version/AChangeVersion.vue'
6
+ import HeaderUsage from '#adata-ui/components/navigation/header/HeaderUsage.vue'
7
+ import ProfileMenu from '#adata-ui/components/navigation/header/ProfileMenu.vue'
8
+
9
+ /**
10
+ * Правая группа шапки: счётчики тарифа, переключатели темы и языка, действия
11
+ * проекта и меню профиля.
12
+ *
13
+ * Отдельный компонент, потому что это единственное, что у шапки лендинга
14
+ * (`a-header`) и шапки оболочки (`a-app-header`) совпадает — и одновременно
15
+ * самое живое место: тарифы, лимиты, авторизация. Левые зоны у них общего не
16
+ * имеют вовсе, поэтому делить шапку флагом `variant` смысла не было.
17
+ */
18
+ defineOptions({ name: 'HeaderAccount' })
19
+
20
+ withDefaults(defineProps<Props>(), {
21
+ limitRemaining: 0,
22
+ daysRemaining: 0,
23
+ maxLimit: 0,
24
+ balance: 0,
25
+ rate: 'Базовый',
26
+ showDays: true,
27
+ showLang: true,
28
+ showLogIn: false,
29
+ showProfileOnMobile: false,
30
+ divider: false,
31
+ })
32
+
33
+ const emit = defineEmits(['logout', 'login'])
34
+
35
+ defineSlots<{
36
+ /** Иконки-действия между переключателями и меню профиля (настройки, уведомления). */
37
+ 'actions'?: () => any
38
+ /** Своё содержимое дропдауна профиля. `close` закрывает дропдаун. */
39
+ 'profile-menu'?: (props: { close: () => void }) => any
40
+ }>()
41
+
42
+ interface Props {
43
+ isAuthenticated: boolean
44
+ email: string | undefined
45
+ rate?: string
46
+ replenish?: string
47
+ daysRemaining?: number
48
+ limitRemaining?: number
49
+ maxLimit?: number
50
+ balance?: number
51
+ /** Блок «остаток дней» в счётчике лимита. */
52
+ showDays?: boolean
53
+ /** Переключатель языка. */
54
+ showLang?: boolean
55
+ /** Кнопка входа в конце группы — там, где страницы открыты неавторизованным. */
56
+ showLogIn?: boolean
57
+ /** Кнопка профиля ниже `lg`. По умолчанию скрыта: профиль живёт в нижней панели. */
58
+ showProfileOnMobile?: boolean
59
+ /** Вертикальная черта между счётчиками и переключателями. */
60
+ divider?: boolean
61
+ /** Ссылка на старую версию проекта — кнопка `a-change-version`. */
62
+ oldVersion?: string
63
+ }
64
+ </script>
65
+
66
+ <template>
67
+ <!-- Адаптив — классами на элементах, а не двумя копиями группы: слот
68
+ `actions` должен монтироваться один раз (в нём у потребителей живёт
69
+ polling уведомлений). -->
70
+ <div class="flex shrink-0 items-center gap-1 text-primary lg:gap-4">
71
+ <template v-if="isAuthenticated">
72
+ <div class="hidden md:flex">
73
+ <header-usage
74
+ :limit-remaining="limitRemaining"
75
+ :max-limit="maxLimit"
76
+ :days-remaining="daysRemaining"
77
+ :show-days="showDays"
78
+ />
79
+ </div>
80
+ <!-- Разделитель отделяет счётчики от переключателей: без него ряд
81
+ читается одной длинной строкой. -->
82
+ <span
83
+ v-if="divider"
84
+ class="hidden h-4 w-px shrink-0 bg-(--border-color-default) md:block"
85
+ aria-hidden="true"
86
+ />
87
+ </template>
88
+
89
+ <div class="flex items-center gap-1 lg:gap-2">
90
+ <!-- Обёртка, а не класс на компоненте: у его корня свой `flex`,
91
+ и `hidden` с ним конфликтовал бы по порядку в CSS. -->
92
+ <div
93
+ v-if="oldVersion"
94
+ class="hidden lg:block"
95
+ >
96
+ <a-change-version :url="oldVersion" />
97
+ </div>
98
+ <a-color-mode />
99
+ <template v-if="showLang">
100
+ <lang-switcher
101
+ class="lg:hidden"
102
+ is-mobile
103
+ />
104
+ <lang-switcher class="hidden lg:block" />
105
+ </template>
106
+ </div>
107
+
108
+ <slot name="actions" />
109
+
110
+ <a-dropdown-v2
111
+ v-show="isAuthenticated"
112
+ placement="bottom-end"
113
+ >
114
+ <template #default="{ toggle, isOpen }">
115
+ <button
116
+ type="button"
117
+ class="h-8 cursor-pointer items-center gap-1.5 rounded-full px-3 text-body-md-medium transition-colors duration-150 hover:bg-action-secondary-hover focus-visible:ring-2 focus-visible:ring-brand-500/50 focus-visible:ring-offset-1 focus-visible:outline-hidden"
118
+ :class="[
119
+ showProfileOnMobile ? 'inline-flex' : 'hidden lg:inline-flex',
120
+ isOpen ? 'bg-action-secondary-hover text-primary' : 'text-primary',
121
+ ]"
122
+ data-test-id="header-email-text"
123
+ aria-haspopup="menu"
124
+ :aria-expanded="isOpen"
125
+ :aria-label="email"
126
+ @click.stop="toggle"
127
+ >
128
+ <span class="max-w-35 truncate lg:max-w-none">{{ email }}</span>
129
+ <a-icon-chevron-down
130
+ class="shrink-0 transition-transform duration-200"
131
+ :class="{ 'rotate-180': isOpen }"
132
+ />
133
+ </button>
134
+ </template>
135
+ <template #content="{ close }">
136
+ <slot
137
+ name="profile-menu"
138
+ :close="close"
139
+ >
140
+ <profile-menu
141
+ :email="email"
142
+ :balance="balance"
143
+ :days-remaining="daysRemaining"
144
+ :limit-remaining="limitRemaining"
145
+ :rate="rate"
146
+ :replenish="replenish"
147
+ @logout="emit('logout')"
148
+ />
149
+ </slot>
150
+ </template>
151
+ </a-dropdown-v2>
152
+
153
+ <!-- Кнопка сама скрыта ниже lg. -->
154
+ <a-button-login
155
+ v-show="showLogIn && !isAuthenticated"
156
+ @click="emit('login')"
157
+ />
158
+ </div>
159
+ </template>
@@ -8,12 +8,19 @@ const props = withDefaults(defineProps<Props>(), {
8
8
  limitRemaining: 0,
9
9
  maxLimit: 300,
10
10
  daysRemaining: 0,
11
+ showDays: true,
11
12
  })
12
13
 
13
14
  interface Props {
14
15
  limitRemaining?: number
15
16
  maxLimit?: number
16
17
  daysRemaining?: number
18
+ /**
19
+ * Блок «остаток дней тарифа». По умолчанию виден (лендинги); выключается в
20
+ * оболочке Комплаенса, где дни не показывают. Аддитивно — default сохраняет
21
+ * прежнее поведение `a-header`.
22
+ */
23
+ showDays?: boolean
17
24
  }
18
25
 
19
26
  const percent = computed(() => {
@@ -99,29 +106,31 @@ const valueColorClass = computed(() => {
99
106
  </template>
100
107
  </a-tooltip-v2>
101
108
 
102
- <span class="h-4 w-px shrink-0 bg-(--border-color-default)" />
109
+ <template v-if="showDays">
110
+ <span class="h-4 w-px shrink-0 bg-(--border-color-default)" />
103
111
 
104
- <a-tooltip-v2
105
- class="shrink-0"
106
- placement="bottom"
107
- arrow
108
- :z-index="10001"
109
- content-class="py-0.5"
110
- >
111
- <div
112
- class="flex shrink-0 items-center gap-1.5"
113
- data-test-id="header-usage-days"
112
+ <a-tooltip-v2
113
+ class="shrink-0"
114
+ placement="bottom"
115
+ arrow
116
+ :z-index="10001"
117
+ content-class="py-0.5"
114
118
  >
115
- <calendar class="size-4 shrink-0 text-quaternary" />
116
- <span class="text-body-md font-bold tabular-nums text-primary">{{ daysRemaining }}</span>
117
- </div>
118
-
119
- <template #content>
120
- <span class="whitespace-nowrap text-body-sm leading-none text-primary">
121
- Остаток дней: {{ daysRemaining }}
122
- </span>
123
- </template>
124
- </a-tooltip-v2>
119
+ <div
120
+ class="flex shrink-0 items-center gap-1.5"
121
+ data-test-id="header-usage-days"
122
+ >
123
+ <calendar class="size-4 shrink-0 text-quaternary" />
124
+ <span class="text-body-md font-bold tabular-nums text-primary">{{ daysRemaining }}</span>
125
+ </div>
126
+
127
+ <template #content>
128
+ <span class="whitespace-nowrap text-body-sm leading-none text-primary">
129
+ Остаток дней: {{ daysRemaining }}
130
+ </span>
131
+ </template>
132
+ </a-tooltip-v2>
133
+ </template>
125
134
  </div>
126
135
  </client-only>
127
136
  </template>
@@ -0,0 +1,110 @@
1
+ <script lang="ts" setup>
2
+ import AdataLogo from '#adata-ui/icons/adata-logo.vue'
3
+ import Logo from '#adata-ui/icons/logo.vue'
4
+ import { twMerge } from '#adata-ui/utils/twMerge'
5
+
6
+ /**
7
+ * Каркас-оболочка бокового меню оболочек с сайдбаром (ВЭД `layouts/app.vue`,
8
+ * Комплаенс `pages/main.vue`).
9
+ *
10
+ * Намеренно тонкий: общего у сайдбаров ВЭД и Комплаенса — только хром (рамка
11
+ * `<aside>`, строка логотипа, поворот знак/начертание по свёрнутости). Данные,
12
+ * источник схлопывания (cookie у Комплаенса, проп у ВЭД), способ разворота
13
+ * (resize у Комплаенса, выезд-peek у ВЭД) и рендер пунктов у каждого свои и
14
+ * живут в проекте — сюда они приезжают содержимым слота по умолчанию.
15
+ *
16
+ * На корне НЕТ `overflow`, `transform`, `filter`, `contain`: сайдбар ВЭД в
17
+ * свёрнутом виде выезжает вложенным `<nav>` за пределы 80px, и любой из этих
18
+ * стилей на предке обрезал бы панель. Ширину, высоту, переход и `display`
19
+ * (у ВЭД корень `hidden lg:flex`) задаёт потребитель пропом `sidebar-class` —
20
+ * он мержится через twMerge, поэтому его `hidden` корректно перебивает базовый
21
+ * `flex`. `style` и `data-*` долетают до корня обычным наследованием атрибутов.
22
+ */
23
+ defineOptions({ name: 'AAppSidebar' })
24
+
25
+ const props = withDefaults(defineProps<Props>(), {
26
+ collapsed: false,
27
+ logoTo: '/',
28
+ logoLocalized: false,
29
+ border: 'full',
30
+ sidebarClass: undefined,
31
+ logoRowClass: undefined,
32
+ logoLinkClass: undefined,
33
+ logoTitle: undefined,
34
+ })
35
+
36
+ defineSlots<{
37
+ /** Знак в строке логотипа. По умолчанию — `logo` (свёрнуто) / `adata-logo`. */
38
+ 'logo'?: (props: { collapsed: boolean }) => any
39
+ /** Полная замена строки логотипа, если проектная разметка не укладывается в пропы. */
40
+ 'logo-row'?: () => any
41
+ /** Содержимое меню: `<nav>` со своими пунктами (у ВЭД — с выездом, у AC — с resize). */
42
+ 'default'?: () => any
43
+ }>()
44
+
45
+ interface Props {
46
+ /** Свёрнут ли сайдбар — управляет знаком логотипа (знак/начертание). */
47
+ collapsed?: boolean
48
+ /** Куда ведёт логотип. */
49
+ logoTo?: string
50
+ /** Локализованная ссылка (`nuxt-link-locale`) вместо обычной (`nuxt-link`). */
51
+ logoLocalized?: boolean
52
+ /** Рамка: `full` — по кругу (Комплаенс), `right` — только справа (ВЭД). */
53
+ border?: 'full' | 'right'
54
+ /** Классы корня (ширина, высота, `display`, переход) — мержатся через twMerge. */
55
+ sidebarClass?: string | (string | false | null | undefined)[]
56
+ /** Классы строки логотипа: высота, видимость, паддинги, выравнивание. */
57
+ logoRowClass?: string
58
+ /** Классы ссылки-логотипа (например, `m-5` у Комплаенса). */
59
+ logoLinkClass?: string
60
+ /** `title`/`aria-label` ссылки-логотипа; при наличии добавляет `data-home-link`. */
61
+ logoTitle?: string
62
+ }
63
+
64
+ const asideClass = computed(() => twMerge(
65
+ 'relative flex flex-col bg-main',
66
+ props.border === 'right' ? 'border-r border-default' : 'border border-default',
67
+ props.sidebarClass,
68
+ ))
69
+
70
+ const logoRowClasses = computed(() => twMerge(
71
+ 'shrink-0 overflow-hidden border-b border-default',
72
+ props.logoRowClass,
73
+ ))
74
+ </script>
75
+
76
+ <template>
77
+ <aside :class="asideClass">
78
+ <slot name="logo-row">
79
+ <div :class="logoRowClasses">
80
+ <nuxt-link-locale
81
+ v-if="logoLocalized"
82
+ :to="logoTo"
83
+ :class="logoLinkClass"
84
+ :data-home-link="logoTitle ? '' : undefined"
85
+ :title="logoTitle"
86
+ :aria-label="logoTitle"
87
+ >
88
+ <slot name="logo" :collapsed="collapsed">
89
+ <logo v-if="collapsed" />
90
+ <adata-logo v-else />
91
+ </slot>
92
+ </nuxt-link-locale>
93
+ <nuxt-link
94
+ v-else
95
+ :to="logoTo"
96
+ :class="logoLinkClass"
97
+ :title="logoTitle"
98
+ :aria-label="logoTitle"
99
+ >
100
+ <slot name="logo" :collapsed="collapsed">
101
+ <logo v-if="collapsed" />
102
+ <adata-logo v-else />
103
+ </slot>
104
+ </nuxt-link>
105
+ </div>
106
+ </slot>
107
+
108
+ <slot />
109
+ </aside>
110
+ </template>
@@ -120,7 +120,7 @@ export function useHeaderNavigationLinks() {
120
120
  {
121
121
  key: 'fines',
122
122
  name: t('header.products.fines.label'),
123
- link: buildLocalizedUrl(locale, urls.avto, PAGES.fines.main),
123
+ link: buildLocalizedUrl(locale, urls.avto, PAGES.fines.fines),
124
124
  icon: AIconCarSide,
125
125
  data_attribute: 'navigation-menu-fines-block',
126
126
  items: [
@@ -128,13 +128,13 @@ export function useHeaderNavigationLinks() {
128
128
  title: t('header.products.fines.items.fines.title'),
129
129
  subtitle: t('header.products.fines.items.fines.subtitle'),
130
130
  icon: IconCheckCircle,
131
- to: buildLocalizedUrl(locale, urls.avto, PAGES.fines.main),
131
+ to: buildLocalizedUrl(locale, urls.avto, PAGES.fines.fines),
132
132
  },
133
133
  {
134
134
  title: t('header.products.fines.items.auto.title'),
135
135
  subtitle: t('header.products.fines.items.auto.subtitle'),
136
136
  icon: IconCar,
137
- to: buildLocalizedUrl(locale, urls.avto, PAGES.fines.avto),
137
+ to: buildLocalizedUrl(locale, urls.avto, PAGES.fines.main),
138
138
  },
139
139
  // {
140
140
  // title: t('header.products.fines.items.wholesaleAuto.title'),
package/lang/en.ts CHANGED
@@ -28,6 +28,12 @@ const EnLocale: RuLocale = {
28
28
  requestLimit: 'Daily request limit',
29
29
  daysLeft: 'Days left',
30
30
  },
31
+ shell: {
32
+ collapse: 'Collapse menu',
33
+ expand: 'Expand menu',
34
+ pin: 'Pin menu',
35
+ home: 'Home',
36
+ },
31
37
  usage: {
32
38
  requests: 'Requests',
33
39
  days: 'days',
package/lang/kk.ts CHANGED
@@ -28,6 +28,12 @@ const KkLocale: RuLocale = {
28
28
  requestLimit: 'Күнделікті сұраныс лимиті',
29
29
  daysLeft: 'Қалған күндер',
30
30
  },
31
+ shell: {
32
+ collapse: 'Мәзірді жию',
33
+ expand: 'Мәзірді жаю',
34
+ pin: 'Мәзірді бекіту',
35
+ home: 'Басты бетке',
36
+ },
31
37
  usage: {
32
38
  requests: 'Сұраныстар',
33
39
  days: 'күн',
package/lang/ru.ts CHANGED
@@ -24,6 +24,12 @@ const RuLocale = {
24
24
  requestLimit: 'Cуточный лимит запросов',
25
25
  daysLeft: 'Остаток дней',
26
26
  },
27
+ shell: {
28
+ collapse: 'Свернуть меню',
29
+ expand: 'Развернуть меню',
30
+ pin: 'Закрепить меню',
31
+ home: 'На главную',
32
+ },
27
33
  usage: {
28
34
  requests: 'Запросы',
29
35
  days: 'дн.',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adata-ui",
3
3
  "type": "module",
4
- "version": "2.1.41-beta.23",
4
+ "version": "2.1.41-beta.24",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "dev": "nuxi dev .playground",
@@ -21,7 +21,7 @@ export const PAGES = {
21
21
  reports: '/profile/reports',
22
22
  browsingHistory: '/profile/view-history',
23
23
  myGroups: '/profile/my-groups',
24
- notes: '/profile/notes'
24
+ notes: '/profile/notes',
25
25
  },
26
26
  pk: {
27
27
  main: '/',
@@ -32,12 +32,12 @@ export const PAGES = {
32
32
  sanctions: '/sanctions',
33
33
  employees: '/employees-check',
34
34
  counterparty: '/counterparty',
35
- unload: '/unload'
35
+ unload: '/unload',
36
36
  },
37
37
  work: {
38
38
  main: '/',
39
39
  summary: '/summary',
40
- vacancy: '/vacancy'
40
+ vacancy: '/vacancy',
41
41
  },
42
42
  tender: {
43
43
  customers: '/main/analytics/customers',
@@ -49,12 +49,13 @@ export const PAGES = {
49
49
  akimat: '/main/eoz',
50
50
  main: '/',
51
51
  contracts: '/main/contracts',
52
- plans: '/main/plans'
52
+ plans: '/main/plans',
53
53
  },
54
54
  fines: {
55
- main: '/?check_fines=true',
55
+ main: '/',
56
56
  avto: '/check-car',
57
- bulk: '/mass-check'
57
+ fines: '/fines',
58
+ bulk: '/mass-check',
58
59
  },
59
60
  analytics: {
60
61
  main: '/',
@@ -81,7 +82,7 @@ export const PAGES = {
81
82
  hotline: '/hotline',
82
83
  sanctions: '/sanctions',
83
84
  suppliers: '/dossier/suppliers',
84
- sb: '/sb'
85
+ sb: '/sb',
85
86
  },
86
87
  edo: {
87
88
  l: '/',
@@ -90,5 +91,5 @@ export const PAGES = {
90
91
  docRisk: '/create?step=2&source=import&postImportAction=risks',
91
92
  docCompare: '/create?step=2&source=import',
92
93
  docAi: '/create?step=3&source=blank',
93
- }
94
+ },
94
95
  }