edvoyui-component-library-test-flight 0.0.39 → 0.0.41

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.
@@ -0,0 +1,489 @@
1
+ <template>
2
+ <div class="max-w-screen-xl mx-auto border border-gray-100 border-solid h-[calc(100dvh-20rem)] overflow-hidden">
3
+ <div :class="tableClasses">
4
+ <table class="table">
5
+ <thead>
6
+ <tr>
7
+ <th
8
+ v-if="checkable"
9
+ style="width: 45px; flex: 45 0 auto; max-width: 45px"
10
+ >
11
+ <UCheckbox
12
+ :checked="isAllChecked"
13
+ :indeterminate="isIndeterminate"
14
+ class="flex justify-center mt-0"
15
+ @change="checkAll"
16
+ />
17
+ </th>
18
+ <th
19
+ v-for="(header, headerIndex) in headers"
20
+ :key="headerIndex"
21
+ :class="[
22
+ {
23
+ 'ui-table__sortable': header.sortable,
24
+ 'ui-table__sortable--active': currentSort === header.value
25
+ },
26
+ sortClass(header)
27
+ ]"
28
+ :style="headerStyle(header)"
29
+ @click="sortBy(header)"
30
+ >
31
+
32
+ <slot name="header" :header="header">
33
+ {{ capitalizeText(header?.text ?? header?.name ?? '') }}
34
+ </slot>
35
+ <span v-if="headerOptional">
36
+ <slot name="headerOptionalItem"></slot>
37
+ </span>
38
+ </th>
39
+ </tr>
40
+ </thead>
41
+ <tbody>
42
+ <tr v-for="(row, rowIndex) in computedItems" :key="rowIndex">
43
+ <td v-if="checkable" style="width: 45px; flex: 45 0 auto; max-width: 45px">
44
+ <UCheckbox
45
+ :disabled="!isRowCheckable(row)"
46
+ :checked="isRowChecked(row)"
47
+ class="flex justify-center mt-0"
48
+ @change.prevent.stop="($event) => checkRow(row, rowIndex, $event)"
49
+ />
50
+ </td>
51
+ <td
52
+ v-for="(header, headerIndex) in headers"
53
+ :key="headerIndex"
54
+ :style="headerStyle(header)"
55
+ >
56
+ <span class="ui-table__truncate">
57
+ <slot :name="`item.${header.value}`" :row="row" :rowIndex="rowIndex">
58
+ {{ getValueByPath(row, header.value) }}
59
+ </slot>
60
+ </span>
61
+ </td>
62
+ </tr>
63
+ <slot v-if="tableExpanded" name="expanded" :row="row" :rowIndex="rowIndex"></slot>
64
+ </tbody>
65
+ <tbody v-if="computedItems.length === 0">
66
+ <tr>
67
+ <td :colspan="headers.length" class="ui-table__no-result">
68
+ No matching records found
69
+ </td>
70
+ </tr>
71
+ </tbody>
72
+ </table>
73
+ </div>
74
+ <div :class="['ui-table__footer', footerClass]">
75
+ <slot name="table-bottom-left">
76
+ <div></div>
77
+ </slot>
78
+ <!-- v-if="paginated && computedItems.length !== 0" -->
79
+ <EUIPagination
80
+ :per-page="perPage"
81
+ :total="itemsLength"
82
+ :data-cy="dataCy"
83
+ :current="newCurrentPage"
84
+ :hide-number="hidePageNumber"
85
+ @change="pageChanged"
86
+ />
87
+ </div>
88
+ </div>
89
+ </template>
90
+
91
+ <script>
92
+ import { ref, computed, watch } from 'vue';
93
+ import { getValueByPath, indexOf, defaultFilter } from '../../utils/helpers';
94
+ import UCheckbox from './UCheckbox.vue';
95
+ import { PropType } from 'vue';
96
+ import { capitalizeText } from '../../utils/lodash';
97
+ import EUIPagination from './EUIPagination.vue';
98
+
99
+ export default {
100
+ name: 'UITable',
101
+ components: { UCheckbox, EUIPagination },
102
+
103
+ props: {
104
+ items: { type: Array, default: () => [] },
105
+ dataCy: { type: String, default: 'ui-pagination' },
106
+ headers: { type: Array, default: () => [] },
107
+ paginated: Boolean,
108
+ tableExpanded: Boolean,
109
+ backendPagination: Boolean,
110
+ search: { type: String, default: '' },
111
+ perPage: { type: [Number, String], default: 5 },
112
+ total: { type: [Number, String], default: 0 },
113
+ currentPage: { type: Number, default: 1 },
114
+ defaultSort: { type: String, default: '' },
115
+ defaultSortDirection: { type: String, default: 'asc' },
116
+ headerOptional: { type: Boolean, default: false },
117
+ checkedRows: { type: Array, default: () => [] },
118
+ checkable: { type: Boolean, default: false },
119
+ stickyHeader: { type: Boolean, default: true },
120
+ stickyColumn: { type: Boolean, default: false },
121
+ stickyColumnTwo: { type: Boolean, default: false },
122
+ isRowCheckable: { type: Function, default: () => true },
123
+ hidePageNumber: { type: Boolean, default: false },
124
+ footerClass: { type: String, default: '' }
125
+ },
126
+
127
+ setup(props, { emit }) {
128
+ const newCurrentPage = ref(props.currentPage);
129
+ const newCheckedRows = ref([...props.checkedRows]);
130
+ const currentSort = ref(props.defaultSort);
131
+ const currentSortDir = ref(props.defaultSortDirection);
132
+
133
+ const tableClasses = computed(() => ({
134
+ 'ui-table scrollbar--thin': true,
135
+ 'ui-table--sticky-header': props.stickyHeader,
136
+ 'ui-table--sticky-column': props.stickyColumn,
137
+ 'ui-table--sticky-column-two': props.stickyColumnTwo
138
+ }));
139
+
140
+ const itemsLength = computed(() => (props.backendPagination ? props.total : filteredItems.value.length));
141
+
142
+ const filteredItems = computed(() => {
143
+ if (!props.backendPagination && props.search) {
144
+ const propsKeys = props.headers.map(h => h.value);
145
+ return props.items.filter(item =>
146
+ propsKeys.some(key => defaultFilter(getValueByPath(item, key), props.search.toLowerCase()))
147
+ );
148
+ }
149
+ return props.items;
150
+ });
151
+
152
+ const computedItems = computed(() => {
153
+ let items = [...filteredItems.value];
154
+ if (currentSort.value && !props.backendPagination) items = sortItems(items);
155
+ if (props.paginated && !props.backendPagination) items = paginateItems(items);
156
+ return items;
157
+ });
158
+
159
+ const headerStyle = header => ({
160
+ width: `${header.width || 300}px`,
161
+ flex: `${header.width || 300} 0 auto`,
162
+ maxWidth: `${header.width || 300}px`,
163
+ backgroundColor: header.color || 'rgb(235 236 240)'
164
+ });
165
+
166
+ const pageChanged = page => {
167
+ newCurrentPage.value = page;
168
+ emit('update:currentPage', page);
169
+ emit('page-change', page);
170
+ };
171
+
172
+ const sortItems = items => {
173
+ return items.sort((a, b) => {
174
+ const aValue = getValueByPath(a, currentSort.value);
175
+ const bValue = getValueByPath(b, currentSort.value);
176
+ return currentSortDir.value === 'asc' ? aValue - bValue : bValue - aValue;
177
+ });
178
+ };
179
+
180
+ const paginateItems = items => {
181
+ const start = (newCurrentPage.value - 1) * props.perPage;
182
+ const end = start + props.perPage;
183
+ return items.slice(start, end);
184
+ };
185
+
186
+ const isRowChecked = row => {
187
+ return newCheckedRows.value.includes(row);
188
+ };
189
+
190
+ const sortClass = () => {
191
+ return header => {
192
+ return this.currentSortDir === "asc" &&
193
+ this.currentSort === header.value
194
+ ? "ui-table__sortable--asc"
195
+ : this.currentSortDir === "desc" && this.currentSort === header.value
196
+ ? "ui-table__sortable--dsc"
197
+ : ""
198
+ }
199
+ }
200
+
201
+ const isAllChecked = computed(() => {
202
+ return (
203
+ computedItems.value.length > 0 &&
204
+ computedItems.value.every(row => newCheckedRows.value.includes(row))
205
+ );
206
+ });
207
+
208
+ const isIndeterminate = computed(() => {
209
+ const checkedCount = computedItems.value.filter(row =>
210
+ newCheckedRows.value.includes(row)
211
+ ).length;
212
+ return checkedCount > 0 && checkedCount < computedItems.value.length;
213
+ });
214
+
215
+ const checkAll = () => {
216
+ if (isAllChecked.value) {
217
+ // Uncheck all rows
218
+ newCheckedRows.value = []
219
+ } else {
220
+ // Check all rows
221
+ const rowsToCheck = computedItems.value.filter(
222
+ row => !newCheckedRows.value.includes(row)
223
+ );
224
+ newCheckedRows.value.push(...rowsToCheck);
225
+ }
226
+ emit('check', newCheckedRows.value);
227
+ emit("check-all", newCheckedRows.value);
228
+ emit("update:checkedRows", newCheckedRows.value);
229
+ emit("update:selectedRows", newCheckedRows.value);
230
+ };
231
+
232
+ const removeCheckedRow = (row) => {
233
+ const index = indexOf(newCheckedRows.value, row);
234
+ if (index >= 0) {
235
+ newCheckedRows.value.splice(index, 1);
236
+ }
237
+ };
238
+
239
+ const checkRow = (row, index, event) => {
240
+ const isChecked = newCheckedRows.value.includes(row);
241
+ if (event.target && isChecked) {
242
+ // Remove row from checkedRows
243
+ removeCheckedRow(row);
244
+ }
245
+ else {
246
+ newCheckedRows.value.push(row);
247
+ };
248
+ emit('check', newCheckedRows.value);
249
+ emit("update:checkedRows", newCheckedRows.value);
250
+ emit("update:selectedRows", newCheckedRows.value);
251
+ };
252
+
253
+ watch(() => props.currentPage, newPage => (newCurrentPage.value = newPage));
254
+ watch(() => props.checkedRows, rows => (newCheckedRows.value = [...rows]));
255
+
256
+ return {
257
+ tableClasses,
258
+ computedItems,
259
+ headerStyle,
260
+ pageChanged,
261
+ checkRow,
262
+ checkAll,
263
+ itemsLength,
264
+ sortClass,
265
+ isRowChecked,
266
+ isAllChecked,
267
+ isIndeterminate,
268
+ getValueByPath,
269
+ capitalizeText
270
+ };
271
+ }
272
+ };
273
+ </script>
274
+
275
+ <style lang="scss" scoped>
276
+ .ui-table {
277
+ @apply overflow-auto bg-white bg-no-repeat border border-solid border-gray-50;
278
+ background-image: linear-gradient(to right, white, white),
279
+ linear-gradient(to right, white, white),
280
+ linear-gradient(to right, rgba(223, 231, 242, 1), rgba(255, 255, 255, 0)),
281
+ linear-gradient(to left, rgba(223, 231, 242, 1), rgba(255, 255, 255, 0)),
282
+ linear-gradient(to top, white, rgba(255, 255, 255, 0)),
283
+ linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(223, 231, 242, 1));
284
+ background-position: left center, right center, left center, right center,
285
+ left bottom, left bottom;
286
+ background-size: 30px 100%, 30px 100%, 20px 100%, 20px 100%, 100% 100px,
287
+ 100% 20px;
288
+ background-attachment: local, local, scroll, scroll, local, scroll;
289
+
290
+ .table {
291
+ @apply border-separate w-full;
292
+ border-spacing: 0;
293
+ thead {
294
+ tr {
295
+ @apply flex flex-grow;
296
+ th {
297
+ @apply bg-gray-50 px-3 py-4 text-sm text-gray-600 text-left font-semibold uppercase border-r border-b border-solid border-gray-100 cursor-auto whitespace-nowrap flex items-center;
298
+ height: 45px;
299
+ transition: all 0.15s ease-out;
300
+ &:hover {
301
+ @apply bg-blue-50;
302
+ }
303
+ &:first-child {
304
+ border-left-width: 0;
305
+ }
306
+ &:last-child {
307
+ border-right-width: 0;
308
+ }
309
+ &:last-of-type {
310
+ max-width: unset !important;
311
+ }
312
+ &__sortable {
313
+ @apply cursor-pointer pr-12;
314
+ &--active {
315
+ @apply bg-blue-50;
316
+ }
317
+ &::before,
318
+ &::after {
319
+ @apply absolute w-0 h-0;
320
+ content: "";
321
+ right: 20px;
322
+ border-left: 4px solid transparent;
323
+ border-right: 4px solid transparent;
324
+ }
325
+ &::before {
326
+ top: 13px;
327
+ border-bottom: 6px solid theme('colors.gray.600');
328
+ }
329
+ &::after {
330
+ top: 22px;
331
+ border-top: 6px solid theme("colors.gray.500");
332
+ }
333
+ &--asc {
334
+ &::before {
335
+ border-bottom: 6px solid theme("colors.blue.300");
336
+ }
337
+ }
338
+ &--dsc {
339
+ &::after {
340
+ border-top: 6px solid theme("colors.blue.300");
341
+ }
342
+ }
343
+ }
344
+ }
345
+ }
346
+ }
347
+ tbody {
348
+ tr {
349
+ @apply flex flex-grow;
350
+ &:hover {
351
+ background-color: rgba(238, 243, 250, 0.8);
352
+ transition: all 0.15s ease-out;
353
+ }
354
+ td {
355
+ @apply relative text-sm text-gray-900 border-b border-solid border-gray-50 break-words flex items-center;
356
+ padding: 7px 12px;
357
+ height: 47px;
358
+ &:last-of-type {
359
+ max-width: unset !important;
360
+ }
361
+ }
362
+ &:last-child {
363
+ td {
364
+ @apply border-b-0;
365
+ }
366
+ }
367
+ }
368
+ }
369
+ }
370
+ &__truncate {
371
+ @apply truncate w-full;
372
+ }
373
+ &--sticky-column {
374
+ .table {
375
+ thead {
376
+ tr {
377
+ th {
378
+ &:first-child {
379
+ @apply sticky left-0 z-20;
380
+ }
381
+ }
382
+ }
383
+ }
384
+ tbody {
385
+ tr {
386
+ td {
387
+ &:first-child {
388
+ @apply sticky left-0 border-r bg-white;
389
+ z-index: 2;
390
+ }
391
+ }
392
+ }
393
+ }
394
+ }
395
+ &-with-checkbox {
396
+ .table {
397
+ thead {
398
+ tr {
399
+ th {
400
+ &:nth-child(2) {
401
+ @apply sticky z-20;
402
+ left: 45px;
403
+ }
404
+ }
405
+ }
406
+ }
407
+ tbody {
408
+ tr {
409
+ td {
410
+ &:nth-child(2) {
411
+ @apply sticky border-r bg-white;
412
+ left: 45px;
413
+ z-index: 2;
414
+ }
415
+ }
416
+ }
417
+ }
418
+ }
419
+ }
420
+ &-two {
421
+ .table {
422
+ thead {
423
+ tr {
424
+ th {
425
+ &:nth-child(2) {
426
+ @apply sticky z-20;
427
+ left: 300px;
428
+ }
429
+ }
430
+ }
431
+ }
432
+ tbody {
433
+ tr {
434
+ td {
435
+ &:nth-child(2) {
436
+ @apply sticky border-r bg-white;
437
+ left: 300px;
438
+ z-index: 2;
439
+ }
440
+ }
441
+ }
442
+ }
443
+ }
444
+ }
445
+ }
446
+ &--sticky-header {
447
+ .table {
448
+ thead {
449
+ tr {
450
+ @apply sticky top-0 z-10;
451
+ }
452
+ }
453
+ }
454
+ }
455
+ &__no-result {
456
+ @apply w-full border-none;
457
+ position: unset;
458
+ }
459
+ &__hovercontent {
460
+ @apply flex items-center absolute inset-y-0;
461
+ right: 10px;
462
+ transition: all 0.15s ease-out;
463
+ &--show {
464
+ opacity: 1;
465
+ }
466
+ &--hide {
467
+ opacity: 0;
468
+ }
469
+ }
470
+ &__footer {
471
+ @apply flex justify-between items-center p-3;
472
+ }
473
+ }
474
+
475
+ .search-icon {
476
+ span {
477
+ &::after {
478
+ @apply absolute m-auto;
479
+ content: "";
480
+ width: 18px;
481
+ height: 18px;
482
+ top: 10px;
483
+ right: 14px;
484
+ background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='iso-8859-1'%3F%3E%3C!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Capa_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='18px' height='18px' fill='%2393a3b8' viewBox='0 0 28.931 28.932' style='enable-background:new 0 0 28.931 28.932;' xml:space='preserve'%3E%3Cg%3E%3Cpath d='M28.344,25.518l-6.114-6.115c1.486-2.067,2.303-4.537,2.303-7.137c0-3.275-1.275-6.355-3.594-8.672 C18.625,1.278,15.543,0,12.266,0C8.99,0,5.909,1.275,3.593,3.594C1.277,5.909,0.001,8.99,0.001,12.266 c0,3.276,1.275,6.356,3.592,8.674c2.316,2.316,5.396,3.594,8.673,3.594c2.599,0,5.067-0.813,7.136-2.303l6.114,6.115 c0.392,0.391,0.902,0.586,1.414,0.586c0.513,0,1.024-0.195,1.414-0.586C29.125,27.564,29.125,26.299,28.344,25.518z M6.422,18.111 c-1.562-1.562-2.421-3.639-2.421-5.846S4.86,7.983,6.422,6.421c1.561-1.562,3.636-2.422,5.844-2.422s4.284,0.86,5.845,2.422 c1.562,1.562,2.422,3.638,2.422,5.845s-0.859,4.283-2.422,5.846c-1.562,1.562-3.636,2.42-5.845,2.42S7.981,19.672,6.422,18.111z'/%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3Cg%3E%3C/g%3E%3C/svg%3E%0A");
485
+ }
486
+ }
487
+ }
488
+
489
+ </style>
@@ -0,0 +1,85 @@
1
+ <template>
2
+ <div>
3
+ <!-- <pre class="text-[0.5rem] p-2 border border-gray-300 rounded-lg max-h-72 overflow-y-auto">{{ selectedRows.map(x => x?._id)}}</pre> -->
4
+ <UTable :checkable="true"
5
+ :table-loading="loading"
6
+ v-model:selectedRows.sync="selectedRows"
7
+ paginated
8
+ :checked-rows.sync="checkedRows"
9
+ backend-pagination
10
+ :per-page="limit"
11
+ :headers="studentHeader"
12
+ :items="studentData"
13
+ :total="studentData.length"
14
+ :default-sort-direction="defaultSortOrder"
15
+ default-sort=""
16
+ :current-page="offset"
17
+ @changePage="onPageChange"
18
+ @sort="onSort"
19
+ @mouseenter="select"
20
+ @mouseleave="(selectedIndex = null), (selected = null)">
21
+ <template #[`item.firstName`]="{ row, rowIndex }">
22
+ <div class="space-y-0.5">
23
+ <div class="block text-sm font-medium leading-snug">
24
+ {{rowIndex + capitalizeText(row?.firstName) + " " + capitalizeText(row?.lastName) }}
25
+ </div>
26
+ <div class="text-xs font-medium text-gray-900 leading-[normal]">
27
+ {{ row?.referenceId }}
28
+ </div>
29
+ </div>
30
+ </template>
31
+ <template #[`item.activeUser`]="{ row, rowIndex }">
32
+ {{ rowIndex }}
33
+ {{ row?.activeUser?.user?.firstName }}
34
+ {{ row?.activeUser?.user?.lastName }}
35
+ </template>
36
+ </UTable>
37
+ </div>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import { ref} from 'vue';
42
+ import UTable from './UTable.vue';
43
+ import {
44
+ studentData,
45
+ studentHeader,
46
+ } from "../../data/table";
47
+ import { capitalizeText } from '../../utils/lodash';
48
+
49
+ //TODO: Dashboard Table
50
+ const selectedRows = ref([])
51
+ const loading = ref(false)
52
+ const checkedRows = ref([]);
53
+ const defaultSortOrder = ref("asc");
54
+ const limit = ref(5);
55
+ const offset = ref(1);
56
+ const selectedIndex = ref<{ index: string } | null>(null);
57
+ const selected = ref<{ index: string } | null>(null);
58
+
59
+ const select = (_item: any, index: any) => {
60
+ selectedIndex.value = index;
61
+ };
62
+
63
+ const onSort = (field: string, order: string) => {
64
+ const modifier = order === "desc" ? -1 : 1;
65
+ studentData.sort((a:any, b:any) => {
66
+ if (a[field] < b[field]) return -1 * modifier;
67
+ if (a[field] > b[field]) return 1 * modifier;
68
+ return 0;
69
+ });
70
+ };
71
+
72
+ const onPageChange = (offsetData: number) => {
73
+ offset.value = offsetData;
74
+ loading.value = true
75
+ console.log("@changePage:", offsetData);
76
+ setTimeout(() => {
77
+ loading.value = false
78
+ }, 800);
79
+ };
80
+
81
+ </script>
82
+
83
+ <style scoped>
84
+
85
+ </style>
@@ -209,7 +209,6 @@ const bindProps = {
209
209
  };
210
210
 
211
211
  const onInputChanged = (_formattedNumber: string, phoneObject: any) => {
212
- console.log(phoneObject.number); // Debugging
213
212
  hasError.value = phoneObject.valid ? true : false;
214
213
  emit("update:modelValue", phoneObject.number);
215
214
  };
@@ -0,0 +1,30 @@
1
+ export function getValueByPath(obj: any, path: string) {
2
+ return path
3
+ .split(".")
4
+ .reduce(
5
+ (o: { [x: string]: any }, i: string | number) => (o ? o[i] : null),
6
+ obj
7
+ );
8
+ }
9
+
10
+ export function defaultFilter(value: { toString: () => string }, search: any) {
11
+ return value.toString().toLowerCase().includes(search);
12
+ }
13
+
14
+ export function indexOf(
15
+ array: string | any[],
16
+ obj: any,
17
+ fn?: (arg0: any, arg1: any) => any
18
+ ) {
19
+ if (!array) return -1;
20
+
21
+ if (!fn || typeof fn !== "function") return array.indexOf(obj);
22
+
23
+ for (let i = 0; i < array.length; i++) {
24
+ if (fn(array[i], obj)) {
25
+ return i;
26
+ }
27
+ }
28
+
29
+ return -1;
30
+ }