vs-datatable 0.6.1 → 1.0.0

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 (33) hide show
  1. package/README.md +1033 -9
  2. package/dist/App_OLD.vue.d.ts +2 -0
  3. package/dist/api/mock/paymentMethods.d.ts +1 -0
  4. package/dist/api/mock/paymentStatuses.d.ts +1 -0
  5. package/dist/components/DropDownButton.vue.d.ts +2 -0
  6. package/dist/components/VsDataTable.vue.d.ts +51 -5
  7. package/dist/components/VsDataTableFilterDropdown.vue.d.ts +71 -0
  8. package/dist/components/VsPagination.vue.d.ts +2 -2
  9. package/dist/components/VsRowsPerPage.vue.d.ts +2 -2
  10. package/dist/components/VsSearch.vue.d.ts +2 -2
  11. package/dist/components/layout/VsColumn.vue.d.ts +47 -0
  12. package/dist/components/layout/VsDFlex.vue.d.ts +36 -0
  13. package/dist/components/layout/VsRow.vue.d.ts +83 -0
  14. package/dist/components/ui/VsMultiSelect.vue.d.ts +11 -0
  15. package/dist/composables/useAsyncOption.d.ts +10 -0
  16. package/dist/composables/useColumnFilter.d.ts +23 -0
  17. package/dist/composables/useDataTable.d.ts +12 -2
  18. package/dist/composables/useDataTablePagination.d.ts +1 -1
  19. package/dist/composables/useDataTableSort.d.ts +1 -2
  20. package/dist/composables/useExpandable.d.ts +13 -0
  21. package/dist/composables/useVsHelper.d.ts +1 -0
  22. package/dist/index.css +1 -1
  23. package/dist/types/datatable.d.ts +88 -16
  24. package/dist/utils/datatable.d.ts +8 -1
  25. package/dist/utils/filterFns.d.ts +1 -0
  26. package/dist/utils/filters.d.ts +11 -0
  27. package/dist/views/DemoLayout.vue.d.ts +3 -0
  28. package/dist/vs-datatable.es.js +2279 -403
  29. package/dist/vs-datatable.umd.js +2 -2
  30. package/package.json +4 -1
  31. package/src/styles/base.scss +769 -79
  32. package/src/styles/base_OLD.scss +1089 -0
  33. package/src/styles/vs-layout.css +645 -0
package/README.md CHANGED
@@ -4,16 +4,20 @@ A lightweight, feature-rich Vue 3 data table component with sorting, pagination,
4
4
 
5
5
  ## Features
6
6
 
7
- - 🔍 **Search & Filter** - Built-in search functionality with customizable search input
8
- - 📊 **Sorting** - Multi-column sorting with visual indicators and priority support
9
- - 📄 **Pagination** - Server-side and client-side pagination with customizable controls
7
+ - 🔍 **Advanced Search & Filtering** - Built-in search with column-specific filters and operators
8
+ - 📊 **Enhanced Sorting** - Multi-column sorting with priority badges and visual indicators
9
+ - 📄 **Flexible Pagination** - Server-side and client-side pagination with customizable controls
10
10
  - ✅ **Row Selection** - Single and multi-row selection with checkbox controls
11
+ - 📁 **Expandable Rows** - Row expansion with accordion mode and loading states
11
12
  - 🎨 **Highly Customizable** - Extensive CSS variables, themes, and slot support
12
- - 📱 **Responsive** - Mobile-friendly design with no external dependencies
13
- - 🚀 **Performance** - Optimized for large datasets with server-side support
14
- - 🎯 **TypeScript** - Full TypeScript support with type definitions
13
+ - 📱 **Responsive Design** - Mobile-friendly with no external dependencies
14
+ - 🚀 **Performance Optimized** - Optimized for large datasets with server-side support
15
+ - 🎯 **TypeScript** - Full TypeScript support with comprehensive type definitions
15
16
  - 🎭 **Zero Dependencies** - No Bootstrap, FontAwesome, or other external libraries
16
17
  - 🎨 **Theme System** - Built-in themes and easy customization via CSS variables
18
+ - 🔧 **Column Filters** - Multiple filter types: text, multi-select, number-range, date-range, custom
19
+ - 📈 **Async Options** - Support for async filter options with caching
20
+ - 🎪 **Floating UI** - Modern dropdown positioning with @floating-ui/dom
17
21
 
18
22
  ## Key Features
19
23
 
@@ -40,11 +44,26 @@ A lightweight, feature-rich Vue 3 data table component with sorting, pagination,
40
44
  - Enhanced slot system
41
45
  - Comprehensive documentation
42
46
 
43
- ### 🔄 **Flexible Sorting**
47
+ ### 🔄 **Enhanced Sorting**
44
48
  - Client-side and server-side sorting support
45
- - Multi-column sorting with priority
49
+ - Multi-column sorting with priority badges
46
50
  - Visual sort indicators with SVG icons
47
51
  - v-model:sort support for reactive sorting
52
+ - Priority-based sorting with numbered badges
53
+
54
+ ### 📁 **Expandable Rows**
55
+ - Row expansion with custom content slots
56
+ - Accordion mode for single-row expansion
57
+ - Loading states for async content
58
+ - Programmatic control of expanded rows
59
+ - Custom expand/collapse icons
60
+
61
+ ### 🔧 **Advanced Column Filtering**
62
+ - Multiple filter types: text, multi-select, number-range, date-range, custom
63
+ - Rich operators for each filter type
64
+ - Async filter options with caching
65
+ - Custom filter slots for complex scenarios
66
+ - Floating UI positioning with collision detection
48
67
 
