shared-ritm 1.2.46 → 1.2.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +103 -103
  2. package/dist/index.css +1 -1
  3. package/dist/shared-ritm.es.js +199855 -72176
  4. package/dist/shared-ritm.umd.js +97 -42
  5. package/dist/types/api/services/ControlsService.d.ts +1 -0
  6. package/dist/types/api/types/Api_Controls.d.ts +12 -2
  7. package/dist/types/index.d.ts +2 -1
  8. package/package.json +1 -1
  9. package/src/api/services/ControlsService.ts +40 -36
  10. package/src/api/services/FileService.ts +15 -15
  11. package/src/api/services/GanttService.ts +17 -17
  12. package/src/api/services/MetricsService.ts +109 -109
  13. package/src/api/services/RepairsService.ts +100 -100
  14. package/src/api/settings/ApiService.ts +128 -128
  15. package/src/api/types/Api_Controls.ts +12 -2
  16. package/src/api/types/Api_Files.ts +1 -1
  17. package/src/api/types/Api_Projects.ts +55 -55
  18. package/src/api/types/Api_Repairs.ts +93 -93
  19. package/src/common/app-button/AppButton.vue +173 -173
  20. package/src/common/app-checkbox/AppCheckbox.vue +26 -26
  21. package/src/common/app-dialogs/AppConfirmDialog.vue +99 -99
  22. package/src/common/app-dropdown/AppDropdown.vue +31 -31
  23. package/src/common/app-input/AppInput.vue +147 -147
  24. package/src/common/app-loader/index.vue +43 -43
  25. package/src/common/app-page-layout/AppPageLayout.vue +122 -122
  26. package/src/common/app-select/AppSelect.vue +157 -157
  27. package/src/common/app-sidebar/AppSidebar.vue +168 -168
  28. package/src/common/app-sidebar/components/SidebarMenu.vue +37 -37
  29. package/src/common/app-sidebar/components/SidebarMenuItem.vue +148 -148
  30. package/src/common/app-table/AppTable.vue +211 -211
  31. package/src/common/app-table/AppTableLayout.vue +93 -93
  32. package/src/common/app-table/components/ModalSelect.vue +215 -0
  33. package/src/common/app-table/components/TableModal.vue +26 -53
  34. package/src/common/app-table/components/TablePagination.vue +151 -151
  35. package/src/common/app-table/components/TableSearch.vue +78 -78
  36. package/src/common/app-table/controllers/useBaseTable.ts +43 -43
  37. package/src/common/app-table/controllers/useColumnSelector.ts +38 -38
  38. package/src/common/app-table/controllers/useTableModel.ts +93 -93
  39. package/src/common/app-toggle/AppToggle.vue +23 -23
  40. package/src/common/app-wrapper/AppWrapper.vue +28 -28
  41. package/src/global.d.ts +1 -1
  42. package/src/icons/components/arrow-down-icon.vue +25 -25
  43. package/src/icons/components/arrow-frame-icon.vue +19 -19
  44. package/src/icons/components/arrow-square.vue +22 -22
  45. package/src/icons/components/table-filter-icon.vue +30 -30
  46. package/src/icons/dialogs/RemoveIcon.vue +12 -12
  47. package/src/icons/dialogs/SafetyIcon.vue +12 -12
  48. package/src/icons/header/flashIcon.vue +24 -24
  49. package/src/icons/header/searchStatusIcon.vue +24 -24
  50. package/src/icons/header/smallCapsIcon.vue +34 -34
  51. package/src/icons/sidebar/assign-module-icon.vue +36 -36
  52. package/src/icons/sidebar/instrument-history-icon.vue +32 -32
  53. package/src/icons/sidebar/instrument-order-icon.vue +38 -38
  54. package/src/icons/sidebar/instrument-work-zone-icon.vue +18 -18
  55. package/src/icons/sidebar/instruments-icon.vue +45 -45
  56. package/src/icons/sidebar/logo-icon.vue +15 -15
  57. package/src/icons/sidebar/logout-icon.vue +13 -13
  58. package/src/icons/sidebar/modules-icon.vue +16 -16
  59. package/src/icons/sidebar/notifications-icon.vue +24 -24
  60. package/src/icons/sidebar/order-icon.vue +44 -44
  61. package/src/icons/sidebar/pass-icon.vue +38 -38
  62. package/src/icons/sidebar/positions-icon.vue +42 -42
  63. package/src/icons/sidebar/preorder-icon.vue +19 -19
  64. package/src/icons/sidebar/projects-icon.vue +31 -31
  65. package/src/icons/sidebar/repair-object-icon.vue +18 -18
  66. package/src/icons/sidebar/repairs-icon.vue +20 -20
  67. package/src/icons/sidebar/roles-icon.vue +26 -26
  68. package/src/icons/sidebar/status-history-icon.vue +24 -24
  69. package/src/icons/sidebar/tasks-icon.vue +28 -28
  70. package/src/icons/sidebar/tasks_tasks-icon.vue +39 -39
  71. package/src/icons/sidebar/tasks_today-icon.vue +27 -27
  72. package/src/icons/sidebar/teams-icon.vue +32 -32
  73. package/src/icons/sidebar/user-icon.vue +18 -18
  74. package/src/icons/sidebar/users-icon.vue +46 -46
  75. package/src/icons/sidebar/videosources-icon.vue +19 -19
  76. package/src/icons/sidebar/videowall-icon.vue +13 -13
  77. package/src/icons/sidebar/videozones-icon.vue +21 -21
  78. package/src/icons/sidebar/warehouses-icon.vue +43 -43
  79. package/src/icons/sidebar/workshop-icon.vue +100 -100
  80. package/src/icons/sidebar/workzones-icon.vue +22 -22
  81. package/src/icons/task/attention-icon.vue +13 -13
  82. package/src/icons/task/clock-icon.vue +10 -10
  83. package/src/icons/task/delete-icon.vue +10 -10
  84. package/src/icons/task/fire-icon.vue +16 -16
  85. package/src/index.ts +2 -0
  86. package/src/quasar-user-options.ts +17 -17
  87. package/src/router/index.ts +10 -10
  88. package/src/shared/styles/general.css +96 -96
  89. package/src/shims-vue.d.ts +5 -5
  90. package/src/utils/confirm.ts +12 -12
  91. package/src/utils/notification.ts +9 -9
