@it-enterprise/forcebpm-ui-kit 1.0.2-beta.25 → 1.0.2-beta.27

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.
package/index.js CHANGED
@@ -25,7 +25,7 @@ export { default as FUserRoles } from './src/FUserRoles.vue'
25
25
  export { default as FDatePicker } from './src/f-date-picker/FDatePicker.vue'
26
26
  export { default as FMenuDatePicker } from './src/f-date-picker/FMenuDatePicker.vue'
27
27
  export { default as FTextFieldDate } from './src/f-date-picker/FTextFieldDate.vue'
28
- // export { default as FUserGroupPicker } from './src/f-user-group-picker/FUserGroupPicker.vue'
28
+ export { default as FUserGroupPicker } from './src/FUserGroupPicker.vue'
29
29
 
30
30
  // Utils
31
31
  export { hexToRGBA, createRadialGradient } from './src/utils/color.js'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@it-enterprise/forcebpm-ui-kit",
4
- "version": "1.0.2-beta.25",
4
+ "version": "1.0.2-beta.27",
5
5
  "description": "FBPM UI Kit",
6
6
  "author": "it-enterprise",
7
7
  "license": "MIT",
package/src/FDialog.vue CHANGED
@@ -90,6 +90,8 @@ watch(modelValue, val => {
90
90
  if (!val) {
91
91
  search.value = null
92
92
  emit('close')
93
+ } else {
94
+ emit('open')
93
95
  }
94
96
  })
95
97
  </script>
