infotable 1.4.1 → 1.6.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.
- checksums.yaml +4 -4
- data/README.md +179 -11
- data/app/assets/javascripts/infotable/controllers/drawer_controller.js +74 -378
- data/app/assets/javascripts/infotable/controllers/table_controller.js +112 -120
- data/app/assets/javascripts/infotable/vendor/sortable.esm.js +3363 -0
- data/app/controllers/infotable/tables_controller.rb +16 -5
- data/app/helpers/infotable/tables_helper.rb +10 -48
- data/app/views/infotable/tables/_column_visibility.html.erb +4 -4
- data/app/views/infotable/tables/_end_indicator.html.erb +8 -0
- data/app/views/infotable/tables/_filter_drawer.html.erb +6 -6
- data/app/views/infotable/tables/_next_page_frame.html.erb +1 -9
- data/app/views/infotable/tables/_pagination.html.erb +4 -7
- data/app/views/infotable/tables/_table.html.erb +1 -1
- data/app/views/infotable/tables/_table_rows.html.erb +9 -2
- data/app/views/infotable/tables/filters/_boolean.html.erb +1 -1
- data/app/views/infotable/tables/filters/_range.html.erb +1 -1
- data/app/views/infotable/tables/filters/_select.html.erb +2 -2
- data/app/views/infotable/tables/filters/_text.html.erb +2 -2
- data/app/views/infotable/tables/show.html.erb +2 -2
- data/config/importmap.rb +2 -2
- data/config/locales/de.yml +126 -0
- data/config/locales/el.yml +126 -0
- data/config/locales/en.yml +18 -4
- data/config/locales/es.yml +126 -0
- data/config/locales/fr.yml +126 -0
- data/config/locales/it.yml +126 -0
- data/config/locales/pl.yml +18 -4
- data/exe/infotable-mcp +13 -0
- data/lib/generators/infotable/install/templates/initializer.rb +5 -3
- data/lib/generators/infotable/table/table_generator.rb +23 -1
- data/lib/generators/infotable/table/templates/table.rb.tt +8 -1
- data/lib/infotable/base.rb +62 -4
- data/lib/infotable/configuration.rb +1 -1
- data/lib/infotable/engine.rb +1 -8
- data/lib/infotable/filter_processor.rb +10 -1
- data/lib/infotable/formatters/boolean_formatter.rb +4 -4
- data/lib/infotable/formatters/currency_formatter.rb +4 -12
- data/lib/infotable/formatters/date_formatter.rb +3 -9
- data/lib/infotable/formatters/duration_formatter.rb +51 -0
- data/lib/infotable/formatters/filesize_formatter.rb +14 -0
- data/lib/infotable/formatters/image_formatter.rb +31 -0
- data/lib/infotable/formatters/number_formatter.rb +46 -0
- data/lib/infotable/formatters/tags_formatter.rb +44 -0
- data/lib/infotable/formatters/truncate_formatter.rb +19 -0
- data/lib/infotable/mcp/introspection.rb +94 -0
- data/lib/infotable/mcp/server.rb +26 -0
- data/lib/infotable/mcp/table_checker.rb +80 -0
- data/lib/infotable/mcp/table_writer.rb +66 -0
- data/lib/infotable/mcp/tools.rb +122 -0
- data/lib/infotable/mcp.rb +30 -0
- data/lib/infotable/table_resolver.rb +9 -0
- data/lib/infotable/version.rb +1 -1
- data/lib/infotable.rb +22 -0
- metadata +53 -4
|
@@ -267,7 +267,7 @@ export default class extends Controller {
|
|
|
267
267
|
// Keep search and sort from the current frame URL
|
|
268
268
|
if (tableFrame?.src) {
|
|
269
269
|
const current = new URL(tableFrame.src, window.location.href).searchParams
|
|
270
|
-
for (const key of ["search", "sort", "sort_column", "sort_direction"]) {
|
|
270
|
+
for (const key of ["search", "per_page", "sort", "sort_column", "sort_direction"]) {
|
|
271
271
|
if (current.has(key) && !params.has(key)) params.set(key, current.get(key))
|
|
272
272
|
}
|
|
273
273
|
}
|
|
@@ -568,7 +568,7 @@ export default class extends Controller {
|
|
|
568
568
|
this.saveColumnVisibility(tableName, columnName, isVisible)
|
|
569
569
|
|
|
570
570
|
// Apply visibility to table
|
|
571
|
-
this.
|
|
571
|
+
this.applyToTable(tableName)
|
|
572
572
|
|
|
573
573
|
// Update badge
|
|
574
574
|
this.updateColumnBadge(tableName)
|
|
@@ -591,7 +591,7 @@ export default class extends Controller {
|
|
|
591
591
|
const columnName = checkbox.dataset.columnName
|
|
592
592
|
this.saveColumnVisibility(tableName, columnName, true)
|
|
593
593
|
})
|
|
594
|
-
this.
|
|
594
|
+
this.applyToTable(tableName)
|
|
595
595
|
this.updateColumnBadge(tableName)
|
|
596
596
|
|
|
597
597
|
// Dispatch event to update export link
|
|
@@ -613,7 +613,7 @@ export default class extends Controller {
|
|
|
613
613
|
const columnName = checkbox.dataset.columnName
|
|
614
614
|
this.saveColumnVisibility(tableName, columnName, false)
|
|
615
615
|
})
|
|
616
|
-
this.
|
|
616
|
+
this.applyToTable(tableName)
|
|
617
617
|
this.updateColumnBadge(tableName)
|
|
618
618
|
|
|
619
619
|
// Dispatch event to update export link
|
|
@@ -638,7 +638,7 @@ export default class extends Controller {
|
|
|
638
638
|
}
|
|
639
639
|
|
|
640
640
|
visibility[columnName] = isVisible
|
|
641
|
-
|
|
641
|
+
this.storeSet(key, JSON.stringify(visibility))
|
|
642
642
|
}
|
|
643
643
|
|
|
644
644
|
// Sync checkboxes with localStorage values or defaults
|
|
@@ -722,67 +722,12 @@ export default class extends Controller {
|
|
|
722
722
|
}
|
|
723
723
|
}
|
|
724
724
|
|
|
725
|
-
//
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// Try to find table in the wrapper or in turbo-frame
|
|
734
|
-
let table = tableWrapper.querySelector("table")
|
|
735
|
-
if (!table) {
|
|
736
|
-
// Table might be inside turbo-frame
|
|
737
|
-
const turboFrame = document.getElementById(`${tableName}_content`)
|
|
738
|
-
if (turboFrame) {
|
|
739
|
-
table = turboFrame.querySelector("table")
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
if (!table) {
|
|
744
|
-
console.warn(`Table not found for: ${tableName}`)
|
|
745
|
-
return
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// Find all table headers and cells
|
|
749
|
-
const headers = table.querySelectorAll("thead th[data-column-name]")
|
|
750
|
-
const rows = table.querySelectorAll("tbody tr")
|
|
751
|
-
|
|
752
|
-
if (headers.length === 0) {
|
|
753
|
-
console.warn(`No column headers found for table: ${tableName}`)
|
|
754
|
-
return
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// Get saved preferences from localStorage
|
|
758
|
-
const visibility = this.getColumnVisibility(tableName)
|
|
759
|
-
|
|
760
|
-
headers.forEach((header) => {
|
|
761
|
-
const columnName = header.dataset.columnName
|
|
762
|
-
const defaultVisible = header.dataset.defaultVisible === "true"
|
|
763
|
-
|
|
764
|
-
// Determine if column should be visible
|
|
765
|
-
let shouldShow
|
|
766
|
-
if (visibility && visibility.hasOwnProperty(columnName)) {
|
|
767
|
-
// Use saved preference
|
|
768
|
-
shouldShow = visibility[columnName]
|
|
769
|
-
} else {
|
|
770
|
-
// Use default visibility
|
|
771
|
-
shouldShow = defaultVisible
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// Apply visibility
|
|
775
|
-
header.style.display = shouldShow ? "" : "none"
|
|
776
|
-
|
|
777
|
-
// Hide/show corresponding cells in all rows
|
|
778
|
-
rows.forEach(row => {
|
|
779
|
-
const cells = row.querySelectorAll("td[data-column-name]")
|
|
780
|
-
const cell = Array.from(cells).find(c => c.dataset.columnName === columnName)
|
|
781
|
-
if (cell) {
|
|
782
|
-
cell.style.display = shouldShow ? "" : "none"
|
|
783
|
-
}
|
|
784
|
-
})
|
|
785
|
-
})
|
|
725
|
+
// The table controller applies order, visibility and pinning (it owns the table DOM); an explicit
|
|
726
|
+
// `order` replaces the stored one (used right after a drag, a pin or a reset)
|
|
727
|
+
applyToTable(tableName, order = null) {
|
|
728
|
+
document.dispatchEvent(new CustomEvent("infotable:apply-columns", {
|
|
729
|
+
detail: { tableName, order }
|
|
730
|
+
}))
|
|
786
731
|
}
|
|
787
732
|
|
|
788
733
|
// Update column badge count for a specific table
|
|
@@ -876,13 +821,10 @@ export default class extends Controller {
|
|
|
876
821
|
const columnItems = list.querySelectorAll('.column-item')
|
|
877
822
|
const newOrder = Array.from(columnItems).map(item => item.dataset.columnName)
|
|
878
823
|
|
|
879
|
-
//
|
|
880
|
-
this.
|
|
824
|
+
// Order and sticky positioning, applied to the table immediately
|
|
825
|
+
this.applyToTable(tableName, newOrder)
|
|
881
826
|
}
|
|
882
827
|
|
|
883
|
-
// Apply sticky positioning to table immediately
|
|
884
|
-
this.applyColumnPinningToTable(tableName)
|
|
885
|
-
|
|
886
828
|
// Dispatch event for export link update
|
|
887
829
|
document.dispatchEvent(new CustomEvent("infotable:columns-changed", {
|
|
888
830
|
detail: { tableName }
|
|
@@ -903,40 +845,53 @@ export default class extends Controller {
|
|
|
903
845
|
console.warn("Failed to parse column pinned from localStorage", e)
|
|
904
846
|
}
|
|
905
847
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
} else {
|
|
909
|
-
delete pinnedData[columnName]
|
|
910
|
-
}
|
|
848
|
+
// An unpin is stored as null (not removed): a column pinned by default must stay unpinned
|
|
849
|
+
pinnedData[columnName] = pinned || null
|
|
911
850
|
|
|
912
|
-
|
|
851
|
+
this.storeSet(key, JSON.stringify(pinnedData))
|
|
913
852
|
}
|
|
914
853
|
|
|
915
|
-
//
|
|
916
|
-
|
|
917
|
-
const key = `infotable_column_pinned_${tableName}`
|
|
854
|
+
// localStorage is missing or full in some browsers (private mode): the drawer then works for this page only
|
|
855
|
+
storeSet(key, value) {
|
|
918
856
|
try {
|
|
919
|
-
|
|
920
|
-
if (stored) {
|
|
921
|
-
const pinnedData = JSON.parse(stored)
|
|
922
|
-
return pinnedData[columnName] || null
|
|
923
|
-
}
|
|
857
|
+
localStorage.setItem(key, value)
|
|
924
858
|
} catch (e) {
|
|
925
|
-
console.warn("Failed to
|
|
859
|
+
console.warn("Failed to write to localStorage", e)
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
storeRemove(key) {
|
|
864
|
+
try {
|
|
865
|
+
localStorage.removeItem(key)
|
|
866
|
+
} catch (e) {
|
|
867
|
+
console.warn("Failed to write to localStorage", e)
|
|
926
868
|
}
|
|
927
|
-
return null
|
|
928
869
|
}
|
|
929
870
|
|
|
930
|
-
// Get
|
|
871
|
+
// Get pinned state from localStorage
|
|
872
|
+
getColumnPinned(tableName, columnName) {
|
|
873
|
+
return this.getAllColumnsPinned(tableName)[columnName] || null
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Pinned columns: what the user chose (localStorage) over the table's `pinned:` defaults.
|
|
877
|
+
// A column the user unpinned is stored as null and stays unpinned.
|
|
931
878
|
getAllColumnsPinned(tableName) {
|
|
932
879
|
const key = `infotable_column_pinned_${tableName}`
|
|
880
|
+
let stored = {}
|
|
933
881
|
try {
|
|
934
|
-
const
|
|
935
|
-
|
|
882
|
+
const raw = localStorage.getItem(key)
|
|
883
|
+
if (raw) stored = JSON.parse(raw)
|
|
936
884
|
} catch (e) {
|
|
937
885
|
console.warn("Failed to parse column pinned from localStorage", e)
|
|
938
|
-
return {}
|
|
939
886
|
}
|
|
887
|
+
|
|
888
|
+
const pinned = {}
|
|
889
|
+
const form = document.getElementById(`column-visibility-form-${tableName}`)
|
|
890
|
+
form?.querySelectorAll('.column-item').forEach(item => {
|
|
891
|
+
const defaultPinned = item.dataset.defaultPinned
|
|
892
|
+
if (defaultPinned) pinned[item.dataset.columnName] = defaultPinned
|
|
893
|
+
})
|
|
894
|
+
return { ...pinned, ...stored }
|
|
940
895
|
}
|
|
941
896
|
|
|
942
897
|
// Update pin button styling
|
|
@@ -1001,202 +956,6 @@ export default class extends Controller {
|
|
|
1001
956
|
this.saveColumnOrder(tableName, newOrder)
|
|
1002
957
|
}
|
|
1003
958
|
|
|
1004
|
-
// Apply pinning to table (add sticky positioning)
|
|
1005
|
-
applyColumnPinningToTable(tableName) {
|
|
1006
|
-
const tableWrapper = document.querySelector(`[data-infotable--table-name-value="${tableName}"]`)
|
|
1007
|
-
if (!tableWrapper) return
|
|
1008
|
-
|
|
1009
|
-
let table = tableWrapper.querySelector("table")
|
|
1010
|
-
if (!table) {
|
|
1011
|
-
const turboFrame = document.getElementById(`${tableName}_content`)
|
|
1012
|
-
if (turboFrame) {
|
|
1013
|
-
table = turboFrame.querySelector("table")
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
if (!table) return
|
|
1018
|
-
|
|
1019
|
-
const pinnedData = this.getAllColumnsPinned(tableName)
|
|
1020
|
-
const headers = Array.from(table.querySelectorAll("thead th[data-column-name]"))
|
|
1021
|
-
const rows = table.querySelectorAll("tbody tr")
|
|
1022
|
-
|
|
1023
|
-
// Calculate positions for pinned columns
|
|
1024
|
-
let leftOffset = 0
|
|
1025
|
-
let rightOffset = 0
|
|
1026
|
-
|
|
1027
|
-
// First pass: reset all columns
|
|
1028
|
-
headers.forEach(header => {
|
|
1029
|
-
header.style.position = ''
|
|
1030
|
-
header.style.left = ''
|
|
1031
|
-
header.style.right = ''
|
|
1032
|
-
header.style.zIndex = ''
|
|
1033
|
-
header.style.removeProperty('background-color')
|
|
1034
|
-
header.style.removeProperty('background-image')
|
|
1035
|
-
header.style.removeProperty('opacity')
|
|
1036
|
-
header.classList.remove('infotable-pinned-left', 'infotable-pinned-right', '!bg-gray-50', 'dark:!bg-gray-800', "after:content-['']", "after:absolute", "after:top-0", "after:right-0", "after:bottom-0", "after:w-5", "after:pointer-events-none", "after:bg-gradient-to-r", "after:from-transparent", "after:to-black/[0.04]", "after:z-[1]", "before:content-['']", "before:absolute", "before:top-0", "before:left-0", "before:bottom-0", "before:w-5", "before:pointer-events-none", "before:bg-gradient-to-l", "before:from-transparent", "before:to-black/[0.04]", "before:z-[1]")
|
|
1037
|
-
})
|
|
1038
|
-
|
|
1039
|
-
rows.forEach(row => {
|
|
1040
|
-
const cells = row.querySelectorAll("td[data-column-name]")
|
|
1041
|
-
cells.forEach(cell => {
|
|
1042
|
-
cell.style.position = ''
|
|
1043
|
-
cell.style.left = ''
|
|
1044
|
-
cell.style.right = ''
|
|
1045
|
-
cell.style.zIndex = ''
|
|
1046
|
-
cell.style.removeProperty('background-color')
|
|
1047
|
-
cell.style.removeProperty('background-image')
|
|
1048
|
-
cell.style.removeProperty('opacity')
|
|
1049
|
-
cell.classList.remove('infotable-pinned-left', 'infotable-pinned-right', '!bg-white', 'dark:!bg-gray-900', "after:content-['']", "after:absolute", "after:top-0", "after:right-0", "after:bottom-0", "after:w-5", "after:pointer-events-none", "after:bg-gradient-to-r", "after:from-transparent", "after:to-black/[0.04]", "after:z-[1]", "before:content-['']", "before:absolute", "before:top-0", "before:left-0", "before:bottom-0", "before:w-5", "before:pointer-events-none", "before:bg-gradient-to-l", "before:from-transparent", "before:to-black/[0.04]", "before:z-[1]")
|
|
1050
|
-
})
|
|
1051
|
-
})
|
|
1052
|
-
|
|
1053
|
-
// Find last left-pinned and first right-pinned columns
|
|
1054
|
-
let lastLeftPinned = null
|
|
1055
|
-
let firstRightPinned = null
|
|
1056
|
-
|
|
1057
|
-
headers.forEach(header => {
|
|
1058
|
-
const columnName = header.dataset.columnName
|
|
1059
|
-
const pinned = pinnedData[columnName]
|
|
1060
|
-
if (pinned === 'left') {
|
|
1061
|
-
lastLeftPinned = columnName
|
|
1062
|
-
}
|
|
1063
|
-
if (pinned === 'right' && !firstRightPinned) {
|
|
1064
|
-
firstRightPinned = columnName
|
|
1065
|
-
}
|
|
1066
|
-
})
|
|
1067
|
-
|
|
1068
|
-
// Apply sticky positioning for pinned columns
|
|
1069
|
-
headers.forEach(header => {
|
|
1070
|
-
const columnName = header.dataset.columnName
|
|
1071
|
-
const pinned = pinnedData[columnName]
|
|
1072
|
-
|
|
1073
|
-
if (pinned === 'left') {
|
|
1074
|
-
// Get existing inline styles
|
|
1075
|
-
const existingStyles = header.getAttribute('style') || ''
|
|
1076
|
-
|
|
1077
|
-
// Build comprehensive style string with all !important flags
|
|
1078
|
-
const newStyles = `
|
|
1079
|
-
position: sticky !important;
|
|
1080
|
-
left: ${leftOffset}px !important;
|
|
1081
|
-
z-index: 30 !important;
|
|
1082
|
-
background: #f9fafb !important;
|
|
1083
|
-
background-color: #f9fafb !important;
|
|
1084
|
-
background-image: none !important;
|
|
1085
|
-
background-position: initial !important;
|
|
1086
|
-
background-size: initial !important;
|
|
1087
|
-
background-repeat: initial !important;
|
|
1088
|
-
background-attachment: initial !important;
|
|
1089
|
-
opacity: 1 !important;
|
|
1090
|
-
${existingStyles}
|
|
1091
|
-
`.replace(/\s+/g, ' ').trim()
|
|
1092
|
-
|
|
1093
|
-
// Set all styles at once via setAttribute
|
|
1094
|
-
header.setAttribute('style', newStyles)
|
|
1095
|
-
header.classList.add('infotable-pinned-left', '!bg-gray-50', 'dark:!bg-gray-800')
|
|
1096
|
-
|
|
1097
|
-
// Mark last left-pinned column
|
|
1098
|
-
if (columnName === lastLeftPinned) {
|
|
1099
|
-
header.classList.add("after:content-['']", "after:absolute", "after:top-0", "after:right-0", "after:bottom-0", "after:w-5", "after:pointer-events-none", "after:bg-gradient-to-r", "after:from-transparent", "after:to-black/[0.04]", "after:z-[1]")
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
// Apply to cells in all rows
|
|
1103
|
-
rows.forEach(row => {
|
|
1104
|
-
const cell = row.querySelector(`td[data-column-name="${columnName}"]`)
|
|
1105
|
-
if (cell) {
|
|
1106
|
-
// Get existing inline styles
|
|
1107
|
-
const existingStyles = cell.getAttribute('style') || ''
|
|
1108
|
-
|
|
1109
|
-
// Build comprehensive style string
|
|
1110
|
-
const newStyles = `
|
|
1111
|
-
position: sticky !important;
|
|
1112
|
-
left: ${leftOffset}px !important;
|
|
1113
|
-
z-index: 15 !important;
|
|
1114
|
-
background: #ffffff !important;
|
|
1115
|
-
background-color: #ffffff !important;
|
|
1116
|
-
background-image: none !important;
|
|
1117
|
-
background-position: initial !important;
|
|
1118
|
-
background-size: initial !important;
|
|
1119
|
-
background-repeat: initial !important;
|
|
1120
|
-
background-attachment: initial !important;
|
|
1121
|
-
opacity: 1 !important;
|
|
1122
|
-
${existingStyles}
|
|
1123
|
-
`.replace(/\s+/g, ' ').trim()
|
|
1124
|
-
|
|
1125
|
-
cell.setAttribute('style', newStyles)
|
|
1126
|
-
cell.classList.add('infotable-pinned-left', '!bg-white', 'dark:!bg-gray-900')
|
|
1127
|
-
|
|
1128
|
-
if (columnName === lastLeftPinned) {
|
|
1129
|
-
cell.classList.add("after:content-['']", "after:absolute", "after:top-0", "after:right-0", "after:bottom-0", "after:w-5", "after:pointer-events-none", "after:bg-gradient-to-r", "after:from-transparent", "after:to-black/[0.04]", "after:z-[1]")
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
})
|
|
1133
|
-
|
|
1134
|
-
leftOffset += header.offsetWidth
|
|
1135
|
-
} else if (pinned === 'right') {
|
|
1136
|
-
// Get existing inline styles
|
|
1137
|
-
const existingStyles = header.getAttribute('style') || ''
|
|
1138
|
-
|
|
1139
|
-
// Build comprehensive style string
|
|
1140
|
-
const newStyles = `
|
|
1141
|
-
position: sticky !important;
|
|
1142
|
-
right: ${rightOffset}px !important;
|
|
1143
|
-
z-index: 30 !important;
|
|
1144
|
-
background: #f9fafb !important;
|
|
1145
|
-
background-color: #f9fafb !important;
|
|
1146
|
-
background-image: none !important;
|
|
1147
|
-
background-position: initial !important;
|
|
1148
|
-
background-size: initial !important;
|
|
1149
|
-
background-repeat: initial !important;
|
|
1150
|
-
background-attachment: initial !important;
|
|
1151
|
-
opacity: 1 !important;
|
|
1152
|
-
${existingStyles}
|
|
1153
|
-
`.replace(/\s+/g, ' ').trim()
|
|
1154
|
-
|
|
1155
|
-
header.setAttribute('style', newStyles)
|
|
1156
|
-
header.classList.add('infotable-pinned-right', '!bg-gray-50', 'dark:!bg-gray-800')
|
|
1157
|
-
|
|
1158
|
-
// Mark first right-pinned column
|
|
1159
|
-
if (columnName === firstRightPinned) {
|
|
1160
|
-
header.classList.add("before:content-['']", "before:absolute", "before:top-0", "before:left-0", "before:bottom-0", "before:w-5", "before:pointer-events-none", "before:bg-gradient-to-l", "before:from-transparent", "before:to-black/[0.04]", "before:z-[1]")
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
// Apply to cells in all rows
|
|
1164
|
-
rows.forEach(row => {
|
|
1165
|
-
const cell = row.querySelector(`td[data-column-name="${columnName}"]`)
|
|
1166
|
-
if (cell) {
|
|
1167
|
-
// Get existing inline styles
|
|
1168
|
-
const existingStyles = cell.getAttribute('style') || ''
|
|
1169
|
-
|
|
1170
|
-
// Build comprehensive style string
|
|
1171
|
-
const newStyles = `
|
|
1172
|
-
position: sticky !important;
|
|
1173
|
-
right: ${rightOffset}px !important;
|
|
1174
|
-
z-index: 15 !important;
|
|
1175
|
-
background: #ffffff !important;
|
|
1176
|
-
background-color: #ffffff !important;
|
|
1177
|
-
background-image: none !important;
|
|
1178
|
-
background-position: initial !important;
|
|
1179
|
-
background-size: initial !important;
|
|
1180
|
-
background-repeat: initial !important;
|
|
1181
|
-
background-attachment: initial !important;
|
|
1182
|
-
opacity: 1 !important;
|
|
1183
|
-
${existingStyles}
|
|
1184
|
-
`.replace(/\s+/g, ' ').trim()
|
|
1185
|
-
|
|
1186
|
-
cell.setAttribute('style', newStyles)
|
|
1187
|
-
cell.classList.add('infotable-pinned-right', '!bg-white', 'dark:!bg-gray-900')
|
|
1188
|
-
|
|
1189
|
-
if (columnName === firstRightPinned) {
|
|
1190
|
-
cell.classList.add("before:content-['']", "before:absolute", "before:top-0", "before:left-0", "before:bottom-0", "before:w-5", "before:pointer-events-none", "before:bg-gradient-to-l", "before:from-transparent", "before:to-black/[0.04]", "before:z-[1]")
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
})
|
|
1194
|
-
|
|
1195
|
-
rightOffset += header.offsetWidth
|
|
1196
|
-
}
|
|
1197
|
-
})
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
959
|
// ========== Column Ordering with Drag and Drop ==========
|
|
1201
960
|
|
|
1202
961
|
// Initialize Sortable for drag and drop column ordering
|
|
@@ -1217,7 +976,7 @@ export default class extends Controller {
|
|
|
1217
976
|
ghostClass: 'opacity-50',
|
|
1218
977
|
dragClass: 'shadow-lg',
|
|
1219
978
|
onEnd: (evt) => {
|
|
1220
|
-
this.handleColumnReorder(tableName, list)
|
|
979
|
+
this.handleColumnReorder(tableName, list, evt.item)
|
|
1221
980
|
}
|
|
1222
981
|
})
|
|
1223
982
|
|
|
@@ -1229,29 +988,29 @@ export default class extends Controller {
|
|
|
1229
988
|
})
|
|
1230
989
|
}
|
|
1231
990
|
|
|
1232
|
-
// Handle column reorder after drag and drop
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
const
|
|
991
|
+
// Handle column reorder after drag and drop.
|
|
992
|
+
// Pins survive a reorder; only the dragged column can change: dropped among the left pins it pins
|
|
993
|
+
// left, among the right pins it pins right, anywhere else it is unpinned.
|
|
994
|
+
handleColumnReorder(tableName, list, dragged) {
|
|
995
|
+
const items = Array.from(list.querySelectorAll('.column-item'))
|
|
996
|
+
const pinnedData = this.getAllColumnsPinned(tableName)
|
|
1237
997
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
998
|
+
if (dragged) {
|
|
999
|
+
const at = items.indexOf(dragged)
|
|
1000
|
+
const nextPin = pinnedData[items[at + 1]?.dataset.columnName]
|
|
1001
|
+
const prevPin = pinnedData[items[at - 1]?.dataset.columnName]
|
|
1002
|
+
const pinned = nextPin === 'left' ? 'left' : (prevPin === 'right' ? 'right' : null)
|
|
1241
1003
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
})
|
|
1246
|
-
|
|
1247
|
-
// Save to localStorage
|
|
1248
|
-
this.saveColumnOrder(tableName, newOrder)
|
|
1004
|
+
this.saveColumnPinned(tableName, dragged.dataset.columnName, pinned)
|
|
1005
|
+
this.updatePinButtons(dragged, pinned)
|
|
1006
|
+
}
|
|
1249
1007
|
|
|
1250
|
-
//
|
|
1251
|
-
this.
|
|
1008
|
+
// Keep the list as left pins, the rest, right pins (this also saves the order)
|
|
1009
|
+
this.reorderColumnsWithPinning(tableName)
|
|
1010
|
+
const newOrder = Array.from(list.querySelectorAll('.column-item')).map(item => item.dataset.columnName)
|
|
1252
1011
|
|
|
1253
|
-
//
|
|
1254
|
-
this.
|
|
1012
|
+
// Apply order and pins to the table
|
|
1013
|
+
this.applyToTable(tableName, newOrder)
|
|
1255
1014
|
|
|
1256
1015
|
// Dispatch event for export link update
|
|
1257
1016
|
document.dispatchEvent(new CustomEvent("infotable:columns-changed", {
|
|
@@ -1262,7 +1021,7 @@ export default class extends Controller {
|
|
|
1262
1021
|
// Save column order to localStorage
|
|
1263
1022
|
saveColumnOrder(tableName, order) {
|
|
1264
1023
|
const key = `infotable_column_order_${tableName}`
|
|
1265
|
-
|
|
1024
|
+
this.storeSet(key, JSON.stringify(order))
|
|
1266
1025
|
}
|
|
1267
1026
|
|
|
1268
1027
|
// Load column order from localStorage
|
|
@@ -1298,7 +1057,7 @@ export default class extends Controller {
|
|
|
1298
1057
|
|
|
1299
1058
|
// Valid schema, apply the order
|
|
1300
1059
|
this.applyColumnOrderToList(tableName, storedOrder)
|
|
1301
|
-
this.
|
|
1060
|
+
this.applyToTable(tableName, storedOrder)
|
|
1302
1061
|
}
|
|
1303
1062
|
|
|
1304
1063
|
// Check if stored order is valid against default schema
|
|
@@ -1352,64 +1111,10 @@ export default class extends Controller {
|
|
|
1352
1111
|
})
|
|
1353
1112
|
}
|
|
1354
1113
|
|
|
1355
|
-
// Apply column order to the table
|
|
1356
|
-
applyColumnOrder(tableName, order) {
|
|
1357
|
-
const tableWrapper = document.querySelector(`[data-infotable--table-name-value="${tableName}"]`)
|
|
1358
|
-
if (!tableWrapper) return
|
|
1359
|
-
|
|
1360
|
-
// Try to find table in the wrapper or in turbo-frame
|
|
1361
|
-
let table = tableWrapper.querySelector("table")
|
|
1362
|
-
if (!table) {
|
|
1363
|
-
const turboFrame = document.getElementById(`${tableName}_content`)
|
|
1364
|
-
if (turboFrame) {
|
|
1365
|
-
table = turboFrame.querySelector("table")
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
if (!table) return
|
|
1370
|
-
|
|
1371
|
-
// Get headers and create a map
|
|
1372
|
-
const headerRow = table.querySelector("thead tr")
|
|
1373
|
-
if (!headerRow) return
|
|
1374
|
-
|
|
1375
|
-
const headers = Array.from(headerRow.querySelectorAll("th[data-column-name]"))
|
|
1376
|
-
const headerMap = new Map()
|
|
1377
|
-
|
|
1378
|
-
headers.forEach((header, index) => {
|
|
1379
|
-
headerMap.set(header.dataset.columnName, { element: header, originalIndex: index })
|
|
1380
|
-
})
|
|
1381
|
-
|
|
1382
|
-
// Reorder headers according to order array
|
|
1383
|
-
order.forEach(columnName => {
|
|
1384
|
-
const headerInfo = headerMap.get(columnName)
|
|
1385
|
-
if (headerInfo) {
|
|
1386
|
-
headerRow.appendChild(headerInfo.element)
|
|
1387
|
-
}
|
|
1388
|
-
})
|
|
1389
|
-
|
|
1390
|
-
// Reorder cells in each row
|
|
1391
|
-
const rows = table.querySelectorAll("tbody tr")
|
|
1392
|
-
rows.forEach(row => {
|
|
1393
|
-
const cells = Array.from(row.querySelectorAll("td[data-column-name]"))
|
|
1394
|
-
const cellMap = new Map()
|
|
1395
|
-
|
|
1396
|
-
cells.forEach(cell => {
|
|
1397
|
-
cellMap.set(cell.dataset.columnName, cell)
|
|
1398
|
-
})
|
|
1399
|
-
|
|
1400
|
-
order.forEach(columnName => {
|
|
1401
|
-
const cell = cellMap.get(columnName)
|
|
1402
|
-
if (cell) {
|
|
1403
|
-
row.appendChild(cell)
|
|
1404
|
-
}
|
|
1405
|
-
})
|
|
1406
|
-
})
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
1114
|
// Clear column order from localStorage
|
|
1410
1115
|
clearColumnOrder(tableName) {
|
|
1411
1116
|
const key = `infotable_column_order_${tableName}`
|
|
1412
|
-
|
|
1117
|
+
this.storeRemove(key)
|
|
1413
1118
|
}
|
|
1414
1119
|
|
|
1415
1120
|
// Reset to defaults - clear all custom settings
|
|
@@ -1421,14 +1126,14 @@ export default class extends Controller {
|
|
|
1421
1126
|
|
|
1422
1127
|
// Clear column visibility
|
|
1423
1128
|
const visibilityKey = `infotable_columns_${tableName}`
|
|
1424
|
-
|
|
1129
|
+
this.storeRemove(visibilityKey)
|
|
1425
1130
|
|
|
1426
1131
|
// Clear column order
|
|
1427
1132
|
this.clearColumnOrder(tableName)
|
|
1428
1133
|
|
|
1429
1134
|
// Clear column pinning
|
|
1430
1135
|
const pinnedKey = `infotable_column_pinned_${tableName}`
|
|
1431
|
-
|
|
1136
|
+
this.storeRemove(pinnedKey)
|
|
1432
1137
|
|
|
1433
1138
|
// Reset checkboxes to defaults
|
|
1434
1139
|
const form = document.getElementById(`column-visibility-form-${tableName}`)
|
|
@@ -1452,19 +1157,10 @@ export default class extends Controller {
|
|
|
1452
1157
|
this.applyColumnOrderToList(tableName, schemaNames)
|
|
1453
1158
|
}
|
|
1454
1159
|
|
|
1455
|
-
// Apply defaults to table
|
|
1456
|
-
this.applyColumnVisibility(tableName)
|
|
1457
|
-
|
|
1458
|
-
// Apply default column order to table
|
|
1160
|
+
// Apply the defaults (visibility, default order, default pinning) to the table
|
|
1459
1161
|
const list = Array.from(this.columnListTargets).find(l => l.dataset.tableName === tableName)
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
const schemaNames = defaultSchema.map(item => typeof item === 'string' ? item : item.name)
|
|
1463
|
-
this.applyColumnOrder(tableName, schemaNames)
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
// Remove sticky positioning
|
|
1467
|
-
this.applyColumnPinningToTable(tableName)
|
|
1162
|
+
const defaultSchema = JSON.parse(list?.dataset.columnsSchema || '[]')
|
|
1163
|
+
this.applyToTable(tableName, list ? defaultSchema.map(item => typeof item === 'string' ? item : item.name) : null)
|
|
1468
1164
|
|
|
1469
1165
|
// Update badge
|
|
1470
1166
|
this.updateColumnBadge(tableName)
|