49
68
  ## Installation
50
69
 
@@ -113,7 +132,9 @@ app.use(VsDataTable)
113
132
  | `rows` | `any[]` | `[]` | Array of data objects |
114
133
  | `loading` | `boolean` | `false` | Shows loading state |
115
134
  | `showSearch` | `boolean` | `true` | Enable/disable search functionality |
116
- | `showRowEntries` | `boolean` | `true` | Show "Showing X to Y of Z entries" |
135
+ | `showHeader` | `boolean` | `true` | Show/hide table header |
136
+ | `showFooter` | `boolean` | `true` | Show/hide table footer |
137
+ | `headerText` | `string` | `''` | Header text for the table |
117
138
  | `itemSelected` | `any[] \| null` | `null` | Controlled selection state |
118
139
  | `tablename` | `string` | `"default-table"` | Unique identifier for the table |
119
140
  | `tableClass` | `string \| string[] \| Record<string, any>` | - | Custom CSS classes for table |
@@ -132,7 +153,11 @@ app.use(VsDataTable)
132
153
  | `noDataDescription` | `string` | `'Try adjusting your search criteria'` | Description for no data state |
133
154
  | `entriesText` | `string` | `'entries'` | Text for pagination info |
134
155
  | `maxVisiblePages` | `number` | `5` | Maximum visible pagination pages |
156
+ | `rowsPerPage` | `number` | `10` | Initial rows per page |
135
157
  | `rowKey` | `string \| ((item: any, index: number) => string \| number)` | `'id'` | Key field for row identification |
158
+ | `expandable` | `boolean` | `false` | Enable row expansion functionality |
159
+ | `accordion` | `boolean` | `false` | Accordion mode (only one row expanded at a time) |
160
+ | `expanded` | `(string \| number)[]` | `[]` | Controlled expanded rows state |
136
161
 
137
162
  ### Column Definition
138
163
 
@@ -143,6 +168,14 @@ interface Column {
143
168
  width?: string; // Column width percentage
144
169
  sortable?: boolean; // Enable sorting
145
170
  isKey?: boolean; // Primary key field
171
+ filter?: { // Column filter configuration
172
+ type: 'text' | 'multi-select' | 'number-range' | 'date-range' | 'custom';
173
+ operators?: string[]; // Custom operators for the filter
174
+ asyncOptions?: () => Promise<string[]>; // Async options for multi-select
175
+ filterFn?: (cellValue: any, filterValue: any, row: any) => boolean; // Custom filter function
176
+ filterKey?: string; // Key for custom filter functions
177
+ custom?: string; // Custom filter slot name
178
+ };
146
179
  }
147
180
  ```
148
181
 
@@ -174,6 +207,15 @@ interface Sort {
174
207
  | `update:serverOptions` | `(options: ServerOptions)` | Fired when server options change |
175
208
  | `update:serverItemsLength` | `(length: number)` | Fired when total items count changes |
176
209
  | `update:sort` | `(sort: Sort[])` | v-model:sort support for reactive sorting |
210
+ | `update:expanded` | `(expanded: (string \| number)[])` | Fired when expanded rows change |
211
+ | `expand-row` | `{ row: any, index: number, rowId: string \| number }` | Fired when a row is expanded |
212
+ | `collapse-row` | `{ row: any, index: number, rowId: string \| number }` | Fired when a row is collapsed |
213
+ | `filter-change` | `Record<string, ColumnFilter>` | Fired when column filters change |
214
+ | `table-mounted` | `()` | Fired when table is mounted |
215
+ | `table-unmounted` | `()` | Fired when table is unmounted |
216
+ | `table-before-mount` | `()` | Fired before table mounts |
217
+ | `data-loaded` | `(data: any[])` | Fired when data is loaded |
218
+ | `data-error` | `(error: any)` | Fired when data loading fails |
177
219
 
178
220
  ## Slots
179
221
 
@@ -209,6 +251,39 @@ interface Sort {
209
251
  </template>
210
252
  ```
211
253
 
254
+ ### Expandable Row Slots
255
+ ```vue
256
+ <template #row-expanded="{ item, index }">
257
+ <div class="expanded-content">
258
+ <h4>Details for {{ item.name }}</h4>
259
+ <p>{{ item.description }}</p>
260
+ </div>
261
+ </template>
262
+
263
+ <template #row-expanded-loader="{ item, index }">
264
+ <div class="custom-loader">
265
+ Loading details for {{ item.name }}...
266
+ </div>
267
+ </template>
268
+ ```
269
+
270
+ ### Custom Filter Slots
271
+ ```vue
272
+ <!-- Custom filter slot -->
273
+ <template #StatusFilterSlot="{ filter, apply, clear }">
274
+ <div class="custom-filter">
275
+ <label>Status Filter</label>
276
+ <select v-model="filter.value">
277
+ <option value="">All</option>
278
+ <option value="active">Active</option>
279
+ <option value="inactive">Inactive</option>
280
+ </select>
281
+ <button @click="apply">Apply</button>
282
+ <button @click="clear">Clear</button>
283
+ </div>
284
+ </template>
285
+ ```
286
+
212
287
  ## Advanced Usage