@@ -121,6 +121,9 @@ const keydownEsc = () => {
121
121
  height: 30px;
122
122
  width: 30px;
123
123
  transition: all 0.2s ease-in-out;
124
+ .v-input__control {
125
+ background-color: transparent;
126
+ }
124
127
  &.v-input--focused {
125
128
  .v-field__prepend-inner {
126
129
  margin-left: 0px;
@@ -0,0 +1,634 @@
1
+ <script setup>
2
+ // FUserGroupPicker
3
+ import { ref, computed, watch } from 'vue'
4
+ import { useI18n } from 'vue-i18n'
5
+ import { useTheme } from 'vuetify'
6
+ import { colorCollection, hexToRGBA } from './utils/color'
7
+
8
+ const { t } = useI18n()
9
+ const theme = useTheme()
10
+
11
+ const props = defineProps({
12
+ items: {
13
+ type: Array,
14
+ required: true,
15
+ validator: (value, props) => {
16
+ const isValid = value.every(item => {
17
+ const hasIdentityId =
18
+ item[props.itemValue] !== undefined && (typeof item[props.itemValue] === 'string' || typeof item[props.itemValue] === 'number')
19
+ const hasName = typeof item.name === 'string'
20
+ const isUserOrGroup = item.type === 'USER' || item.type === 'GROUP' || item.groupId !== undefined
21
+ return hasIdentityId && hasName && isUserOrGroup
22
+ })
23
+ if (!isValid) {
24
+ console.warn('FUserGroupPicker: Each item should have identityId (string or number), name (string) and type "USER" or "GROUP" (or groupId)')
25
+ }
26
+ return isValid
27
+ }
28
+ },
29
+ itemValue: {
30
+ type: String,
31
+ default: 'identityId',
32
+ validator: (value, props) => {
33
+ const hasGroups = props.items?.some(item => item.type === 'GROUP' || item.groupId)
34
+ if (hasGroups && value === 'userId') {
35
+ console.warn('FUserGroupPicker: itemValue cannot be "userId" when items contain groups. Use "identityId" instead.')
36
+ return false
37
+ }
38
+ if (!['identityId', 'userId'].includes(value)) {
39
+ console.warn('FUserGroupPicker: itemValue should be one of identityId, userId')
40
+ return false
41
+ }
42
+ return true
43
+ }
44
+ },
45
+ // todo when there will be a connection in ForceBPMTasks
46
+ executorLists: {
47
+ type: Array,
48
+ default: () => []
49
+ },
50
+ multiple: {
51
+ type: Boolean,
52
+ default: false
53
+ },
54
+ returnObject: {
55
+ type: Boolean,
56
+ default: false
57
+ },
58
+ groupSelection: {
59
+ type: Boolean,
60
+ default: false
61
+ }
62
+ })
63
+
64
+ const state = defineModel('state', {
65
+ type: Boolean,
66
+ default: false
67
+ })
68
+
69
+ const modelValue = defineModel('modelValue', {
70
+ type: [Array, Object, String, Number],
71
+ default: () => []
72
+ })
73
+
74
+ const search = ref('')
75
+ const selectedMenuId = ref(null)
76
+ const tab = ref('USERS_BY_GROUP')
77
+ const calculatedHeight = Math.floor(window.innerHeight * 0.1) // 10vh rounded down
78
+
79
+ // Tabs for header
80
+ const tabs = computed(() => {
81
+ const baseTabs = [
82
+ { value: 'USERS_BY_GROUP', text: t('user.usersByGroup') },
83
+ { value: 'USERS_BY_POSITION', text: t('user.usersByPosition') }
84
+ ]
85
+ if (props.executorLists.length > 0) {
86
+ baseTabs.push({ value: 'EXECUTOR_LISTS', text: t('user.executorLists') })
87
+ }
88
+ return baseTabs
89
+ })
90
+
91
+ // Map for quick access by identityId - O(1)
92
+ const itemsMap = computed(() => {
93
+ const map = new Map()
94
+ for (const item of props.items) {
95
+ map.set(item.identityId, item)
96
+ }
97
+ return map
98
+ })
99
+
100
+ // Map userId -> identityId for executor lists lookup
101
+ const userIdMap = computed(() => {
102
+ const map = new Map()
103
+ for (const item of props.items) {
104
+ if (item.userId !== undefined) {
105
+ map.set(item.userId, item.identityId)
106
+ }
107
+ }
108
+ return map
109
+ })
110
+
111
+ // Unique groups from items (extracted from item.groups)
112
+ const uniqueGroups = computed(() => {
113
+ const groupsMap = new Map()
114
+ for (const item of props.items) {
115
+ if (item.groups?.length) {
116
+ for (const group of item.groups) {
117
+ if (!groupsMap.has(group.identityId)) {
118
+ groupsMap.set(group.identityId, { ...group, id: group.identityId })
119
+ }
120
+ }
121
+ }
122
+ }
123
+ return Array.from(groupsMap.values())
124
+ })
125
+
126
+ // Unique positions from items
127
+ const uniquePositions = computed(() => {
128
+ const positionsMap = new Map()
129
+ for (const item of props.items) {
130
+ if (item.positionCode && !positionsMap.has(item.positionCode)) {
131
+ positionsMap.set(item.positionCode, {
132
+ id: item.positionCode,
133
+ title: item.positionTitle
134
+ })
135
+ }
136
+ }
137
+ return Array.from(positionsMap.values())
138
+ })
139
+
140
+ // Index: groupId -> Set<itemValue> for quick filtering
141
+ const groupIndex = computed(() => {
142
+ const index = new Map()
143
+ for (const item of props.items) {
144
+ if (item.groups?.length) {
145
+ for (const group of item.groups) {
146
+ if (!index.has(group.identityId)) {
147
+ index.set(group.identityId, new Set())
148
+ }
149
+ index.get(group.identityId).add(item.identityId)
150
+ }
151
+ }
152
+ }
153
+ return index
154
+ })
155
+
156
+ // Index: positionCode -> Set<itemValue>
157
+ const positionIndex = computed(() => {
158
+ const index = new Map()
159
+ for (const item of props.items) {
160
+ if (item.positionCode) {
161
+ if (!index.has(item.positionCode)) {
162
+ index.set(item.positionCode, new Set())
163
+ }
164
+ index.get(item.positionCode).add(item.identityId)
165
+ }
166
+ }
167
+ return index
168
+ })
169
+
170
+ // Index: executorListId -> Set<identityId>
171
+ const executorIndex = computed(() => {
172
+ const index = new Map()
173
+ for (const list of props.executorLists) {
174
+ const userIds = new Set()
175
+ for (const userId of list.usersIds || []) {
176
+ const identityId = userIdMap.value.get(userId)
177
+ if (identityId) {
178
+ userIds.add(identityId)
179
+ }
180
+ }
181
+ index.set(list.id, userIds)
182
+ }
183
+ return index
184
+ })
185
+
186
+ // Set selected items - O(1) for checking
187
+ const selectedIds = ref(new Set())
188
+
189
+ // Set selected groups (when groupSelection = true)
190
+ const selectedGroupIds = ref(new Set())
191
+
192
+ const menuItems = computed(() => {
193
+ const color = idx => {
194
+ return hexToRGBA(colorCollection[theme.global.name.value === 'light' ? 'light' : 'dark'][idx % colorCollection.light.length]).slice(5, -3) // rgba(255, 0, 0, 0.2) -> 255, 0, 0
195
+ }
196
+
197
+ switch (tab.value) {
198
+ case 'USERS_BY_GROUP':
199
+ return uniqueGroups.value.map((g, idx) => ({
200
+ id: g.identityId,
201
+ name: g.name,
202
+ color: color(idx)
203
+ }))
204
+ case 'USERS_BY_POSITION':
205
+ return uniquePositions.value.map((p, idx) => ({
206
+ id: p.id,
207
+ name: p.title,
208
+ color: color(idx)
209
+ }))
210
+ case 'EXECUTOR_LISTS':
211
+ return props.executorLists.map((list, idx) => ({
212
+ id: list.id,
213
+ name: list.name,
214
+ color: color(idx)
215
+ }))
216
+ default:
217
+ return []
218
+ }
219
+ })
220
+
221
+ const titleByMenu = computed(() => {
222
+ const currentMenu = menuItems.value.find(i => i.id === selectedMenuId.value)
223
+ return currentMenu?.name || currentMenu?.id || (props.groupSelection ? t('user.allUsersAndGroups') : t('user.allUsers'))
224
+ })
225
+
226
+ const filteredItems = computed(() => {
227
+ let resultIds = null
228
+
229
+ // Step 1: Get relevant item IDs based on selected menu
230
+ if (selectedMenuId.value) {
231
+ switch (tab.value) {
232
+ case 'USERS_BY_GROUP':
233
+ resultIds = groupIndex.value.get(selectedMenuId.value)
234
+ break
235
+ case 'USERS_BY_POSITION':
236
+ resultIds = positionIndex.value.get(selectedMenuId.value)
237
+ break
238
+ case 'EXECUTOR_LISTS':
239
+ resultIds = executorIndex.value.get(selectedMenuId.value)
240
+ break
241
+ }
242
+ }
243
+
244
+ // Step 2: Map IDs to items (if resultIds is null, we take all items)
245
+ let result
246
+ if (resultIds) {
247
+ result = Array.from(resultIds, id => itemsMap.value.get(id)).filter(Boolean)
248
+ } else {
249
+ result = props.items
250
+ }
251
+
252
+ // Step 3: Apply search filter
253
+ if (search.value) {
254
+ result = result.filter(item => item.name?.toLowerCase().includes(search.value.toLowerCase()))
255
+ }
256
+
257
+ // Step 4: If groupSelection is enabled, mark items as disabled if they belong to a selected group
258
+ if (props.groupSelection && selectedGroupIds.value.size > 0) {
259
+ result = result.map(item => {
260
+ const isDisabledByGroup = item.groups?.some(g => selectedGroupIds.value.has(g.identityId))
261
+ return isDisabledByGroup ? { ...item, disabled: true } : item
262
+ })
263
+ }
264
+
265
+ return result
266
+ })
267
+
268
+ // Selected items (groups + users)
269
+ const selectedItems = computed(() => {
270
+ // Users — filter out those covered by a selected group
271
+ const users = Array.from(selectedIds.value, id => itemsMap.value.get(id))
272
+ .filter(Boolean)
273
+ .filter(user => {
274
+ if (props.groupSelection && selectedGroupIds.value.size > 0 && user.groups?.length) {
275
+ return !user.groups.some(g => selectedGroupIds.value.has(g.identityId))
276
+ }
277
+ return true
278
+ })
279
+
280
+ // Groups — with selectAll flag for removeSelected logic
281
+ const groups = Array.from(selectedGroupIds.value, groupId => {
282
+ const group = uniqueGroups.value.find(g => g.identityId === groupId)
283
+ if (group) {
284
+ return {
285
+ ...group,
286
+ type: 'GROUP',
287
+ selectAll: true
288
+ }
289
+ }
290
+ return null
291
+ }).filter(Boolean)
292
+
293
+ const result = [...groups, ...users]
294
+
295
+ if (search.value) {
296
+ return result.filter(item => item.name?.toLowerCase().includes(search.value.toLowerCase()))
297
+ }
298
+
299
+ return result
300
+ })
301
+
302
+ const isSelected = item => {
303
+ // Check if user is individually selected
304
+ if (selectedIds.value.has(item.identityId)) return true
305
+
306
+ // Check if user is selected via group
307
+ if (props.groupSelection && item.groups?.some(g => selectedGroupIds.value.has(g.identityId))) {
308
+ return true
309
+ }
310
+
311
+ return false
312
+ }
313
+
314
+ const toggleSelect = item => {
315
+ if (item.disabled) return
316
+
317
+ const id = item.identityId
318
+
319
+ if (props.multiple) {
320
+ if (selectedIds.value.has(id)) {
321
+ selectedIds.value.delete(id)
322
+ } else {
323
+ selectedIds.value.add(id)
324
+ }
325
+ // Trigger reactivity
326
+ selectedIds.value = new Set(selectedIds.value)
327
+ } else {
328
+ // Single selection - immediately close picker
329
+ selectedIds.value = new Set([id])
330
+ selectHandler()
331
+ }
332
+ }
333
+
334
+ const removeSelected = item => {
335
+ if (item.disabled) return
336
+
337
+ if (item.selectAll) {
338
+ selectedGroupIds.value.delete(item.identityId)
339
+ selectedGroupIds.value = new Set(selectedGroupIds.value)
340
+ } else {
341
+ selectedIds.value.delete(item.identityId)
342
+ selectedIds.value = new Set(selectedIds.value)
343
+ }
344
+ }
345
+
346
+ const toggleAllInMenu = () => {
347
+ if (!selectedMenuId.value) return
348
+
349
+ if (props.groupSelection && tab.value === 'USERS_BY_GROUP') {
350
+ if (selectedGroupIds.value.has(selectedMenuId.value)) {
351
+ // Deselect group
352
+ selectedGroupIds.value.delete(selectedMenuId.value)
353
+ selectedGroupIds.value = new Set(selectedGroupIds.value)
354
+ } else {
355
+ // Select group
356
+ selectedGroupIds.value.add(selectedMenuId.value)
357
+ selectedGroupIds.value = new Set(selectedGroupIds.value)
358
+
359
+ // Remove individually selected users from this group
360
+ const groupUserIds = groupIndex.value.get(selectedMenuId.value)
361
+ if (groupUserIds) {
362
+ for (const userId of groupUserIds) {
363
+ selectedIds.value.delete(userId)
364
+ }
365
+ selectedIds.value = new Set(selectedIds.value)
366
+ }
367
+ }
368
+ } else {
369
+ let ids
370
+ switch (tab.value) {
371
+ case 'USERS_BY_GROUP':
372
+ ids = groupIndex.value.get(selectedMenuId.value)
373
+ break
374
+ case 'USERS_BY_POSITION':
375
+ ids = positionIndex.value.get(selectedMenuId.value)
376
+ break
377
+ case 'EXECUTOR_LISTS':
378
+ ids = executorIndex.value.get(selectedMenuId.value)
379
+ break
380
+ }
381
+
382
+ if (!ids) return
383
+
384
+ if (isAllSelectedInMenu.value) {
385
+ // Deselect all
386
+ for (const id of ids) {
387
+ selectedIds.value.delete(id)
388
+ }
389
+ } else {
390
+ // Select all (skip disabled)
391
+ for (const id of ids) {
392
+ const item = itemsMap.value.get(id)
393
+ if (item && !item.disabled) {
394
+ selectedIds.value.add(id)
395
+ }
396
+ }
397
+ }
398
+ selectedIds.value = new Set(selectedIds.value)
399
+ }
400
+ }
401
+
402
+ // Check if all items in current menu are selected (taking into account groupSelection logic)
403
+ const isAllSelectedInMenu = computed(() => {
404
+ if (!selectedMenuId.value) return false
405
+
406
+ if (props.groupSelection && tab.value === 'USERS_BY_GROUP') {
407
+ return selectedGroupIds.value.has(selectedMenuId.value)
408
+ }
409
+
410
+ let ids
411
+ switch (tab.value) {
412
+ case 'USERS_BY_GROUP':
413
+ ids = groupIndex.value.get(selectedMenuId.value)
414
+ break
415
+ case 'USERS_BY_POSITION':
416
+ ids = positionIndex.value.get(selectedMenuId.value)
417
+ break
418
+ case 'EXECUTOR_LISTS':
419
+ ids = executorIndex.value.get(selectedMenuId.value)
420
+ break
421
+ }
422
+
423
+ if (!ids || ids.size === 0) return false
424
+
425
+ for (const id of ids) {
426
+ if (!selectedIds.value.has(id)) return false
427
+ }
428
+ return true
429
+ })
430
+
431
+ const selectMenuItem = id => {
432
+ selectedMenuId.value = selectedMenuId.value === id ? null : id
433
+ }
434
+
435
+ const close = () => {
436
+ state.value = false
437
+ search.value = ''
438
+ selectedMenuId.value = null
439
+ }
440
+
441
+ const selectHandler = () => {
442
+ if (props.multiple) {
443
+ if (props.returnObject) {
444
+ modelValue.value = selectedItems.value
445
+ } else {
446
+ modelValue.value = selectedItems.value.map(item => item[props.itemValue])
447
+ }
448
+ } else {
449
+ const selectedItem = selectedItems.value[0]
450
+ if (props.returnObject) {
451
+ modelValue.value = selectedItem
452
+ } else {
453
+ modelValue.value = selectedItem[props.itemValue]
454
+ }
455
+ }
456
+
457
+ state.value = false
458
+ }
459
+
460
+ // Reset menu selection when switching tabs
461
+ watch(tab, () => {
462
+ selectedMenuId.value = null
463
+ })
464
+
465
+ // Sync with external modelValue changes
466
+ watch(state, newState => {
467
+ // Reset
468
+ selectedIds.value = new Set()
469
+ selectedGroupIds.value = new Set()
470
+
471
+ if (newState && modelValue.value) {
472
+ // Populate from modelValue
473
+ const values = Array.isArray(modelValue.value) ? modelValue.value : [modelValue.value]
474
+ for (const val of values) {
475
+ if (props.returnObject) {
476
+ if (val.type === 'GROUP' || val.groupId) {
477
+ selectedGroupIds.value.add(val.identityId)
478
+ } else {
479
+ selectedIds.value.add(val.identityId)
480
+ }
481
+ } else {
482
+ // Find item by itemValue field
483
+ let found = false
484
+ for (const item of props.items) {
485
+ if (item[props.itemValue] === val) {
486
+ if (item.type === 'GROUP' || item.groupId) {
487
+ selectedGroupIds.value.add(item.identityId)
488
+ } else {
489
+ selectedIds.value.add(item.identityId)
490
+ }
491
+ found = true
492
+ break
493
+ }
494
+ }
495
+ // If not found in items, try uniqueGroups
496
+ if (!found && uniqueGroups.value.some(g => g[props.itemValue] === val)) {
497
+ const group = uniqueGroups.value.find(g => g[props.itemValue] === val)
498
+ selectedGroupIds.value.add(group.identityId)
499
+ }
500
+ }
501
+ }
502
+ }
503
+ })
504
+ </script>
505
+ <template>
506
+ <FDialog
507
+ v-model:search="search"
508
+ :model-value="state"
509
+ name="userGroupPicker"
510
+ width="80vw"
511
+ max-width="900px"
512
+ persistent
513
+ no-space
514
+ is-search
515
+ :title="$t('user.executors')"
516
+ @close="close"
517
+ >
518
+ <div class="user-group-picker">
519
+ <!-- Header tabs -->
520
+ <v-tabs v-model="tab" grow class="mb-2">
521
+ <v-tab v-for="tabItem in tabs" :key="tabItem.value" :value="tabItem.value" height="35px" :text="tabItem.text" />
522
+ </v-tabs>
523
+
524
+ <!-- Filter menu -->
525
+ <v-tabs-window v-model="tab" class="mx-5">
526
+ <v-tabs-window-item
527
+ v-for="tabItem in tabs"
528
+ :key="tabItem.value"
529
+ :value="tabItem.value"
530
+ :style="{ height: `${calculatedHeight}px` }"
531
+ class="f-scrollbar-y"
532
+ >
533
+ <v-chip-group selected-class="active" class="pa-0" column>
534
+ <v-chip
535
+ v-for="item in menuItems"
536
+ :key="item.id"
537
+ :style="`--f-user-group-chip-color: ${item.color}`"
538
+ variant="flat"
539
+ class="user-group-picker-chip"
540
+ @click="selectMenuItem(item.id)"
541
+ >
542
+ {{ item.name || item.id }}
543
+ </v-chip>
544
+ </v-chip-group>
545
+ </v-tabs-window-item>
546
+ </v-tabs-window>
547
+
548
+ <div class="d-flex flex-column mx-5 mt-5">
549
+ <!-- Title -->
550
+ <span class="mb-2 ml-1 font-weight-semibold fs-11">{{ titleByMenu }}</span>
551
+
552
+ <!-- Select all users -->
553
+ <v-checkbox
554
+ v-if="selectedMenuId && multiple && (filteredItems.length || selectedItems.length)"
555
+ class="mb-2 ml-1"
556
+ :model-value="isAllSelectedInMenu"
557
+ :label="props.groupSelection && tab === 'USERS_BY_GROUP' ? $t('user.selectGroup') : $t('user.selectAllFromList')"
558
+ @update:model-value="toggleAllInMenu"
559
+ />
560
+ </div>
561
+
562
+ <div v-if="filteredItems.length || selectedItems.length" class="d-flex justify-space-between mx-5">
563
+ <!-- Available items -->
564
+ <v-virtual-scroll :items="filteredItems" height="40vh" item-key="identityId" item-height="40" max-width="45%" class="pr-2">
565
+ <template #default="{ item }">
566
+ <div class="d-flex align-center justify-start picker-list-item" :class="{ 'is-disabled': item.disabled }" @click="toggleSelect(item)">
567
+ <v-checkbox :model-value="isSelected(item)" :disabled="item.disabled" />
568
+ <FAvatar :full-user="item.type === 'USER' ? item : null" :group="item.type === 'GROUP' ? item : null" size="20" class="ml-1 mr-2" />
569
+ <span class="picker-item-name">{{ item.name }}</span>
570
+ </div>
571
+ </template>
572
+ </v-virtual-scroll>
573
+
574
+ <!-- Selected items -->
575
+ <v-virtual-scroll :items="selectedItems" height="40vh" item-key="identityId" item-height="40" max-width="45%" class="pr-2">
576
+ <template #default="{ item }">
577
+ <div class="d-flex align-center justify-start picker-list-item" :class="{ 'is-disabled': item.disabled }" @click="removeSelected(item)">
578
+ <v-checkbox :model-value="true" :disabled="item.disabled" />
579
+ <FAvatar :full-user="item.type === 'USER' ? item : null" :group="item.type === 'GROUP' ? item : null" size="20" class="ml-1 mr-2" />
580
+ <span class="picker-item-name">{{ item.name }}</span>
581
+ </div>
582
+ </template>
583
+ </v-virtual-scroll>
584
+ </div>
585
+
586
+ <FNoData v-else height="40vh" class="mt-5" :is-search="search" />
587
+ </div>
588
+
589
+ <template #actions>
590
+ <v-btn class="mx-auto" width="300" @click="selectHandler">
591
+ {{ $t('buttons.select') }}
592
+ </v-btn>
593
+ </template>
594
+ </FDialog>
595
+ </template>
596
+
597
+ <style lang="scss" scoped>
598
+ .user-group-picker {
599
+ :deep(.v-virtual-scroll__item) {
600
+ padding: 0;
601
+ &:nth-child(odd) {
602
+ background-color: rgb(var(--v-theme-fields));
603
+ }
604
+ .picker-list-item {
605
+ padding: 5px;
606
+ transition: all 0.3s ease-in-out;
607
+ &:not(.is-disabled) {
608
+ cursor: pointer;
609
+ &:hover {
610
+ background-color: rgba(var(--v-theme-primary), 0.2);
611
+ }
612
+ }
613
+ }
614
+ }
615
+ .user-group-picker-chip {
616
+ --v-chip-height: 20px;
617
+ margin: 3px;
618
+ border-radius: 4.8px;
619
+ color: rgb(var(--f-user-group-chip-color));
620
+ background: rgba(var(--f-user-group-chip-color), 0.2);
621
+ border: 1px solid transparent;
622
+ &.active {
623
+ border-color: rgb(var(--f-user-group-chip-color));
624
+ }
625
+ &.v-theme--dark {
626
+ color: rgb(var(--v-theme-title));
627
+ background: rgba(var(--f-user-group-chip-color), 0.7);
628
+ &.active {
629
+ border-color: rgb(var(--v-theme-title));
630
+ }
631
+ }
632
+ }
633
+ }
634
+ </style>
@@ -1,9 +1,10 @@
1
1
  // Override input styles
2
2
  .f-input {
3
3
  border-radius: 5.8px;
4
- background-color: rgb(var(--v-theme-background));
5
4
  color: rgb(var(--v-theme-text));
6
-
5
+ .v-input__control {
6
+ background-color: rgb(var(--v-theme-fields));
7
+ }
7
8
  &:hover {
8
9
  .v-field {
9
10
  .v-label {
@@ -45,6 +46,10 @@
45
46
  .v-label {
46
47
  opacity: 1;
47
48
  color: rgb(var(--v-theme-text));
49
+ font-size: 0.95rem;
50
+ &.v-field-label--floating {
51
+ font-size: 0.7rem;
52
+ }
48
53
  }
49
54
 
50
55
  &.v-field--variant-outlined {
@@ -72,7 +77,7 @@
72
77
  padding-top: 16px;
73
78
  padding-bottom: 10px;
74
79
  padding-inline: 12px 12px;
75
- min-height: auto;
80
+ min-height: 20px;
76
81
  font-size: 0.95em;
77
82
  line-height: 1em;
78
83
  color: rgb(var(--v-theme-subTitle));
@@ -83,6 +88,15 @@
83
88
  border-radius: 5.8px !important;
84
89
  }
85
90
  }
91
+ input {
92
+ min-height: 20px;
93
+ &:is(:-webkit-autofill, :autofill) {
94
+ -webkit-text-fill-color: rgb(var(--v-theme-subTitle)) !important;
95
+ -webkit-box-shadow: 0 0 0 1000px rgb(var(--v-theme-background)) inset !important;
96
+ background-color: rgb(var(--v-theme-background)) !important;
97
+ border-radius: 5.8px !important;
98
+ }
99
+ }
86
100
  input::placeholder {
87
101
  font-size: 0.9em;
88
102
  opacity: 0.9;
@@ -140,6 +154,26 @@
140
154
  }
141
155
  }
142
156
 
157
+ // Autocomplete User
158
+ .f-input.f-autocomplete-user {
159
+ // .v-field {
160
+ // .v-field__outline__end {
161
+ // min-width: calc(100% - 53px);
162
+ // }
163
+ // }
164
+ &.v-input--horizontal .v-input__append {
165
+ margin-inline-start: 0px;
166
+ .f-icon {
167
+ opacity: 1;
168
+ transition: all 0.3s ease-in-out;
169
+ &:hover {
170
+ background: rgb(var(--v-theme-primary)) !important;
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ // Textarea
143
177
  .f-input.v-textarea {
144
178
  .v-field {
145
179
  .v-field__input {
@@ -164,12 +198,14 @@
164
198
  }
165
199
  }
166
200
  .v-autocomplete__selection {
201
+ height: 20px;
167
202
  .f-chip.v-chip {
168
- --v-chip-height: 22px;
203
+ --v-chip-height: 20px;
169
204
  padding: 0;
170
205
  border-radius: 16.8px;
171
206
  font-size: 0.75em;
172
207
  background: rgb(var(--v-theme-line));
208
+ color: rgb(var(--v-theme-subTitle));
173
209
  .v-chip__close {
174
210
  margin-inline-start: 5px;
175
211
  margin-inline-end: 5px;
@@ -242,11 +278,11 @@
242
278
 
243
279
  //Radio
244
280
  .f-radio {
245
- --v-selection-control-size: 16px;
246
281
  &.v-selection-control--inline:not(:last-child) {
247
282
  margin-right: 8px;
248
283
  }
249
284
  .v-selection-control__wrapper {
285
+ --v-selection-control-size: 16px;
250
286
  .v-selection-control__input {
251
287
  &::before {
252
288
  content: none;
@@ -15,7 +15,7 @@
15
15
  height: 1px;
16
16
  width: 100%;
17
17
  background: rgb(var(--v-theme-line));
18
- transform: scaleY(0.7);
18
+ transform: scaleY(1);
19
19
  transform-origin: bottom;
20
20
  }
21
21
  .v-slide-group__content {
@@ -29,8 +29,8 @@
29
29
  }
30
30
  .v-btn__content {
31
31
  .v-tab__slider {
32
- height: 1.5px;
33
- z-index: 1;
32
+ // height: 0;
33
+ // opacity: 0;
34
34
  }
35
35
  }
36
36
  }
@@ -63,19 +63,41 @@
63
63
  &::-webkit-scrollbar {
64
64
  width: 6px;
65
65
  }
66
-
66
+ &::-webkit-scrollbar-track {
67
+ border-radius: 10.8px;
68
+ box-shadow: inset 0 0 6px rgba(var(--v-theme-disabled));
69
+ }
67
70
  &::-webkit-scrollbar-thumb {
68
- background: rgba(var(--v-theme-primary), 0.6);
71
+ border-top: 0px solid rgba(0, 0, 0, 0);
72
+ border-bottom: 0px solid rgba(0, 0, 0, 0);
73
+ background-clip: padding-box;
69
74
  border-radius: 10.8px;
70
-
75
+ height: 50px;
76
+ background-color: rgba(var(--v-theme-disabled));
71
77
  &:hover {
72
- background: rgba(var(--v-theme-primary), 1);
78
+ background-color: rgba(var(--v-theme-primary));
73
79
  }
74
80
  }
81
+ }
75
82
 
83
+ .v-virtual-scroll {
84
+ &::-webkit-scrollbar {
85
+ width: 6px;
86
+ }
76
87
  &::-webkit-scrollbar-track {
77
- background: rgba(var(--v-theme-disabled), 0.4);
78
88
  border-radius: 10.8px;
89
+ box-shadow: inset 0 0 6px rgba(var(--v-theme-disabled));
90
+ }
91
+ &::-webkit-scrollbar-thumb {
92
+ border-top: 0px solid rgba(0, 0, 0, 0);
93
+ border-bottom: 0px solid rgba(0, 0, 0, 0);
94
+ background-clip: padding-box;
95
+ border-radius: 10.8px;
96
+ height: 50px;
97
+ background-color: rgba(var(--v-theme-disabled));
98
+ &:hover {
99
+ background-color: rgba(var(--v-theme-primary));
100
+ }
79
101
  }
80
102
  }
81
103
 
@@ -53,7 +53,19 @@
53
53
  "user": {
54
54
  "telephone": "Phone number",
55
55
  "position": "Position",
56
- "department": "Department"
56
+ "department": "Department",
57
+ "executor": "Executor",
58
+ "executors": "Executors",
59
+ "initiator": "Initiator",
60
+ "candidates": "Candidates",
61
+ "inform": "To attention",
62
+ "executorLists": "Executor lists",
63
+ "usersByGroup": "Users by groups",
64
+ "usersByPosition": "Users by job title",
65
+ "allUsers": "All users",
66
+ "allUsersAndGroups": "All users and groups",
67
+ "selectGroup": "Select group",
68
+ "selectAllFromList": "Select all from list"
57
69
  },
58
70
  "tooltip": {
59
71
  "actions": "Additional actions",
@@ -53,7 +53,20 @@
53
53
  "user": {
54
54
  "telephone": "Номер телефона",
55
55
  "position": "Должность",
56
- "department": "Департамент"
56
+ "department": "Департамент",
57
+ "executor": "Исполнитель",
58
+ "executors": "Исполнители",
59
+ "candidate": "Кандидат",
60
+ "candidates": "Кандидаты",
61
+ "initiator": "Инициатор",
62
+ "inform": "К сведению",
63
+ "executorLists": "Списки исполнителей",
64
+ "usersByGroup": "Пользователи по группам",
65
+ "usersByPosition": "Пользователи по должностям",
66
+ "allUsers": "Все пользователи",
67
+ "allUsersAndGroups": "Все пользователи и группы",
68
+ "selectGroup": "Выбрать группу",
69
+ "selectAllFromList": "Выбрать всех из списка"
57
70
  },
58
71
  "tooltip": {
59
72
  "actions": "Дополнительные действия",
@@ -53,7 +53,20 @@
53
53
  "user": {
54
54
  "telephone": "Номер телефону",
55
55
  "position": "Посада",
56
- "department": "Департамент"
56
+ "department": "Департамент",
57
+ "executor": "Виконавець",
58
+ "executors": "Виконавці",
59
+ "candidate": "Кандидат",
60
+ "candidates": "Кандидати",
61
+ "initiator": "Ініціатор",
62
+ "inform": "До відома",
63
+ "executorLists": "Списки виконавців",
64
+ "usersByGroup": "Користувачі за групами",
65
+ "usersByPosition": "Користувачі за посадою",
66
+ "allUsers": "Всі користувачі",
67
+ "allUsersAndGroups": "Всі користувачі та групи",
68
+ "selectGroup": "Вибрати групу",
69
+ "selectAllFromList": "Вибрати всіх зі списку"
57
70
  },
58
71
  "tooltip": {
59
72
  "actions": "Додаткові дії",
@@ -31,3 +31,8 @@ export const hexToRGBA = (hex, alpha) => {
31
31
  export const createRadialGradient = (mainColor, spreadColor) => {
32
32
  return `radial-gradient(83.34% 88.78% at 50.00% 50.00%, ${hexToRGBA(mainColor, 0.25)} 0%, ${hexToRGBA(mainColor, 0.08)} 31.56%, ${hexToRGBA(spreadColor, 0)} 57.76%)`
33
33
  }
34
+
35
+ export const colorCollection = {
36
+ light: ['#5400A1', '#038D00', '#0051DC', '#EE6D10', '#DC005C', '#7400CF', '#006794', '#0B14EF', '#CA00DC', '#007B5E'],
37
+ dark: ['#8848C5', '#6A9269', '#6E96DB', '#D45496', '#0081B9', '#D1AD55', '#DC7B7B', '#614FCF', '#007B5E', '#AF3EB9']
38
+ }