@@ -1,211 +1,211 @@
1
- <script setup lang="ts">
2
- import { defineProps, defineEmits, ref, computed } from 'vue'
3
- import type { Ref } from 'vue'
4
- import FilterIcon from '@/icons/components/table-filter-icon.vue'
5
-
6
- interface FilterOption {
7
- id: string
8
- name: string
9
- }
10
-
11
- interface TableEmits {
12
- 'toggle-filter-value': [colName: string, value: string]
13
- 'clear-filter': [colName: string]
14
- 'open-filter-menu': [colName: string, isOpen: boolean]
15
- 'row-click': [row: Record<string, any>]
16
- }
17
-
18
- const emit = defineEmits<TableEmits>()
19
-
20
- const props = defineProps<{
21
- rows: Ref<any[]>
22
- columns: any[]
23
- columnFilters: Ref<Record<string, string | string[] | undefined>>
24
- filterMenus: Ref<Record<string, boolean>>
25
- filtersOptions: Record<string, FilterOption[]>
26
- meta: Ref<{ currentPage: number; perPage: number }>
27
- }>()
28
-
29
- const localSearches = ref<Record<string, string>>({})
30
-
31
- const filteredOptions = computed(() => {
32
- const result: Record<string, FilterOption[]> = {}
33
- for (const col of props.columns) {
34
- const search = localSearches.value[col.name]?.toLowerCase() || ''
35
- const options = props.filtersOptions[col.name] || []
36
- result[col.name] = options.filter(opt => opt.name.toLowerCase().includes(search))
37
- }
38
- return result
39
- })
40
- </script>
41
-
42
- <template>
43
- <q-page class="flex flex-col" style="min-height: 100%">
44
- <q-table
45
- :rows="rows.value"
46
- :columns="columns"
47
- row-key="id"
48
- flat
49
- bordered
50
- hide-bottom
51
- class="full-width"
52
- :rows-per-page-options="[0]"
53
- @row-click="(_, row) => emit('row-click', row)"
54
- >
55
- <template #body-cell-index="props">
56
- <q-td :props="props" class="text-center">
57
- {{ props.rowIndex + 1 + (meta.value.currentPage - 1) * meta.value.perPage }}
58
- </q-td>
59
- </template>
60
-
61
- <template v-for="col in columns" :key="col.name" #[`header-cell-${col.name}`]="propsSlot">
62
- <q-th :props="propsSlot" :class="{ 'cursor-pointer': col.filterType }">
63
- <div
64
- v-if="col.filterType"
65
- class="row items-center no-wrap"
66
- @click.stop="emit('open-filter-menu', col.name, !filterMenus.value[col.name])"
67
- >
68
- <filter-icon class="q-mr-xs" />
69
- <span>{{ col.label }}</span>
70
- <template v-if="col.filterType === 'multi' && columnFilters.value[col.name]?.length">
71
- <div class="label-length">- {{ columnFilters.value[col.name]?.length }}</div>
72
- </template>
73
- </div>
74
- <div v-else>{{ col.label }}</div>
75
-
76
- <q-menu
77
- v-if="col.filterType"
78
- :model-value="filterMenus.value[col.name]"
79
- fit
80
- max-height="300px"
81
- class="filter-menu"
82
- @update:model-value="isOpen => emit('open-filter-menu', col.name, isOpen)"
83
- >
84
- <div class="filter-content">
85
- <q-input v-model="localSearches[col.name]" dense outlined placeholder="Поиск" class="q-mb-sm" clearable />
86
- <q-scroll-area style="height: 200px">
87
- <q-list style="min-width: 200px">
88
- <q-item v-for="option in filteredOptions[col.name]" :key="`${col.name}-${option.id}`" tag="label">
89
- <q-item-section avatar>
90
- <q-checkbox
91
- v-if="col.filterType === 'multi'"
92
- dense
93
- :model-value="
94
- Array.isArray(columnFilters.value[col.name]) &&
95
- columnFilters.value[col.name]?.includes(option.name)
96
- "
97
- @update:model-value="() => emit('toggle-filter-value', col.name, option.name)"
98
- />
99
- <q-radio
100
- v-else
101
- dense
102
- :model-value="columnFilters.value[col.name]"
103
- :val="option.name"
104
- @update:model-value="emit('toggle-filter-value', col.name, $event)"
105
- />
106
- </q-item-section>
107
- <q-item-section>{{ option.name }}</q-item-section>
108
- </q-item>
109
- </q-list>
110
- </q-scroll-area>
111
-
112
- <div class="filter-footer">
113
- <q-btn color="negative" flat dense label="Сбросить фильтр" @click="emit('clear-filter', col.name)" />
114
- </div>
115
- </div>
116
- </q-menu>
117
- </q-th>
118
- </template>
119
-
120
- <template #body-cell="props">
121
- <q-td :props="props">
122
- <q-badge
123
- v-if="props.col.badge && typeof props.value === 'boolean'"
124
- :color="
125
- props.col.badge.colorTrue && props.value
126
- ? props.col.badge.colorTrue
127
- : props.col.badge.colorFalse && !props.value
128
- ? props.col.badge.colorFalse
129
- : props.value
130
- ? 'green'
131
- : 'red'
132
- "
133
- outline
134
- class="text-bold"
135
- >
136
- {{ props.value ? props.col.badge.true ?? 'Да' : props.col.badge.false ?? 'Нет' }}
137
- </q-badge>
138
- <span v-else-if="props.col.html" v-html="props.value"></span>
139
- <span v-else>{{ props.value }}</span>
140
- </q-td>
141
- </template>
142
- </q-table>
143
- </q-page>
144
- </template>
145
-
146
- <style scoped lang="scss">
147
- .cursor-pointer {
148
- cursor: pointer;
149
- }
150
-
151
- .filter-menu {
152
- padding: 10px;
153
- }
154
- .label-length {
155
- margin-left: 5px;
156
- }
157
- .filter-content {
158
- display: flex;
159
- flex-direction: column;
160
- color: #1d425d;
161
- .q-item {
162
- padding: 0 5px;
163
- min-height: 40px;
164
- }
165
- .q-item__section {
166
- min-width: 30px;
167
- padding: 0px;
168
- }
169
- ::v-deep(.q-radio__inner),
170
- ::v-deep(.q-checkbox__inner) {
171
- color: #a4b4cf;
172
- }
173
- }
174
-
175
- .filter-footer {
176
- border-top: 1px solid #eee;
177
- display: flex;
178
- justify-content: center;
179
- }
180
-
181
- ::v-deep(.q-table thead) {
182
- background: #f2f7fb;
183
- }
184
-
185
- ::v-deep(.q-table th) {
186
- font-family: NunitoSansFont, sans-serif;
187
- color: #a4b4cf;
188
- font-size: 15px;
189
- font-style: normal;
190
- font-weight: 700;
191
- line-height: 20px;
192
- height: 61px;
193
- }
194
-
195
- ::v-deep(.q-table tbody td) {
196
- height: 61px;
197
- font-size: 14px;
198
- }
199
-
200
- ::v-deep(.q-table tbody) {
201
- font-family: NunitoSansFont, sans-serif;
202
- border-color: #d7e0ef;
203
- color: #1d425d;
204
- line-height: 20px;
205
- font-size: 14px;
206
- }
207
-
208
- ::v-deep(.q-table tbody tr:last-child td) {
209
- border-bottom: 1px solid #d7e0ef;
210
- }
211
- </style>
1
+ <script setup lang="ts">
2
+ import { defineProps, defineEmits, ref, computed } from 'vue'
3
+ import type { Ref } from 'vue'
4
+ import FilterIcon from '@/icons/components/table-filter-icon.vue'
5
+
6
+ interface FilterOption {
7
+ id: string
8
+ name: string
9
+ }
10
+
11
+ interface TableEmits {
12
+ 'toggle-filter-value': [colName: string, value: string]
13
+ 'clear-filter': [colName: string]
14
+ 'open-filter-menu': [colName: string, isOpen: boolean]
15
+ 'row-click': [row: Record<string, any>]
16
+ }
17
+
18
+ const emit = defineEmits<TableEmits>()
19
+
20
+ const props = defineProps<{
21
+ rows: Ref<any[]>
22
+ columns: any[]
23
+ columnFilters: Ref<Record<string, string | string[] | undefined>>
24
+ filterMenus: Ref<Record<string, boolean>>
25
+ filtersOptions: Record<string, FilterOption[]>
26
+ meta: Ref<{ currentPage: number; perPage: number }>
27
+ }>()
28
+
29
+ const localSearches = ref<Record<string, string>>({})
30
+
31
+ const filteredOptions = computed(() => {
32
+ const result: Record<string, FilterOption[]> = {}
33
+ for (const col of props.columns) {
34
+ const search = localSearches.value[col.name]?.toLowerCase() || ''
35
+ const options = props.filtersOptions[col.name] || []
36
+ result[col.name] = options.filter(opt => opt.name.toLowerCase().includes(search))
37
+ }
38
+ return result
39
+ })
40
+ </script>
41
+
42
+ <template>
43
+ <q-page class="flex flex-col" style="min-height: 100%">
44
+ <q-table
45
+ :rows="rows.value"
46
+ :columns="columns"
47
+ row-key="id"
48
+ flat
49
+ bordered
50
+ hide-bottom
51
+ class="full-width"
52
+ :rows-per-page-options="[0]"
53
+ @row-click="(_, row) => emit('row-click', row)"
54
+ >
55
+ <template #body-cell-index="props">
56
+ <q-td :props="props" class="text-center">
57
+ {{ props.rowIndex + 1 + (meta.value.currentPage - 1) * meta.value.perPage }}
58
+ </q-td>
59
+ </template>
60
+
61
+ <template v-for="col in columns" :key="col.name" #[`header-cell-${col.name}`]="propsSlot">
62
+ <q-th :props="propsSlot" :class="{ 'cursor-pointer': col.filterType }">
63
+ <div
64
+ v-if="col.filterType"
65
+ class="row items-center no-wrap"
66
+ @click.stop="emit('open-filter-menu', col.name, !filterMenus.value[col.name])"
67
+ >
68
+ <filter-icon class="q-mr-xs" />
69
+ <span>{{ col.label }}</span>
70
+ <template v-if="col.filterType === 'multi' && columnFilters.value[col.name]?.length">
71
+ <div class="label-length">- {{ columnFilters.value[col.name]?.length }}</div>
72
+ </template>
73
+ </div>
74
+ <div v-else>{{ col.label }}</div>
75
+
76
+ <q-menu
77
+ v-if="col.filterType"
78
+ :model-value="filterMenus.value[col.name]"
79
+ fit
80
+ max-height="300px"
81
+ class="filter-menu"
82
+ @update:model-value="isOpen => emit('open-filter-menu', col.name, isOpen)"
83
+ >
84
+ <div class="filter-content">
85
+ <q-input v-model="localSearches[col.name]" dense outlined placeholder="Поиск" class="q-mb-sm" clearable />
86
+ <q-scroll-area style="height: 200px">
87
+ <q-list style="min-width: 200px">
88
+ <q-item v-for="option in filteredOptions[col.name]" :key="`${col.name}-${option.id}`" tag="label">
89
+ <q-item-section avatar>
90
+ <q-checkbox
91
+ v-if="col.filterType === 'multi'"
92
+ dense
93
+ :model-value="
94
+ Array.isArray(columnFilters.value[col.name]) &&
95
+ columnFilters.value[col.name]?.includes(option.name)
96
+ "
97
+ @update:model-value="() => emit('toggle-filter-value', col.name, option.name)"
98
+ />
99
+ <q-radio
100
+ v-else
101
+ dense
102
+ :model-value="columnFilters.value[col.name]"
103
+ :val="option.name"
104
+ @update:model-value="emit('toggle-filter-value', col.name, $event)"
105
+ />
106
+ </q-item-section>
107
+ <q-item-section>{{ option.name }}</q-item-section>
108
+ </q-item>
109
+ </q-list>
110
+ </q-scroll-area>
111
+
112
+ <div class="filter-footer">
113
+ <q-btn color="negative" flat dense label="Сбросить фильтр" @click="emit('clear-filter', col.name)" />
114
+ </div>
115
+ </div>
116
+ </q-menu>
117
+ </q-th>
118
+ </template>
119
+
120
+ <template #body-cell="props">
121
+ <q-td :props="props">
122
+ <q-badge
123
+ v-if="props.col.badge && typeof props.value === 'boolean'"
124
+ :color="
125
+ props.col.badge.colorTrue && props.value
126
+ ? props.col.badge.colorTrue
127
+ : props.col.badge.colorFalse && !props.value
128
+ ? props.col.badge.colorFalse
129
+ : props.value
130
+ ? 'green'
131
+ : 'red'
132
+ "
133
+ outline
134
+ class="text-bold"
135
+ >
136
+ {{ props.value ? props.col.badge.true ?? 'Да' : props.col.badge.false ?? 'Нет' }}
137
+ </q-badge>
138
+ <span v-else-if="props.col.html" v-html="props.value"></span>
139
+ <span v-else>{{ props.value }}</span>
140
+ </q-td>
141
+ </template>
142
+ </q-table>
143
+ </q-page>
144
+ </template>
145
+
146
+ <style scoped lang="scss">
147
+ .cursor-pointer {
148
+ cursor: pointer;
149
+ }
150
+
151
+ .filter-menu {
152
+ padding: 10px;
153
+ }
154
+ .label-length {
155
+ margin-left: 5px;
156
+ }
157
+ .filter-content {
158
+ display: flex;
159
+ flex-direction: column;
160
+ color: #1d425d;
161
+ .q-item {
162
+ padding: 0 5px;
163
+ min-height: 40px;
164
+ }
165
+ .q-item__section {
166
+ min-width: 30px;
167
+ padding: 0px;
168
+ }
169
+ ::v-deep(.q-radio__inner),
170
+ ::v-deep(.q-checkbox__inner) {
171
+ color: #a4b4cf;
172
+ }
173
+ }
174
+
175
+ .filter-footer {
176
+ border-top: 1px solid #eee;
177
+ display: flex;
178
+ justify-content: center;
179
+ }
180
+
181
+ ::v-deep(.q-table thead) {
182
+ background: #f2f7fb;
183
+ }
184
+
185
+ ::v-deep(.q-table th) {
186
+ font-family: NunitoSansFont, sans-serif;
187
+ color: #a4b4cf;
188
+ font-size: 15px;
189
+ font-style: normal;
190
+ font-weight: 700;
191
+ line-height: 20px;
192
+ height: 61px;
193
+ }
194
+
195
+ ::v-deep(.q-table tbody td) {
196
+ height: 61px;
197
+ font-size: 14px;
198
+ }
199
+
200
+ ::v-deep(.q-table tbody) {
201
+ font-family: NunitoSansFont, sans-serif;
202
+ border-color: #d7e0ef;
203
+ color: #1d425d;
204
+ line-height: 20px;
205
+ font-size: 14px;
206
+ }
207
+
208
+ ::v-deep(.q-table tbody tr:last-child td) {
209
+ border-bottom: 1px solid #d7e0ef;
210
+ }
211
+ </style>
@@ -1,93 +1,93 @@
1
- <script setup lang="ts">
2
- import { AppTable, AppTablePagination, AppTableSearch } from 'shared-ritm'
3
- import { defineProps } from 'vue'
4
-
5
- const props = defineProps<{
6
- search: string
7
- loading: boolean
8
- currentPage: number
9
- totalPages: number
10
- tableProps: any
11
- tableEvents: any
12
- onSearch: (val: string) => void
13
- onPageChange: (page: number) => void
14
- actionsSlot?: boolean
15
- modalSlot?: boolean
16
- }>()
17
- </script>
18
-
19
- <template>
20
- <div class="table-layout">
21
- <div class="table-controls">
22
- <app-table-search
23
- :model-value="props.search"
24
- class="search-input"
25
- placeholder="Введите наименование"
26
- @search="props.onSearch"
27
- />
28
- <slot v-if="actionsSlot" name="actions" />
29
- </div>
30
-
31
- <div class="table-wrapper">
32
- <app-table v-bind="props.tableProps" v-on="props.tableEvents" />
33
- <div v-if="props.loading" class="loader-overlay">
34
- <q-spinner-audio class="loader-spinner" size="md" :thickness="3" color="primary" />
35
- </div>
36
- </div>
37
-
38
- <app-table-pagination
39
- :model-value="props.currentPage"
40
- :total-pages="props.totalPages"
41
- @page-change="props.onPageChange"
42
- />
43
-
44
- <slot v-if="modalSlot" name="modal" />
45
- </div>
46
- </template>
47
-
48
- <style scoped lang="scss">
49
- .table-layout {
50
- height: calc(100vh - 100px);
51
- display: flex;
52
- flex-direction: column;
53
- box-sizing: border-box;
54
- }
55
- .table-controls {
56
- display: flex;
57
- align-items: center;
58
- justify-content: space-between;
59
- gap: 18px;
60
- margin-bottom: 20px;
61
-
62
- .search-input {
63
- flex: 1;
64
- }
65
-
66
- ::v-deep(.q-btn) {
67
- flex-shrink: 0;
68
- border-radius: 2px;
69
- background: #fff;
70
- box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
71
- color: #3f8cff;
72
- height: 50px;
73
- width: 50px;
74
- }
75
- }
76
- .table-wrapper {
77
- position: relative;
78
- flex: 1;
79
- overflow-y: auto;
80
- }
81
- .loader-overlay {
82
- position: absolute;
83
- inset: 0;
84
- z-index: 10;
85
- background: rgba(255, 255, 255, 0.8);
86
- display: flex;
87
- align-items: center;
88
- justify-content: center;
89
- }
90
- .loader-spinner {
91
- z-index: 11;
92
- }
93
- </style>
1
+ <script setup lang="ts">
2
+ import { AppTable, AppTablePagination, AppTableSearch } from 'shared-ritm'
3
+ import { defineProps } from 'vue'
4
+
5
+ const props = defineProps<{
6
+ search: string
7
+ loading: boolean
8
+ currentPage: number
9
+ totalPages: number
10
+ tableProps: any
11
+ tableEvents: any
12
+ onSearch: (val: string) => void
13
+ onPageChange: (page: number) => void
14
+ actionsSlot?: boolean
15
+ modalSlot?: boolean
16
+ }>()
17
+ </script>
18
+
19
+ <template>
20
+ <div class="table-layout">
21
+ <div class="table-controls">
22
+ <app-table-search
23
+ :model-value="props.search"
24
+ class="search-input"
25
+ placeholder="Введите наименование"
26
+ @search="props.onSearch"
27
+ />
28
+ <slot v-if="actionsSlot" name="actions" />
29
+ </div>
30
+
31
+ <div class="table-wrapper">
32
+ <app-table v-bind="props.tableProps" v-on="props.tableEvents" />
33
+ <div v-if="props.loading" class="loader-overlay">
34
+ <q-spinner-audio class="loader-spinner" size="md" :thickness="3" color="primary" />
35
+ </div>
36
+ </div>
37
+
38
+ <app-table-pagination
39
+ :model-value="props.currentPage"
40
+ :total-pages="props.totalPages"
41
+ @page-change="props.onPageChange"
42
+ />
43
+
44
+ <slot v-if="modalSlot" name="modal" />
45
+ </div>
46
+ </template>
47
+
48
+ <style scoped lang="scss">
49
+ .table-layout {
50
+ height: calc(100vh - 100px);
51
+ display: flex;
52
+ flex-direction: column;
53
+ box-sizing: border-box;
54
+ }
55
+ .table-controls {
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: space-between;
59
+ gap: 18px;
60
+ margin-bottom: 20px;
61
+
62
+ .search-input {
63
+ flex: 1;
64
+ }
65
+
66
+ ::v-deep(.q-btn) {
67
+ flex-shrink: 0;
68
+ border-radius: 2px;
69
+ background: #fff;
70
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
71
+ color: #3f8cff;
72
+ height: 50px;
73
+ width: 50px;
74
+ }
75
+ }
76
+ .table-wrapper {
77
+ position: relative;
78
+ flex: 1;
79
+ overflow-y: auto;
80
+ }
81
+ .loader-overlay {
82
+ position: absolute;
83
+ inset: 0;
84
+ z-index: 10;
85
+ background: rgba(255, 255, 255, 0.8);
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ }
90
+ .loader-spinner {
91
+ z-index: 11;
92
+ }
93
+ </style>