213
288
 
214
289
  ### Server-Side Pagination & Sorting
@@ -381,6 +456,431 @@ The component includes built-in SVG sort icons that automatically show the curre
381
456
  - **Priority Badge**: Shows sort priority number for multi-column sorting
382
457
  - **Hover Effects**: Visual feedback on sortable columns
383
458
 
459
+ ### Enhanced Sorting with Priority Badges
460
+
461
+ VsDataTable features advanced sorting capabilities with visual priority indicators:
462
+
463
+ #### Priority-Based Sorting
464
+ When multiple columns are sorted, each column displays a priority badge showing its sort order:
465
+
466
+ ```vue
467
+ <template>
468
+ <VsDataTable
469
+ :columns="columns"
470
+ :rows="data"
471
+ v-model:sort="sortState"
472
+ />
473
+ </template>
474
+
475
+ <script setup lang="ts">
476
+ const sortState = ref([
477
+ { field: 'name', order: 'asc', priority: 1 }, // Primary sort
478
+ { field: 'age', order: 'desc', priority: 2 }, // Secondary sort
479
+ { field: 'salary', order: 'asc', priority: 3 } // Tertiary sort
480
+ ])
481
+ </script>
482
+ ```
483
+
484
+ #### Visual Priority Indicators
485
+ - **Priority 1**: Primary sort column (highest priority)
486
+ - **Priority 2**: Secondary sort column
487
+ - **Priority 3+**: Additional sort columns
488
+ - **Badge Display**: Small numbered badges appear next to sort icons
489
+ - **Color Coding**: Different colors for different priority levels
490
+
491
+ #### Interactive Sorting
492
+ Users can click column headers to:
493
+ - **First Click**: Sort ascending with priority 1
494
+ - **Second Click**: Sort descending with priority 1
495
+ - **Third Click**: Remove sort
496
+ - **Shift+Click**: Add as secondary sort with priority 2
497
+ - **Ctrl+Click**: Add as tertiary sort with priority 3
498
+
499
+ #### Sort State Management
500
+ ```vue
501
+ <script setup lang="ts">
502
+ // Reactive sort state
503
+ const sortState = ref([])
504
+
505
+ // Programmatic sort control
506
+ const setSort = (field: string, order: 'asc' | 'desc', priority: number = 1) => {
507
+ // Remove existing sort for this field
508
+ sortState.value = sortState.value.filter(s => s.field !== field)
509
+
510
+ // Add new sort with priority
511
+ sortState.value.push({ field, order, priority })
512
+
513
+ // Reorder by priority
514
+ sortState.value.sort((a, b) => a.priority - b.priority)
515
+ }
516
+
517
+ // Clear all sorts
518
+ const clearSorts = () => {
519
+ sortState.value = []
520
+ }
521
+
522
+ // Clear specific field sort
523
+ const clearFieldSort = (field: string) => {
524
+ sortState.value = sortState.value.filter(s => s.field !== field)
525
+ }
526
+ </script>
527
+ ```
528
+
529
+ #### Server-Side Sorting Integration
530
+ For server-side sorting, the sort state is automatically sent to your server:
531
+
532
+ ```vue
533
+ <script setup lang="ts">
534
+ const handleServerOptionsChange = async (options) => {
535
+ const response = await fetch('/api/data', {
536
+ method: 'POST',
537
+ body: JSON.stringify({
538
+ page: options.page,
539
+ limit: options.rowsPerPage,
540
+ sort: options.sort // Array of { field, order, priority }
541
+ })
542
+ })
543
+
544
+ return response.json()
545
+ }
546
+ </script>
547
+ ```
548
+
549
+ #### Custom Sort Icons
550
+ You can customize the sort icons by overriding the CSS:
551
+
552
+ ```css
553
+ .vs-sort-icon {
554
+ /* Custom sort icon styling */
555
+ }
556
+
557
+ .vs-sort-priority {
558
+ /* Custom priority badge styling */
559
+ background: var(--vs-primary);
560
+ color: white;
561
+ border-radius: 50%;
562
+ font-size: 10px;
563
+ font-weight: bold;
564
+ }
565
+ ```
566
+
567
+ ## Column Filtering
568
+
569
+ VsDataTable provides powerful column-level filtering with multiple filter types and operators.
570
+
571
+ ### Filter Types
572
+
573
+ #### Text Filter
574
+ ```typescript
575
+ const columns = [
576
+ {
577
+ label: 'Name',
578
+ field: 'name',
579
+ filter: {
580
+ type: 'text',
581
+ operators: ['contains', 'equals', 'startsWith', 'endsWith']
582
+ }
583
+ }
584
+ ]
585
+ ```
586
+
587
+ **Available Operators:**
588
+ - `contains` - Contains text
589
+ - `doesNotContains` - Does not contain text
590
+ - `equals` - Exact match
591
+ - `doesNotEqual` - Not equal
592
+ - `startsWith` - Starts with text
593
+ - `endsWith` - Ends with text
594
+ - `empty` - Field is empty
595
+ - `notEmpty` - Field is not empty
596
+
597
+ #### Multi-Select Filter
598
+ ```typescript
599
+ const columns = [
600
+ {
601
+ label: 'Status',
602
+ field: 'status',
603
+ filter: {
604
+ type: 'multi-select',
605
+ operators: ['in', 'notIn']
606
+ }
607
+ }
608
+ ]
609
+ ```
610
+
611
+ #### Number Range Filter
612
+ ```typescript
613
+ const columns = [
614
+ {
615
+ label: 'Age',
616
+ field: 'age',
617
+ filter: {
618
+ type: 'number-range',
619
+ operators: ['between', 'equals', 'greaterThan', 'lessThan', 'empty', 'notEmpty']
620
+ }
621
+ }
622
+ ]
623
+ ```
624
+
625
+ #### Date Range Filter
626
+ ```typescript
627
+ const columns = [
628
+ {
629
+ label: 'Created Date',
630
+ field: 'createdAt',
631
+ filter: {
632
+ type: 'date-range',
633
+ operators: ['between', 'equals', 'before', 'after', 'empty', 'notEmpty']
634
+ }
635
+ }
636
+ ]
637
+ ```
638
+
639
+ #### Custom Filter
640
+ ```typescript
641
+ const columns = [
642
+ {
643
+ label: 'Custom Field',
644
+ field: 'custom',
645
+ filter: {
646
+ type: 'custom',
647
+ custom: 'CustomFilterSlot'
648
+ }
649
+ }
650
+ ]
651
+ ```
652
+
653
+ ### Async Filter Options
654
+
655
+ For multi-select filters, you can load options asynchronously:
656
+
657
+ ```typescript
658
+ const columns = [
659
+ {
660
+ label: 'Department',
661
+ field: 'department',
662
+ filter: {
663
+ type: 'multi-select',
664
+ asyncOptions: async () => {
665
+ const response = await fetch('/api/departments')
666
+ return response.json()
667
+ }
668
+ }
669
+ }
670
+ ]
671
+ ```
672
+
673
+ ### Complete Filtering Example
674
+
675
+ ```vue
676
+ <template>
677
+ <VsDataTable
678
+ :columns="columns"
679
+ :rows="data"
680
+ @filter-change="handleFilterChange"
681
+ >
682
+ <!-- Custom filter slot -->
683
+ <template #CustomFilterSlot="{ filter, apply, clear }">
684
+ <div class="custom-filter">
685
+ <label>Custom Filter</label>
686
+ <input
687
+ type="text"
688
+ v-model="filter.value"
689
+ placeholder="Enter custom value"
690
+ />
691
+ <button @click="apply">Apply</button>
692
+ <button @click="clear">Clear</button>
693
+ </div>
694
+ </template>
695
+ </VsDataTable>
696
+ </template>
697
+
698
+ <script setup lang="ts">
699
+ const columns = [
700
+ { label: 'ID', field: 'id', width: '10%' },
701
+ {
702
+ label: 'Name',
703
+ field: 'name',
704
+ filter: {
705
+ type: 'text',
706
+ operators: ['contains', 'equals', 'startsWith']
707
+ }
708
+ },
709
+ {
710
+ label: 'Age',
711
+ field: 'age',
712
+ filter: {
713
+ type: 'number-range',
714
+ operators: ['between', 'greaterThan', 'lessThan']
715
+ }
716
+ },
717
+ {
718
+ label: 'Status',
719
+ field: 'status',
720
+ filter: {
721
+ type: 'multi-select',
722
+ asyncOptions: async () => {
723
+ return ['Active', 'Inactive', 'Pending']
724
+ }
725
+ }
726
+ },
727
+ {
728
+ label: 'Created Date',
729
+ field: 'createdAt',
730
+ filter: {
731
+ type: 'date-range',
732
+ operators: ['between', 'before', 'after']
733
+ }
734
+ }
735
+ ]
736
+
737
+ const handleFilterChange = (filters) => {
738
+ console.log('Active filters:', filters)
739
+ // Handle filter changes for server-side filtering
740
+ }
741
+ </script>
742
+ ```
743
+
744
+ ## Expandable Rows
745
+
746
+ VsDataTable supports row expansion functionality with custom content, loading states, and accordion mode.
747
+
748
+ ### Basic Expandable Rows
749
+
750
+ ```vue
751
+ <template>
752
+ <VsDataTable
753
+ :columns="columns"
754
+ :rows="data"
755
+ expandable
756
+ @expand-row="handleExpand"
757
+ @collapse-row="handleCollapse"
758
+ >
759
+ <template #row-expanded="{ item, index }">
760
+ <div class="expanded-content">
761
+ <h4>Details for {{ item.name }}</h4>
762
+ <p><strong>Description:</strong> {{ item.description }}</p>
763
+ <p><strong>Created:</strong> {{ item.createdAt }}</p>
764
+ </div>
765
+ </template>
766
+ </VsDataTable>
767
+ </template>
768
+
769
+ <script setup lang="ts">
770
+ const handleExpand = ({ row, index, rowId }) => {
771
+ console.log('Row expanded:', row)
772
+ }
773
+
774
+ const handleCollapse = ({ row, index, rowId }) => {
775
+ console.log('Row collapsed:', row)
776
+ }
777
+ </script>
778
+ ```
779
+
780
+ ### Accordion Mode
781
+
782
+ Enable accordion mode to allow only one row to be expanded at a time:
783
+
784
+ ```vue
785
+ <template>
786
+ <VsDataTable
787
+ :columns="columns"
788
+ :rows="data"
789
+ expandable
790
+ accordion
791
+ @expand-row="handleExpand"
792
+ >
793
+ <template #row-expanded="{ item, index }">
794
+ <div class="expanded-content">
795
+ <!-- Custom expanded content -->
796
+ </div>
797
+ </template>
798
+ </VsDataTable>
799
+ </template>
800
+ ```
801
+
802
+ ### Controlled Expansion State
803
+
804
+ You can control which rows are expanded using the `expanded` prop:
805
+
806
+ ```vue
807
+ <template>
808
+ <VsDataTable
809
+ :columns="columns"
810
+ :rows="data"
811
+ expandable
812
+ v-model:expanded="expandedRows"
813
+ >
814
+ <template #row-expanded="{ item, index }">
815
+ <div class="expanded-content">
816
+ <!-- Custom content -->
817
+ </div>
818
+ </template>
819
+ </VsDataTable>
820
+ </template>
821
+
822
+ <script setup lang="ts">
823
+ const expandedRows = ref<(string | number)[]>(['1', '3'])
824
+
825
+ // Programmatically expand/collapse rows
826
+ const toggleRow = (rowId: string | number) => {
827
+ const index = expandedRows.value.indexOf(rowId)
828
+ if (index > -1) {
829
+ expandedRows.value.splice(index, 1)
830
+ } else {
831
+ expandedRows.value.push(rowId)
832
+ }
833
+ }
834
+ </script>
835
+ ```
836
+
837
+ ### Loading States
838
+
839
+ You can show loading states while fetching expanded content:
840
+
841
+ ```vue
842
+ <template>
843
+ <VsDataTable
844
+ ref="tableRef"
845
+ :columns="columns"
846
+ :rows="data"
847
+ expandable
848
+ @expand-row="handleExpand"
849
+ >
850
+ <template #row-expanded="{ item, index }">
851
+ <div class="expanded-content">
852
+ <h4>Details for {{ item.name }}</h4>
853
+ <p>{{ item.details }}</p>
854
+ </div>
855
+ </template>
856
+
857
+ <template #row-expanded-loader="{ item, index }">
858
+ <div class="loading-spinner">
859
+ Loading details for {{ item.name }}...
860
+ </div>
861
+ </template>
862
+ </VsDataTable>
863
+ </template>
864
+
865
+ <script setup lang="ts">
866
+ const tableRef = ref()
867
+
868
+ const handleExpand = async ({ row, index, rowId }) => {
869
+ // Set loading state
870
+ tableRef.value.setRowLoading(rowId, true)
871
+
872
+ try {
873
+ // Fetch additional data
874
+ const details = await fetchRowDetails(row.id)
875
+ row.details = details
876
+ } finally {
877
+ // Clear loading state
878
+ tableRef.value.setRowLoading(rowId, false)
879
+ }
880
+ }
881
+ </script>
882
+ ```
883
+
384
884
  ## Styling & Customization
385
885
 
386
886
  ### CSS Variables System
@@ -703,6 +1203,530 @@ onMounted(() => {
703
1203
  </style>
704
1204
  ```
705
1205
 
1206
+ ### Advanced Example with All Features
1207
+
1208
+ Here's a comprehensive example showcasing all the new features including column filtering, expandable rows, enhanced sorting, and more:
1209
+
1210
+ ```vue
1211
+ <template>
1212
+ <div class="advanced-datatable-demo">
1213
+ <h2>Advanced VsDataTable Demo</h2>
1214
+
1215
+ <VsDataTable
1216
+ ref="tableRef"
1217
+ :columns="columns"
1218
+ :rows="employees"
1219
+ :server-options="serverOptions"
1220
+ :server-items-length="totalEmployees"
1221
+ :loading="loading"
1222
+ v-model:item-selected="selectedEmployees"
1223
+ v-model:sort="sortState"
1224
+ v-model:expanded="expandedRows"
1225
+ expandable
1226
+ accordion
1227
+ header-text="Employee Management System"
1228
+ @update:server-options="fetchEmployees"
1229
+ @sort-changed="handleSortChange"
1230
+ @expand-row="loadEmployeeDetails"
1231
+ @collapse-row="clearEmployeeDetails"
1232
+ @filter-change="handleFilterChange"
1233
+ @row-click="viewEmployee"
1234
+ >
1235
+ <!-- Custom avatar cell -->
1236
+ <template #cell-avatar="{ item }">
1237
+ <img :src="item.avatar" :alt="item.name" class="employee-avatar" />
1238
+ </template>
1239
+
1240
+ <!-- Custom status cell -->
1241
+ <template #cell-status="{ item }">
1242
+ <span :class="`status-badge status-${item.status.toLowerCase()}`">
1243
+ {{ item.status }}
1244
+ </span>
1245
+ </template>
1246
+
1247
+ <!-- Custom salary cell -->
1248
+ <template #cell-salary="{ item }">
1249
+ ${{ formatNumber(item.salary) }}
1250
+ </template>
1251
+
1252
+ <!-- Custom actions cell -->
1253
+ <template #cell-actions="{ item }">
1254
+ <div class="action-buttons">
1255
+ <button class="btn-edit" @click.stop="editEmployee(item)">
1256
+ Edit
1257
+ </button>
1258
+ <button class="btn-delete" @click.stop="deleteEmployee(item)">
1259
+ Delete
1260
+ </button>
1261
+ </div>
1262
+ </template>
1263
+
1264
+ <!-- Expanded row content -->
1265
+ <template #row-expanded="{ item, index }">
1266
+ <div class="employee-details">
1267
+ <div class="detail-grid">
1268
+ <div class="detail-section">
1269
+ <h4>Personal Information</h4>
1270
+ <p><strong>Email:</strong> {{ item.email }}</p>
1271
+ <p><strong>Phone:</strong> {{ item.phone }}</p>
1272
+ <p><strong>Birth Date:</strong> {{ formatDate(item.birthDate) }}</p>
1273
+ </div>
1274
+
1275
+ <div class="detail-section">
1276
+ <h4>Work Information</h4>
1277
+ <p><strong>Department:</strong> {{ item.department }}</p>
1278
+ <p><strong>Position:</strong> {{ item.position }}</p>
1279
+ <p><strong>Hire Date:</strong> {{ formatDate(item.hireDate) }}</p>
1280
+ </div>
1281
+
1282
+ <div class="detail-section">
1283
+ <h4>Address</h4>
1284
+ <p>{{ item.address.street }}</p>
1285
+ <p>{{ item.address.city }}, {{ item.address.state }} {{ item.address.zip }}</p>
1286
+ </div>
1287
+
1288
+ <div class="detail-section">
1289
+ <h4>Recent Activity</h4>
1290
+ <ul class="activity-list">
1291
+ <li v-for="activity in item.recentActivity" :key="activity.id">
1292
+ <span class="activity-action">{{ activity.action }}</span>
1293
+ <span class="activity-date">{{ formatDate(activity.date) }}</span>
1294
+ </li>
1295
+ </ul>
1296
+ </div>
1297
+ </div>
1298
+ </div>
1299
+ </template>
1300
+
1301
+ <!-- Loading state for expanded rows -->
1302
+ <template #row-expanded-loader="{ item, index }">
1303
+ <div class="loading-details">
1304
+ <div class="spinner"></div>
1305
+ <span>Loading details for {{ item.name }}...</span>
1306
+ </div>
1307
+ </template>
1308
+
1309
+ <!-- Custom filter for department -->
1310
+ <template #DepartmentFilterSlot="{ filter, apply, clear }">
1311
+ <div class="custom-filter">
1312
+ <label>Department Filter</label>
1313
+ <select v-model="filter.value" class="filter-select">
1314
+ <option value="">All Departments</option>
1315
+ <option value="Engineering">Engineering</option>
1316
+ <option value="Marketing">Marketing</option>
1317
+ <option value="Sales">Sales</option>
1318
+ <option value="HR">Human Resources</option>
1319
+ <option value="Finance">Finance</option>
1320
+ </select>
1321
+ <div class="filter-actions">
1322
+ <button class="btn-apply" @click="apply">Apply</button>
1323
+ <button class="btn-clear" @click="clear">Clear</button>
1324
+ </div>
1325
+ </div>
1326
+ </template>
1327
+ </VsDataTable>
1328
+
1329
+ <!-- Bulk actions -->
1330
+ <div v-if="selectedEmployees.length" class="bulk-actions">
1331
+ <h3>Bulk Actions ({{ selectedEmployees.length }} selected)</h3>
1332
+ <button class="btn-bulk-edit" @click="bulkEdit">Edit Selected</button>
1333
+ <button class="btn-bulk-delete" @click="bulkDelete">Delete Selected</button>
1334
+ <button class="btn-bulk-export" @click="exportSelected">Export Selected</button>
1335
+ </div>
1336
+ </div>
1337
+ </template>
1338
+
1339
+ <script setup lang="ts">
1340
+ import { ref, onMounted } from 'vue'
1341
+ import { VsDataTable } from 'vs-datatable'
1342
+
1343
+ const tableRef = ref()
1344
+ const loading = ref(false)
1345
+ const totalEmployees = ref(0)
1346
+ const selectedEmployees = ref([])
1347
+ const expandedRows = ref<(string | number)[]>([])
1348
+
1349
+ const sortState = ref([
1350
+ { field: 'name', order: 'asc', priority: 1 }
1351
+ ])
1352
+
1353
+ const serverOptions = ref({
1354
+ page: 1,
1355
+ rowsPerPage: 10,
1356
+ sort: sortState.value
1357
+ })
1358
+
1359
+ const columns = [
1360
+ { label: 'Avatar', field: 'avatar', width: '8%' },
1361
+ {
1362
+ label: 'Name',
1363
+ field: 'name',
1364
+ sortable: true,
1365
+ filter: {
1366
+ type: 'text',
1367
+ operators: ['contains', 'equals', 'startsWith']
1368
+ }
1369
+ },
1370
+ {
1371
+ label: 'Department',
1372
+ field: 'department',
1373
+ sortable: true,
1374
+ filter: {
1375
+ type: 'custom',
1376
+ custom: 'DepartmentFilterSlot'
1377
+ }
1378
+ },
1379
+ {
1380
+ label: 'Position',
1381
+ field: 'position',
1382
+ sortable: true,
1383
+ filter: {
1384
+ type: 'text',
1385
+ operators: ['contains', 'equals']
1386
+ }
1387
+ },
1388
+ {
1389
+ label: 'Status',
1390
+ field: 'status',
1391
+ sortable: true,
1392
+ filter: {
1393
+ type: 'multi-select',
1394
+ asyncOptions: async () => ['Active', 'Inactive', 'On Leave', 'Terminated']
1395
+ }
1396
+ },
1397
+ {
1398
+ label: 'Salary',
1399
+ field: 'salary',
1400
+ sortable: true,
1401
+ filter: {
1402
+ type: 'number-range',
1403
+ operators: ['between', 'greaterThan', 'lessThan']
1404
+ }
1405
+ },
1406
+ {
1407
+ label: 'Hire Date',
1408
+ field: 'hireDate',
1409
+ sortable: true,
1410
+ filter: {
1411
+ type: 'date-range',
1412
+ operators: ['between', 'before', 'after']
1413
+ }
1414
+ },
1415
+ { label: 'Actions', field: 'actions', width: '12%' }
1416
+ ]
1417
+
1418
+ const employees = ref([
1419
+ {
1420
+ id: 1,
1421
+ name: 'John Smith',
1422
+ email: 'john.smith@company.com',
1423
+ phone: '+1-555-0123',
1424
+ birthDate: '1985-03-15',
1425
+ department: 'Engineering',
1426
+ position: 'Senior Developer',
1427
+ status: 'Active',
1428
+ salary: 95000,
1429
+ hireDate: '2020-01-15',
1430
+ avatar: '/avatars/john.jpg',
1431
+ address: {
1432
+ street: '123 Main St',
1433
+ city: 'San Francisco',
1434
+ state: 'CA',
1435
+ zip: '94102'
1436
+ },
1437
+ recentActivity: []
1438
+ }
1439
+ // ... more employees
1440
+ ])
1441
+
1442
+ // Utility functions
1443
+ const formatNumber = (value: number): string => {
1444
+ return new Intl.NumberFormat().format(value)
1445
+ }
1446
+
1447
+ const formatDate = (date: string): string => {
1448
+ return new Intl.DateTimeFormat('en-US').format(new Date(date))
1449
+ }
1450
+
1451
+ // Event handlers
1452
+ const fetchEmployees = async (options) => {
1453
+ loading.value = true
1454
+ try {
1455
+ const response = await api.getEmployees({
1456
+ page: options.page,
1457
+ limit: options.rowsPerPage,
1458
+ sort: options.sort,
1459
+ filters: getActiveFilters()
1460
+ })
1461
+ employees.value = response.data
1462
+ totalEmployees.value = response.total
1463
+ serverOptions.value = options
1464
+ } finally {
1465
+ loading.value = false
1466
+ }
1467
+ }
1468
+
1469
+ const handleSortChange = ({ sort }) => {
1470
+ console.log('Sort changed:', sort)
1471
+ sortState.value = sort
1472
+ }
1473
+
1474
+ const loadEmployeeDetails = async ({ row, index, rowId }) => {
1475
+ tableRef.value.setRowLoading(rowId, true)
1476
+
1477
+ try {
1478
+ const activity = await api.getEmployeeActivity(row.id)
1479
+ row.recentActivity = activity
1480
+ } finally {
1481
+ tableRef.value.setRowLoading(rowId, false)
1482
+ }
1483
+ }
1484
+
1485
+ const clearEmployeeDetails = ({ row, index, rowId }) => {
1486
+ row.recentActivity = []
1487
+ }
1488
+
1489
+ const handleFilterChange = (filters) => {
1490
+ console.log('Filters changed:', filters)
1491
+ // Reset to first page when filters change
1492
+ serverOptions.value.page = 1
1493
+ fetchEmployees(serverOptions.value)
1494
+ }
1495
+
1496
+ const viewEmployee = (employee) => {
1497
+ console.log('Viewing employee:', employee)
1498
+ }
1499
+
1500
+ const editEmployee = (employee) => {
1501
+ console.log('Editing employee:', employee)
1502
+ }
1503
+
1504
+ const deleteEmployee = (employee) => {
1505
+ console.log('Deleting employee:', employee)
1506
+ }
1507
+
1508
+ const bulkEdit = () => {
1509
+ console.log('Bulk editing:', selectedEmployees.value)
1510
+ }
1511
+
1512
+ const bulkDelete = () => {
1513
+ console.log('Bulk deleting:', selectedEmployees.value)
1514
+ }
1515
+
1516
+ const exportSelected = () => {
1517
+ console.log('Exporting:', selectedEmployees.value)
1518
+ }
1519
+
1520
+ const getActiveFilters = () => {
1521
+ // Return current active filters for server request
1522
+ return {}
1523
+ }
1524
+
1525
+ onMounted(() => {
1526
+ fetchEmployees(serverOptions.value)
1527
+ })
1528
+ </script>
1529
+
1530
+ <style scoped>
1531
+ .advanced-datatable-demo {
1532
+ padding: 20px;
1533
+ max-width: 1200px;
1534
+ margin: 0 auto;
1535
+ }
1536
+
1537
+ .employee-avatar {
1538
+ width: 40px;
1539
+ height: 40px;
1540
+ border-radius: 50%;
1541
+ object-fit: cover;
1542
+ }
1543
+
1544
+ .status-badge {
1545
+ padding: 4px 8px;
1546
+ border-radius: 4px;
1547
+ font-size: 12px;
1548
+ font-weight: 500;
1549
+ }
1550
+
1551
+ .status-active {
1552
+ background: #d4edda;
1553
+ color: #155724;
1554
+ }
1555
+
1556
+ .status-inactive {
1557
+ background: #f8d7da;
1558
+ color: #721c24;
1559
+ }
1560
+
1561
+ .status-on-leave {
1562
+ background: #fff3cd;
1563
+ color: #856404;
1564
+ }
1565
+
1566
+ .action-buttons {
1567
+ display: flex;
1568
+ gap: 8px;
1569
+ }
1570
+
1571
+ .btn-edit, .btn-delete {
1572
+ padding: 4px 8px;
1573
+ border: none;
1574
+ border-radius: 4px;
1575
+ cursor: pointer;
1576
+ font-size: 12px;
1577
+ }
1578
+
1579
+ .btn-edit {
1580
+ background: #007bff;
1581
+ color: white;
1582
+ }
1583
+
1584
+ .btn-delete {
1585
+ background: #dc3545;
1586
+ color: white;
1587
+ }
1588
+
1589
+ .employee-details {
1590
+ padding: 20px;
1591
+ background: #f8f9fa;
1592
+ border-radius: 8px;
1593
+ }
1594
+
1595
+ .detail-grid {
1596
+ display: grid;
1597
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
1598
+ gap: 20px;
1599
+ }
1600
+
1601
+ .detail-section h4 {
1602
+ margin: 0 0 10px 0;
1603
+ color: #333;
1604
+ font-size: 16px;
1605
+ }
1606
+
1607
+ .activity-list {
1608
+ list-style: none;
1609
+ padding: 0;
1610
+ margin: 0;
1611
+ }
1612
+
1613
+ .activity-list li {
1614
+ display: flex;
1615
+ justify-content: space-between;
1616
+ padding: 4px 0;
1617
+ border-bottom: 1px solid #eee;
1618
+ }
1619
+
1620
+ .activity-action {
1621
+ font-weight: 500;
1622
+ }
1623
+
1624
+ .activity-date {
1625
+ color: #666;
1626
+ font-size: 12px;
1627
+ }
1628
+
1629
+ .loading-details {
1630
+ display: flex;
1631
+ align-items: center;
1632
+ gap: 10px;
1633
+ padding: 20px;
1634
+ justify-content: center;
1635
+ }
1636
+
1637
+ .spinner {
1638
+ width: 20px;
1639
+ height: 20px;
1640
+ border: 2px solid #f3f3f3;
1641
+ border-top: 2px solid #007bff;
1642
+ border-radius: 50%;
1643
+ animation: spin 1s linear infinite;
1644
+ }
1645
+
1646
+ @keyframes spin {
1647
+ 0% { transform: rotate(0deg); }
1648
+ 100% { transform: rotate(360deg); }
1649
+ }
1650
+
1651
+ .custom-filter {
1652
+ padding: 16px;
1653
+ min-width: 200px;
1654
+ }
1655
+
1656
+ .custom-filter label {
1657
+ display: block;
1658
+ margin-bottom: 8px;
1659
+ font-weight: 500;
1660
+ }
1661
+
1662
+ .filter-select {
1663
+ width: 100%;
1664
+ padding: 8px;
1665
+ border: 1px solid #ddd;
1666
+ border-radius: 4px;
1667
+ margin-bottom: 12px;
1668
+ }
1669
+
1670
+ .filter-actions {
1671
+ display: flex;
1672
+ gap: 8px;
1673
+ }
1674
+
1675
+ .btn-apply, .btn-clear {
1676
+ padding: 6px 12px;
1677
+ border: none;
1678
+ border-radius: 4px;
1679
+ cursor: pointer;
1680
+ font-size: 12px;
1681
+ }
1682
+
1683
+ .btn-apply {
1684
+ background: #007bff;
1685
+ color: white;
1686
+ }
1687
+
1688
+ .btn-clear {
1689
+ background: #6c757d;
1690
+ color: white;
1691
+ }
1692
+
1693
+ .bulk-actions {
1694
+ margin-top: 20px;
1695
+ padding: 16px;
1696
+ background: #e9ecef;
1697
+ border-radius: 8px;
1698
+ }
1699
+
1700
+ .bulk-actions h3 {
1701
+ margin: 0 0 12px 0;
1702
+ font-size: 16px;
1703
+ }
1704
+
1705
+ .btn-bulk-edit, .btn-bulk-delete, .btn-bulk-export {
1706
+ padding: 8px 16px;
1707
+ border: none;
1708
+ border-radius: 4px;
1709
+ cursor: pointer;
1710
+ margin-right: 8px;
1711
+ }
1712
+
1713
+ .btn-bulk-edit {
1714
+ background: #28a745;
1715
+ color: white;
1716
+ }
1717
+
1718
+ .btn-bulk-delete {
1719
+ background: #dc3545;
1720
+ color: white;
1721
+ }
1722
+
1723
+ .btn-bulk-export {
1724
+ background: #17a2b8;
1725
+ color: white;
1726
+ }
1727
+ </style>
1728
+ ```
1729
+
706
1730
  ## Browser Support
707
1731
 
708
1732
  - Chrome 60+