pgo-uiux2 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.
- package/.env +1 -0
- package/.env.production +1 -0
- package/.prettierrc +13 -0
- package/.vscode/extensions.json +3 -0
- package/BUTTON_GUIDE.md +257 -0
- package/README.md +49 -0
- package/THEME_REFERENCE.md +310 -0
- package/eslint.config.ts +27 -0
- package/index.html +13 -0
- package/package.json +85 -0
- package/public/favicon.ico +0 -0
- package/src/App.vue +368 -0
- package/src/assets/fonts/Faruma.ttf +0 -0
- package/src/components/examples/AppBarExample.vue +101 -0
- package/src/components/examples/AvatarExample.vue +47 -0
- package/src/components/examples/BannerExample.vue +287 -0
- package/src/components/examples/BaseInputExample.vue +25 -0
- package/src/components/examples/BreadcrumbExample.vue +53 -0
- package/src/components/examples/CardExample.vue +77 -0
- package/src/components/examples/ChipExample.vue +225 -0
- package/src/components/examples/DatePickerExample.vue +31 -0
- package/src/components/examples/DropdownExample.vue +84 -0
- package/src/components/examples/EditorExample.vue +200 -0
- package/src/components/examples/ExpansionPanelExample.vue +42 -0
- package/src/components/examples/FileUploadExample.vue +40 -0
- package/src/components/examples/FormExample.vue +121 -0
- package/src/components/examples/HugeTest.vue +8 -0
- package/src/components/examples/LayoutContainerExample.vue +80 -0
- package/src/components/examples/ModalExample.vue +82 -0
- package/src/components/examples/NavDrawerExample.vue +170 -0
- package/src/components/examples/NumberFieldExample.vue +145 -0
- package/src/components/examples/RadioButtonExample.vue +161 -0
- package/src/components/examples/SearchExample.vue +322 -0
- package/src/components/examples/SelectExample.vue +121 -0
- package/src/components/examples/StackedTableViewExample.vue +53 -0
- package/src/components/examples/TabExample.vue +336 -0
- package/src/components/examples/TableExample.vue +228 -0
- package/src/components/examples/TextFieldExample.vue +181 -0
- package/src/components/examples/TextareaExample.vue +173 -0
- package/src/components/examples/ThemeToggle.vue +50 -0
- package/src/components/examples/TimelineExample.vue +66 -0
- package/src/components/examples/TipTapEditorExample.vue +20 -0
- package/src/components/examples/TooltipExample.vue +53 -0
- package/src/components/examples/VueDatePickerShowcase.vue +214 -0
- package/src/components/examples/_DatePickerExample.vue +33 -0
- package/src/components/examples/__FormExample.vue +77 -0
- package/src/components/index.ts +25 -0
- package/src/components/pgo/AppBar.vue +347 -0
- package/src/components/pgo/Avatar.vue +139 -0
- package/src/components/pgo/Banner.vue +300 -0
- package/src/components/pgo/Breadcrumb.vue +101 -0
- package/src/components/pgo/Button.vue +171 -0
- package/src/components/pgo/Card.vue +178 -0
- package/src/components/pgo/ConfirmationModel.vue +32 -0
- package/src/components/pgo/DataTable.vue +845 -0
- package/src/components/pgo/DatePicker/CalendarPanel.vue +43 -0
- package/src/components/pgo/DatePicker/__DatePicker.vue +122 -0
- package/src/components/pgo/DatePicker/types.ts +11 -0
- package/src/components/pgo/DatePicker/useCalendar.ts +39 -0
- package/src/components/pgo/DatePicker/useDatePicker.ts +31 -0
- package/src/components/pgo/Deprecated/ToastContainer.vue +51 -0
- package/src/components/pgo/Deprecated/ToastItem.vue +55 -0
- package/src/components/pgo/Dropdown.vue +296 -0
- package/src/components/pgo/DropdownItem.vue +40 -0
- package/src/components/pgo/Editor.vue +511 -0
- package/src/components/pgo/ExpansionPanel.vue +185 -0
- package/src/components/pgo/Footer.vue +39 -0
- package/src/components/pgo/HeroIcon.vue +124 -0
- package/src/components/pgo/InputSearch.vue +194 -0
- package/src/components/pgo/LayoutContainer.vue +104 -0
- package/src/components/pgo/Main.vue +37 -0
- package/src/components/pgo/Modal.vue +273 -0
- package/src/components/pgo/NavDrawer.vue +127 -0
- package/src/components/pgo/NavDrawerItem.vue +161 -0
- package/src/components/pgo/NavigationDrawer.vue +849 -0
- package/src/components/pgo/OLDNavDrawer.vue +661 -0
- package/src/components/pgo/OldAppBar.vue +223 -0
- package/src/components/pgo/PApp.vue +102 -0
- package/src/components/pgo/Pagination.vue +242 -0
- package/src/components/pgo/Search copy.vue +310 -0
- package/src/components/pgo/Search.vue +411 -0
- package/src/components/pgo/StackedTableView.vue +167 -0
- package/src/components/pgo/Tab.vue +617 -0
- package/src/components/pgo/TestInput.vue +395 -0
- package/src/components/pgo/Timeline.vue +367 -0
- package/src/components/pgo/TimelineItem.vue +80 -0
- package/src/components/pgo/TipTapEditor.vue +315 -0
- package/src/components/pgo/Tooltip.NOTES.md +12 -0
- package/src/components/pgo/Tooltip.PROPS.md +21 -0
- package/src/components/pgo/Tooltip.vue +281 -0
- package/src/components/pgo/base/Base.vue +444 -0
- package/src/components/pgo/buttons/Chip.vue +324 -0
- package/src/components/pgo/buttons/ChipGroup.vue +224 -0
- package/src/components/pgo/buttons/Radio.vue +424 -0
- package/src/components/pgo/filters/FilterSection.vue +188 -0
- package/src/components/pgo/filters/Searchbar.vue +216 -0
- package/src/components/pgo/forms/DynamicForm.vue +45 -0
- package/src/components/pgo/forms/Form.vue +132 -0
- package/src/components/pgo/index.ts +15 -0
- package/src/components/pgo/inputs/Checkbox.vue +320 -0
- package/src/components/pgo/inputs/DatePicker.vue +395 -0
- package/src/components/pgo/inputs/FileUpload.vue +326 -0
- package/src/components/pgo/inputs/NumberField.vue +243 -0
- package/src/components/pgo/inputs/Radio.vue +162 -0
- package/src/components/pgo/inputs/RadioGroup.vue +188 -0
- package/src/components/pgo/inputs/Select.vue +535 -0
- package/src/components/pgo/inputs/TextField.vue +194 -0
- package/src/components/pgo/inputs/Textarea.vue +181 -0
- package/src/main.js +12 -0
- package/src/pgo-components/_index.js +31 -0
- package/src/pgo-components/assets/fonts/Faruma.ttf +0 -0
- package/src/pgo-components/assets/fonts/logo.png +0 -0
- package/src/pgo-components/composables/useTheme.js +10 -0
- package/src/pgo-components/directives/tooltip-directive.ts +393 -0
- package/src/pgo-components/index.js +96 -0
- package/src/pgo-components/lib/componentConfig.js +147 -0
- package/src/pgo-components/lib/core/composables/_useCalendar.ts +127 -0
- package/src/pgo-components/lib/core/composables/useDefaults.ts +15 -0
- package/src/pgo-components/lib/core/composables/useLanguageSelect.js +0 -0
- package/src/pgo-components/lib/core/composables/useRtl.ts +12 -0
- package/src/pgo-components/lib/core/defaults/createDefaults.ts +5 -0
- package/src/pgo-components/lib/core/defaults/defaults.ts +7 -0
- package/src/pgo-components/lib/core/rtl/rtl.ts +3 -0
- package/src/pgo-components/lib/core/rtl/setRtl.ts +19 -0
- package/src/pgo-components/lib/drawerState.ts +3 -0
- package/src/pgo-components/lib/i18n/defaultLables.js +71 -0
- package/src/pgo-components/lib/i18n/i18nPlugin.js +52 -0
- package/src/pgo-components/lib/i18n/useI18n.js +35 -0
- package/src/pgo-components/lib/index.ts +38 -0
- package/src/pgo-components/pages/Component.vue +7 -0
- package/src/pgo-components/pages/ComponentRenderer.vue +85 -0
- package/src/pgo-components/pages/Home.vue +130 -0
- package/src/pgo-components/pages/ListView.vue +370 -0
- package/src/pgo-components/pages/Page1.vue +296 -0
- package/src/pgo-components/pages/_Page1.vue +180 -0
- package/src/pgo-components/plugins/SnackBar.vue +251 -0
- package/src/pgo-components/plugins/SnackBarContainer.vue +53 -0
- package/src/pgo-components/plugins/SnackBarPlugin.ts +136 -0
- package/src/pgo-components/plugins/theme-plugin.js +114 -0
- package/src/pgo-components/plugins/types.ts +46 -0
- package/src/pgo-components/plugins/useSnackBar.js +11 -0
- package/src/pgo-components/plugins/useSnackBar.ts +21 -0
- package/src/pgo-components/plugins/validation-plugin.js +11 -0
- package/src/pgo-components/services/Entry.json +813 -0
- package/src/pgo-components/services/axios.js +54 -0
- package/src/pgo-components/services/data.json +90 -0
- package/src/pgo-components/services/person.json +260 -0
- package/src/pgo-components/services/toast.ts +44 -0
- package/src/pgo-components/styles/global.css +234 -0
- package/src/pgo-components/styles/reset.css +96 -0
- package/src/pgo-components/styles/tokens.css +18 -0
- package/src/pgo-components/styles/utilities/border-radius.css +57 -0
- package/src/pgo-components/styles/utilities/borders.css +85 -0
- package/src/pgo-components/styles/utilities/colors.css +38 -0
- package/src/pgo-components/styles/utilities/cursor.css +19 -0
- package/src/pgo-components/styles/utilities/display.css +78 -0
- package/src/pgo-components/styles/utilities/elevation.css +33 -0
- package/src/pgo-components/styles/utilities/flex.css +403 -0
- package/src/pgo-components/styles/utilities/float.css +41 -0
- package/src/pgo-components/styles/utilities/hover.css +9 -0
- package/src/pgo-components/styles/utilities/index.css +18 -0
- package/src/pgo-components/styles/utilities/opacity.css +27 -0
- package/src/pgo-components/styles/utilities/overflow.css +26 -0
- package/src/pgo-components/styles/utilities/palette.css +515 -0
- package/src/pgo-components/styles/utilities/position.css +14 -0
- package/src/pgo-components/styles/utilities/sizing.css +70 -0
- package/src/pgo-components/styles/utilities/spacing.css +578 -0
- package/src/pgo-components/styles/utilities/transitions.css +58 -0
- package/src/pgo-components/styles/utilities/typography.css +91 -0
- package/src/pgo-components/styles/utilities/z-index.css +11 -0
- package/src/pgo-components/tokens/index.js +337 -0
- package/src/router/index.js +88 -0
- package/src/shims-vue.d.ts +14 -0
- package/src/validations/validationRules.js +50 -0
- package/tailwind.config.js +73 -0
- package/test.php +5 -0
- package/tsconfig.json +25 -0
- package/ui +31 -0
- package/ui.pgo.mv.conf +18 -0
- package/vite.config.js +42 -0
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="['data-table-container', loading ? 'opacity-50 pointer-events-none relative' : '']">
|
|
3
|
+
<!-- Header with title and actions -->
|
|
4
|
+
<div v-if="title || $slots.header" class="data-table-header mb-6">
|
|
5
|
+
<div class="flex justify-between items-center">
|
|
6
|
+
<div>
|
|
7
|
+
<h2 v-if="title" :class="[headerClass || 'text-2xl font-semibold text-gray-800']">{{ title }}</h2>
|
|
8
|
+
<slot name="header" />
|
|
9
|
+
</div>
|
|
10
|
+
<div class="flex items-center gap-3">
|
|
11
|
+
<!-- Search -->
|
|
12
|
+
<div v-if="searchable" class="search-container">
|
|
13
|
+
<div class="relative">
|
|
14
|
+
<input
|
|
15
|
+
v-model="searchQuery"
|
|
16
|
+
type="text"
|
|
17
|
+
:placeholder="searchPlaceholder"
|
|
18
|
+
:class="[
|
|
19
|
+
'pl-10 pr-4 py-2 rounded-lg w-64 text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-offset-0',
|
|
20
|
+
inputBorder
|
|
21
|
+
]"
|
|
22
|
+
@input="debounceSearch"
|
|
23
|
+
/>
|
|
24
|
+
<div class="absolute left-3 top-1/2 transform -translate-y-1/2">
|
|
25
|
+
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
26
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
27
|
+
</svg>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
<slot name="header-actions" />
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Table Container -->
|
|
37
|
+
<div :class="['w-full', tableClass]">
|
|
38
|
+
<div :class="[rounded, border, shadow, bg, 'overflow-hidden', 'relative']">
|
|
39
|
+
<!-- Loading Bar -->
|
|
40
|
+
<div class="absolute top-0 left-0 right-0 z-10 h-1 overflow-hidden">
|
|
41
|
+
<div v-if="loading" class="bg-gray-200 w-full">
|
|
42
|
+
<div class="h-full bg-primary animate-loading-bar"></div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<table class="w-full caption-bottom text-sm">
|
|
47
|
+
<!-- Table Header -->
|
|
48
|
+
<thead :class="[headerBg]">
|
|
49
|
+
<tr :class="[headerBorder, 'transition-colors']">
|
|
50
|
+
<!-- Selection checkbox -->
|
|
51
|
+
<th v-if="selectable" :class="['h-10 px-3 text-left align-middle font-medium', headerText, 'w-12']">
|
|
52
|
+
<div class="flex items-center justify-center">
|
|
53
|
+
<input
|
|
54
|
+
type="checkbox"
|
|
55
|
+
:checked="allSelected"
|
|
56
|
+
:indeterminate.prop="someSelected"
|
|
57
|
+
class="h-4 w-4 rounded border border-gray-200 text-primary focus:ring-2 focus:ring-primary-500 focus:ring-offset-0"
|
|
58
|
+
@change="toggleSelectAll"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
</th>
|
|
62
|
+
<!-- Column Headers -->
|
|
63
|
+
<th
|
|
64
|
+
v-for="header in headers"
|
|
65
|
+
:key="header.value"
|
|
66
|
+
:class="[
|
|
67
|
+
'h-10 px-3 text-left align-middle font-medium cursor-pointer select-none transition-colors',
|
|
68
|
+
headerText,
|
|
69
|
+
getColumnFont(header.lang),
|
|
70
|
+
'hover:bg-input-hover-border',
|
|
71
|
+
{ 'bg-surface': isSorted(header.value) }
|
|
72
|
+
]"
|
|
73
|
+
@click="header.sortable !== false ? toggleSort(header.value) : null"
|
|
74
|
+
>
|
|
75
|
+
<div class="flex items-center space-x-2">
|
|
76
|
+
<span class="whitespace-nowrap">{{ header.title }}</span>
|
|
77
|
+
<div v-if="header.sortable !== false" class="flex h-4 w-4 items-center justify-center">
|
|
78
|
+
<!-- Unsorted state -->
|
|
79
|
+
<svg
|
|
80
|
+
v-if="!isSorted(header.value)"
|
|
81
|
+
class="h-4 w-4 text-gray-400"
|
|
82
|
+
fill="none"
|
|
83
|
+
stroke="currentColor"
|
|
84
|
+
viewBox="0 0 24 24"
|
|
85
|
+
>
|
|
86
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" />
|
|
87
|
+
</svg>
|
|
88
|
+
<!-- Ascending -->
|
|
89
|
+
<svg
|
|
90
|
+
v-else-if="getSortDirection(header.value) === 'asc'"
|
|
91
|
+
class="h-4 w-4 text-blue-600"
|
|
92
|
+
fill="none"
|
|
93
|
+
stroke="currentColor"
|
|
94
|
+
viewBox="0 0 24 24"
|
|
95
|
+
>
|
|
96
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
|
|
97
|
+
</svg>
|
|
98
|
+
<!-- Descending -->
|
|
99
|
+
<svg
|
|
100
|
+
v-else
|
|
101
|
+
class="h-4 w-4 text-blue-600"
|
|
102
|
+
fill="none"
|
|
103
|
+
stroke="currentColor"
|
|
104
|
+
viewBox="0 0 24 24"
|
|
105
|
+
>
|
|
106
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
|
107
|
+
</svg>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</th>
|
|
111
|
+
<!-- Actions column -->
|
|
112
|
+
<th v-if="$slots['item-actions'] || showActions" :class="['h-10 px-3 text-end align-middle font-medium', headerText]">
|
|
113
|
+
<Button
|
|
114
|
+
v-if="inlineEdit"
|
|
115
|
+
:label="editMode ? 'Cancel Edit' : 'Inline Edit'"
|
|
116
|
+
:color="editMode ? 'danger' : 'primary'"
|
|
117
|
+
size="xs"
|
|
118
|
+
@click="toggleEditMode"
|
|
119
|
+
/>
|
|
120
|
+
</th>
|
|
121
|
+
</tr>
|
|
122
|
+
</thead>
|
|
123
|
+
|
|
124
|
+
<!-- Table Body -->
|
|
125
|
+
<tbody :class="[rowBorder, loading ? 'opacity-50 pointer-events-none' : '']">
|
|
126
|
+
<!-- No Data State -->
|
|
127
|
+
<tr v-if="!items || items.length === 0">
|
|
128
|
+
<td :colspan="totalColumns" class="px-6 py-12 text-center">
|
|
129
|
+
<div class="flex flex-col items-center">
|
|
130
|
+
<svg class="w-12 h-12 text-gray-300 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
131
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
132
|
+
</svg>
|
|
133
|
+
<span class="text-sm text-gray-500">{{ noDataText }}</span>
|
|
134
|
+
</div>
|
|
135
|
+
</td>
|
|
136
|
+
</tr>
|
|
137
|
+
|
|
138
|
+
<!-- Data Rows -->
|
|
139
|
+
<tr
|
|
140
|
+
v-else
|
|
141
|
+
v-for="(item, index) in items"
|
|
142
|
+
:key="getItemKey(item, index)"
|
|
143
|
+
:class="[
|
|
144
|
+
'transition-colors cursor-pointer',
|
|
145
|
+
rowHover,
|
|
146
|
+
selectedItems.includes(getItemKey(item, index)) ? rowSelected : ''
|
|
147
|
+
]"
|
|
148
|
+
@click="handleRowClick(item, index)"
|
|
149
|
+
>
|
|
150
|
+
<!-- Selection checkbox -->
|
|
151
|
+
<td v-if="selectable" class="px-6 py-4">
|
|
152
|
+
<div class="flex items-center justify-center">
|
|
153
|
+
<input
|
|
154
|
+
type="checkbox"
|
|
155
|
+
:checked="selectedItems.includes(getItemKey(item, index))"
|
|
156
|
+
class="h-4 w-4 rounded border-2 border-gray-300 text-blue-600 focus:ring-2 focus:ring-blue-500 focus:ring-offset-0"
|
|
157
|
+
@click.stop
|
|
158
|
+
@change="toggleItemSelection(item, index)"
|
|
159
|
+
/>
|
|
160
|
+
</div>
|
|
161
|
+
</td>
|
|
162
|
+
|
|
163
|
+
<!-- Data columns -->
|
|
164
|
+
<template v-for="header in headers" :key="header.value">
|
|
165
|
+
<td
|
|
166
|
+
:class="[
|
|
167
|
+
'px-6 py-4 whitespace-nowrap',
|
|
168
|
+
header.displayType == 'englishText' ? 'eng-font' : 'faruma ',
|
|
169
|
+
getColumnFont(header.lang)
|
|
170
|
+
]"
|
|
171
|
+
@click.stop
|
|
172
|
+
>
|
|
173
|
+
<slot
|
|
174
|
+
:name="`item.${header.value}`"
|
|
175
|
+
:item="item"
|
|
176
|
+
:value="getNestedValue(item, header.value)"
|
|
177
|
+
:header="header"
|
|
178
|
+
:index="index"
|
|
179
|
+
>
|
|
180
|
+
<!-- Chip Display -->
|
|
181
|
+
<div v-if="header.displayType == 'chip'"
|
|
182
|
+
:class="['align-middle leading-none', getColumnFont(header.lang)]"
|
|
183
|
+
>
|
|
184
|
+
<Chip
|
|
185
|
+
size="small"
|
|
186
|
+
v-bind="header.displayProps?.[getNestedValue(item, header.value)]"
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<!-- Checkbox (Inline Editable) -->
|
|
191
|
+
<div v-else-if="header.displayType == 'checkbox'"
|
|
192
|
+
:class="['align-middle leading-none', getColumnFont(header.lang)]"
|
|
193
|
+
>
|
|
194
|
+
<Checkbox
|
|
195
|
+
:model-value="!!getNestedValue(item, header.value)"
|
|
196
|
+
v-bind="header.displayProps"
|
|
197
|
+
:disabled="!editMode || !header.inlineEditable"
|
|
198
|
+
@update:model-value="(value) => handleCellUpdate(item, header.value, value, index)"
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<!-- Select (Inline Editable) -->
|
|
203
|
+
<div v-else-if="header.displayType == 'select'"
|
|
204
|
+
:class="['align-middle leading-none', getColumnFont(header.lang)]"
|
|
205
|
+
>
|
|
206
|
+
<Select
|
|
207
|
+
v-if="editMode && header.inlineEditable"
|
|
208
|
+
:model-value="getNestedValue(item, header.value)"
|
|
209
|
+
v-bind="header.displayProps"
|
|
210
|
+
@update:model-value="(value) => handleCellUpdate(item, header.value, value, index)"
|
|
211
|
+
/>
|
|
212
|
+
<div v-else :class="['text-sm', cellText]">
|
|
213
|
+
{{ formatCellValue(getNestedValue(item, header.value), header) }}
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<!-- Text Input (Inline Editable) -->
|
|
218
|
+
<div v-else-if="header.displayType == 'input'"
|
|
219
|
+
:class="['align-middle leading-none', getColumnFont(header.lang)]"
|
|
220
|
+
>
|
|
221
|
+
<input
|
|
222
|
+
v-if="editMode && header.inlineEditable"
|
|
223
|
+
:value="getNestedValue(item, header.value)"
|
|
224
|
+
type="text"
|
|
225
|
+
class="w-full px-2 py-1 text-sm border rounded focus:outline-none focus:ring-2 focus:ring-primary"
|
|
226
|
+
@input="(e) => handleCellUpdate(item, header.value, e.target.value, index)"
|
|
227
|
+
/>
|
|
228
|
+
<div v-else :class="['text-sm', cellText]">
|
|
229
|
+
{{ formatCellValue(getNestedValue(item, header.value), header) }}
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<!-- Default Text Display -->
|
|
234
|
+
<div v-else :class="['text-sm', cellText]">
|
|
235
|
+
{{ formatCellValue(getNestedValue(item, header.value), header) }}
|
|
236
|
+
</div>
|
|
237
|
+
</slot>
|
|
238
|
+
</td>
|
|
239
|
+
</template>
|
|
240
|
+
|
|
241
|
+
<!-- Actions -->
|
|
242
|
+
<td v-if="$slots['item-actions'] || showActions" class="px-6 py-4">
|
|
243
|
+
<slot name="item-actions" :item="item" :index="index">
|
|
244
|
+
<div class="flex items-center justify-end space-x-2">
|
|
245
|
+
<Button v-if="showView" @click.stop="emit('view', item)" size="md" color="info" variant="text" icon="eye" icon-type="outline" label="" />
|
|
246
|
+
<Button v-if="showEdit" @click.stop="emit('edit', item)" size="md" icon="pencil-square" color="warning" variant="text" label="" />
|
|
247
|
+
<Button v-if="showDelete" @click.stop="emit('delete', item)" size="md" color="danger" variant="text" icon="trash" label="" />
|
|
248
|
+
</div>
|
|
249
|
+
</slot>
|
|
250
|
+
</td>
|
|
251
|
+
</tr>
|
|
252
|
+
</tbody>
|
|
253
|
+
</table>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<!-- Pagination Component -->
|
|
257
|
+
<Pagination
|
|
258
|
+
v-if="!hidePagination && !loading"
|
|
259
|
+
v-model:page="internalOptions.page"
|
|
260
|
+
v-model:items-per-page="internalOptions.itemsPerPage"
|
|
261
|
+
:items-length="internalOptions.itemsLength"
|
|
262
|
+
:items-per-page-options="itemsPerPageOptions"
|
|
263
|
+
:bg="paginationBg"
|
|
264
|
+
:border="paginationBorder"
|
|
265
|
+
:text-color="'text-textsecondary'"
|
|
266
|
+
:input-border="inputBorder"
|
|
267
|
+
:button-primary="buttonPrimary"
|
|
268
|
+
:button-secondary="buttonSecondary"
|
|
269
|
+
@change="handlePaginationChange"
|
|
270
|
+
/>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<!-- Footer slot -->
|
|
274
|
+
<div v-if="$slots.footer" class="mt-4">
|
|
275
|
+
<slot name="footer" />
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<!-- Confirmation Modal -->
|
|
279
|
+
<Model
|
|
280
|
+
v-model="showConfirmation"
|
|
281
|
+
persistent
|
|
282
|
+
lang="en"
|
|
283
|
+
|
|
284
|
+
title="Confirm Update"
|
|
285
|
+
>
|
|
286
|
+
<p class="text-sm text-gray-600 mb-6">
|
|
287
|
+
Are you sure you want to update <strong>{{ pendingUpdate.header }}</strong>
|
|
288
|
+
from <strong>{{ pendingUpdate.oldValue }}</strong> to <strong>{{ pendingUpdate.newValue }}</strong>?
|
|
289
|
+
</p>
|
|
290
|
+
<template #footer>
|
|
291
|
+
<div class="flex justify-end gap-2">
|
|
292
|
+
<Button
|
|
293
|
+
label="Cancel"
|
|
294
|
+
color="secondary"
|
|
295
|
+
variant="outlined"
|
|
296
|
+
@click="cancelUpdate"
|
|
297
|
+
/>
|
|
298
|
+
<Button
|
|
299
|
+
label="Confirm"
|
|
300
|
+
color="primary"
|
|
301
|
+
:loading="updateLoading"
|
|
302
|
+
@click="confirmUpdate"
|
|
303
|
+
/>
|
|
304
|
+
</div>
|
|
305
|
+
</template>
|
|
306
|
+
</Model>
|
|
307
|
+
</div>
|
|
308
|
+
</template>
|
|
309
|
+
|
|
310
|
+
<script setup>
|
|
311
|
+
import { ref, computed, watch, onMounted, inject } from 'vue'
|
|
312
|
+
import Button from '../pgo/Button.vue'
|
|
313
|
+
import Pagination from '../pgo/Pagination.vue'
|
|
314
|
+
import Chip from '../pgo/buttons/Chip.vue'
|
|
315
|
+
import Checkbox from '../pgo/inputs/Checkbox.vue'
|
|
316
|
+
import Select from '../pgo/inputs/Select.vue'
|
|
317
|
+
import Model from '../pgo/Modal.vue'
|
|
318
|
+
|
|
319
|
+
const props = defineProps({
|
|
320
|
+
// Data
|
|
321
|
+
items: {
|
|
322
|
+
type: Array,
|
|
323
|
+
default: () => []
|
|
324
|
+
},
|
|
325
|
+
headers: {
|
|
326
|
+
type: Array,
|
|
327
|
+
required: true
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
// Server-side options
|
|
331
|
+
serverSideOptions: {
|
|
332
|
+
type: Object,
|
|
333
|
+
default: () => ({
|
|
334
|
+
page: 1,
|
|
335
|
+
itemsPerPage: 10,
|
|
336
|
+
sortBy: [],
|
|
337
|
+
sortDesc: [],
|
|
338
|
+
itemsLength: 0
|
|
339
|
+
})
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
// Table configuration
|
|
343
|
+
title: {
|
|
344
|
+
type: String,
|
|
345
|
+
default: ''
|
|
346
|
+
},
|
|
347
|
+
loading: {
|
|
348
|
+
type: Boolean,
|
|
349
|
+
default: false
|
|
350
|
+
},
|
|
351
|
+
loadingText: {
|
|
352
|
+
type: String,
|
|
353
|
+
default: 'Loading...'
|
|
354
|
+
},
|
|
355
|
+
noDataText: {
|
|
356
|
+
type: String,
|
|
357
|
+
default: 'No data available'
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
// Features
|
|
361
|
+
showActions: {
|
|
362
|
+
type: Boolean,
|
|
363
|
+
default: true
|
|
364
|
+
},
|
|
365
|
+
searchable: {
|
|
366
|
+
type: Boolean,
|
|
367
|
+
default: false
|
|
368
|
+
},
|
|
369
|
+
searchPlaceholder: {
|
|
370
|
+
type: String,
|
|
371
|
+
default: 'Search...'
|
|
372
|
+
},
|
|
373
|
+
showView: {
|
|
374
|
+
type: Boolean,
|
|
375
|
+
default: true
|
|
376
|
+
},
|
|
377
|
+
showEdit: {
|
|
378
|
+
type: Boolean,
|
|
379
|
+
default: true
|
|
380
|
+
},
|
|
381
|
+
showDelete: {
|
|
382
|
+
type: Boolean,
|
|
383
|
+
default: true
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
inlineEdit: {
|
|
387
|
+
type: Boolean,
|
|
388
|
+
default: false
|
|
389
|
+
},
|
|
390
|
+
updateUrl: {
|
|
391
|
+
type: String,
|
|
392
|
+
default: '' // API endpoint for updates
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
// Styling props - using Card defaults
|
|
396
|
+
tableClass: {
|
|
397
|
+
type: String,
|
|
398
|
+
default: ''
|
|
399
|
+
},
|
|
400
|
+
headerClass: {
|
|
401
|
+
type: String,
|
|
402
|
+
default: ''
|
|
403
|
+
},
|
|
404
|
+
// Container styling
|
|
405
|
+
bg: {
|
|
406
|
+
type: String,
|
|
407
|
+
default: 'bg-background'
|
|
408
|
+
},
|
|
409
|
+
border: {
|
|
410
|
+
type: String,
|
|
411
|
+
default: 'border-0'
|
|
412
|
+
},
|
|
413
|
+
rounded: {
|
|
414
|
+
type: String,
|
|
415
|
+
default: 'rounded-sm'
|
|
416
|
+
},
|
|
417
|
+
shadow: {
|
|
418
|
+
type: String,
|
|
419
|
+
default: 'shadow-none'
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
// Header styling
|
|
423
|
+
headerBg: {
|
|
424
|
+
type: String,
|
|
425
|
+
default: 'bg-surface-elevated'
|
|
426
|
+
},
|
|
427
|
+
headerBorder: {
|
|
428
|
+
type: String,
|
|
429
|
+
default: 'border-b border-input-border'
|
|
430
|
+
},
|
|
431
|
+
headerText: {
|
|
432
|
+
type: String,
|
|
433
|
+
default: 'text-textcolor'
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
// Row styling
|
|
437
|
+
rowBorder: {
|
|
438
|
+
type: String,
|
|
439
|
+
default: 'divide-y divide-input-border'
|
|
440
|
+
},
|
|
441
|
+
rowHover: {
|
|
442
|
+
type: String,
|
|
443
|
+
default: 'hover:bg-input-hover-border'
|
|
444
|
+
},
|
|
445
|
+
rowSelected: {
|
|
446
|
+
type: String,
|
|
447
|
+
default: 'bg-surface-elevated'
|
|
448
|
+
},
|
|
449
|
+
cellText: {
|
|
450
|
+
type: String,
|
|
451
|
+
default: 'text-textcolor'
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
// Input styling
|
|
455
|
+
inputBorder: {
|
|
456
|
+
type: String,
|
|
457
|
+
default: 'border-input-border focus:border-input-focus-border focus:ring-input-focus-ring'
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
// Button styling
|
|
461
|
+
buttonPrimary: {
|
|
462
|
+
type: String,
|
|
463
|
+
default: 'bg-primary hover:bg-secondary text-white'
|
|
464
|
+
},
|
|
465
|
+
buttonSecondary: {
|
|
466
|
+
type: String,
|
|
467
|
+
default: 'bg-surface hover:bg-input-hover-border text-textcolor border border-input-border'
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
// Pagination styling
|
|
471
|
+
paginationBg: {
|
|
472
|
+
type: String,
|
|
473
|
+
default: 'bg-background'
|
|
474
|
+
},
|
|
475
|
+
paginationBorder: {
|
|
476
|
+
type: String,
|
|
477
|
+
default: 'border-t border-input-border'
|
|
478
|
+
},
|
|
479
|
+
selectable: {
|
|
480
|
+
type: Boolean,
|
|
481
|
+
default: false
|
|
482
|
+
},
|
|
483
|
+
hidePagination: {
|
|
484
|
+
type: Boolean,
|
|
485
|
+
default: false
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
// Pagination
|
|
489
|
+
itemsPerPageOptions: {
|
|
490
|
+
type: Array,
|
|
491
|
+
default: () => [5, 10, 25, 50, 100]
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
// Item key
|
|
495
|
+
itemKey: {
|
|
496
|
+
type: String,
|
|
497
|
+
default: 'id'
|
|
498
|
+
}
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
const emit = defineEmits([
|
|
502
|
+
'update:options',
|
|
503
|
+
'update:cell',
|
|
504
|
+
'inline-update', // New event for inline updates
|
|
505
|
+
'row-click',
|
|
506
|
+
'selection-change',
|
|
507
|
+
'search',
|
|
508
|
+
'view',
|
|
509
|
+
'edit',
|
|
510
|
+
'delete'
|
|
511
|
+
])
|
|
512
|
+
|
|
513
|
+
// Internal state
|
|
514
|
+
const searchQuery = ref('')
|
|
515
|
+
const selectedItems = ref([])
|
|
516
|
+
const searchTimeout = ref(null)
|
|
517
|
+
const editMode = ref(false)
|
|
518
|
+
const showConfirmation = ref(false)
|
|
519
|
+
const updateLoading = ref(false)
|
|
520
|
+
const pendingUpdate = ref({
|
|
521
|
+
item: null,
|
|
522
|
+
key: '',
|
|
523
|
+
newValue: null,
|
|
524
|
+
oldValue: null,
|
|
525
|
+
header: '',
|
|
526
|
+
index: null
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
// Internal options that sync with parent
|
|
530
|
+
const internalOptions = ref({
|
|
531
|
+
page: 1,
|
|
532
|
+
itemsPerPage: 10,
|
|
533
|
+
sortBy: [],
|
|
534
|
+
sortDesc: [],
|
|
535
|
+
itemsLength: 0,
|
|
536
|
+
search: '',
|
|
537
|
+
...props.serverSideOptions
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
// Methods
|
|
541
|
+
const handlePaginationChange = ({ page, itemsPerPage }) => {
|
|
542
|
+
internalOptions.value.page = page
|
|
543
|
+
internalOptions.value.itemsPerPage = itemsPerPage
|
|
544
|
+
emitOptionsUpdate()
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Computed properties
|
|
548
|
+
const totalColumns = computed(() => {
|
|
549
|
+
let count = props.headers.length
|
|
550
|
+
if (props.selectable) count++
|
|
551
|
+
if (props.$slots?.['item-actions']) count++
|
|
552
|
+
return count
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
const allSelected = computed(() => {
|
|
557
|
+
return props.items.length > 0 && selectedItems.value.length === props.items.length
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
const someSelected = computed(() => {
|
|
561
|
+
return selectedItems.value.length > 0 && !allSelected.value
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
// Methods
|
|
565
|
+
const getItemKey = (item, index) => {
|
|
566
|
+
return item[props.itemKey] ?? index
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// const getNestedValue = (obj, path) => {
|
|
570
|
+
// return path.split('.').reduce((current, key) => current?.[key], obj)
|
|
571
|
+
// }
|
|
572
|
+
const getNestedValue = (obj, path) => {
|
|
573
|
+
if (!path) return '' // Safety check
|
|
574
|
+
return path.split('.').reduce((current, key) => current?.[key], obj)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const formatCellValue = (value, header) => {
|
|
578
|
+
if (header.format && typeof header.format === 'function') {
|
|
579
|
+
return header.format(value)
|
|
580
|
+
}
|
|
581
|
+
return value ?? ''
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const toggleSort = (key) => {
|
|
585
|
+
// Find the header to check if it's sortable
|
|
586
|
+
const header = props.headers.find(h => h.key === key)
|
|
587
|
+
|
|
588
|
+
// Don't sort if explicitly marked as not sortable
|
|
589
|
+
if (header && header.sortable === false) {
|
|
590
|
+
console.log('Column is not sortable:', key)
|
|
591
|
+
return
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const sortBy = [...internalOptions.value.sortBy]
|
|
595
|
+
const sortDesc = [...internalOptions.value.sortDesc]
|
|
596
|
+
|
|
597
|
+
const index = sortBy.findIndex(item => item === key)
|
|
598
|
+
|
|
599
|
+
if (index === -1) {
|
|
600
|
+
// Add new sort
|
|
601
|
+
sortBy.push(key)
|
|
602
|
+
sortDesc.push(false)
|
|
603
|
+
console.log('Adding new sort - ASC')
|
|
604
|
+
} else {
|
|
605
|
+
// Toggle existing sort
|
|
606
|
+
if (!sortDesc[index]) {
|
|
607
|
+
sortDesc[index] = true
|
|
608
|
+
console.log('Changing to DESC')
|
|
609
|
+
} else {
|
|
610
|
+
// Remove sort
|
|
611
|
+
sortBy.splice(index, 1)
|
|
612
|
+
sortDesc.splice(index, 1)
|
|
613
|
+
console.log('Removing sort')
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
internalOptions.value.sortBy = sortBy
|
|
618
|
+
internalOptions.value.sortDesc = sortDesc
|
|
619
|
+
internalOptions.value.page = 1 // Reset to first page
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
emitOptionsUpdate()
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const isSorted = (key) => {
|
|
626
|
+
return internalOptions.value.sortBy.includes(key)
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const getSortDirection = (key) => {
|
|
630
|
+
const index = internalOptions.value.sortBy.findIndex(item => item === key)
|
|
631
|
+
if (index === -1) return null
|
|
632
|
+
return internalOptions.value.sortDesc[index] ? 'desc' : 'asc'
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const getColumnFont = (lang) => {
|
|
636
|
+
if (!lang) return ''
|
|
637
|
+
|
|
638
|
+
const fontMap = {
|
|
639
|
+
'en': 'font-inter', // or whatever your English font is
|
|
640
|
+
'dv': 'faruma', // Dhivehi font
|
|
641
|
+
// Add more languages as needed
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return fontMap[lang] || ''
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const debounceSearch = () => {
|
|
648
|
+
if (searchTimeout.value) {
|
|
649
|
+
clearTimeout(searchTimeout.value)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
searchTimeout.value = setTimeout(() => {
|
|
653
|
+
internalOptions.value.search = searchQuery.value
|
|
654
|
+
internalOptions.value.page = 1
|
|
655
|
+
emitOptionsUpdate()
|
|
656
|
+
emit('search', searchQuery.value)
|
|
657
|
+
}, 500)
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const handleRowClick = (item, index) => {
|
|
661
|
+
emit('row-click', item, index)
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const toggleItemSelection = (item, index) => {
|
|
665
|
+
const key = getItemKey(item, index)
|
|
666
|
+
const currentIndex = selectedItems.value.indexOf(key)
|
|
667
|
+
|
|
668
|
+
if (currentIndex === -1) {
|
|
669
|
+
selectedItems.value.push(key)
|
|
670
|
+
} else {
|
|
671
|
+
selectedItems.value.splice(currentIndex, 1)
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
emit('selection-change', selectedItems.value, getSelectedItems())
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const toggleSelectAll = () => {
|
|
678
|
+
if (allSelected.value) {
|
|
679
|
+
selectedItems.value = []
|
|
680
|
+
} else {
|
|
681
|
+
selectedItems.value = props.items.map((item, index) => getItemKey(item, index))
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
emit('selection-change', selectedItems.value, getSelectedItems())
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const getSelectedItems = () => {
|
|
688
|
+
return props.items.filter((item, index) =>
|
|
689
|
+
selectedItems.value.includes(getItemKey(item, index))
|
|
690
|
+
)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const emitOptionsUpdate = () => {
|
|
694
|
+
emit('update:options', { ...internalOptions.value })
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Toggle edit mode
|
|
698
|
+
const toggleEditMode = () => {
|
|
699
|
+
editMode.value = !editMode.value
|
|
700
|
+
if (!editMode.value) {
|
|
701
|
+
// Cancel any pending changes
|
|
702
|
+
showConfirmation.value = false
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Handle cell update
|
|
707
|
+
const handleCellUpdate = (item, key, newValue, index) => {
|
|
708
|
+
const header = props.headers.find(h => h.key === key)
|
|
709
|
+
const oldValue = getNestedValue(item, key)
|
|
710
|
+
|
|
711
|
+
// Only show confirmation if value actually changed
|
|
712
|
+
if (oldValue === newValue) return
|
|
713
|
+
|
|
714
|
+
pendingUpdate.value = {
|
|
715
|
+
item,
|
|
716
|
+
key,
|
|
717
|
+
newValue,
|
|
718
|
+
oldValue,
|
|
719
|
+
header: header?.title || key,
|
|
720
|
+
index
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
showConfirmation.value = true
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Confirm and send update
|
|
727
|
+
const confirmUpdate = async () => {
|
|
728
|
+
updateLoading.value = true
|
|
729
|
+
|
|
730
|
+
try {
|
|
731
|
+
const { item, key, newValue, index } = pendingUpdate.value
|
|
732
|
+
|
|
733
|
+
// Prepare update data
|
|
734
|
+
const updateData = {
|
|
735
|
+
[props.itemKey]: item[props.itemKey],
|
|
736
|
+
[key]: newValue
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Emit event to parent
|
|
740
|
+
emit('inline-update', {
|
|
741
|
+
item,
|
|
742
|
+
key,
|
|
743
|
+
value: newValue,
|
|
744
|
+
index,
|
|
745
|
+
updateData
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
// If updateUrl is provided, send API request
|
|
749
|
+
if (props.updateUrl) {
|
|
750
|
+
const api = inject('api', null)
|
|
751
|
+
if (api) {
|
|
752
|
+
const url = `${props.updateUrl}/${item[props.itemKey]}`
|
|
753
|
+
await api.patch(url, updateData)
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Update local data
|
|
758
|
+
item[key] = newValue
|
|
759
|
+
|
|
760
|
+
// Reset confirmation
|
|
761
|
+
showConfirmation.value = false
|
|
762
|
+
updateLoading.value = false
|
|
763
|
+
pendingUpdate.value = {
|
|
764
|
+
item: null,
|
|
765
|
+
key: '',
|
|
766
|
+
newValue: null,
|
|
767
|
+
oldValue: null,
|
|
768
|
+
header: '',
|
|
769
|
+
index: null
|
|
770
|
+
}
|
|
771
|
+
} catch (error) {
|
|
772
|
+
console.error('Update failed:', error)
|
|
773
|
+
updateLoading.value = false
|
|
774
|
+
// You can add error handling here
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Cancel update
|
|
779
|
+
const cancelUpdate = () => {
|
|
780
|
+
showConfirmation.value = false
|
|
781
|
+
pendingUpdate.value = {
|
|
782
|
+
item: null,
|
|
783
|
+
key: '',
|
|
784
|
+
newValue: null,
|
|
785
|
+
oldValue: null,
|
|
786
|
+
header: '',
|
|
787
|
+
index: null
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Watchers
|
|
792
|
+
watch(() => props.serverSideOptions, (newOptions) => {
|
|
793
|
+
internalOptions.value = { ...internalOptions.value, ...newOptions }
|
|
794
|
+
}, { deep: true })
|
|
795
|
+
|
|
796
|
+
// Clear selection when items change
|
|
797
|
+
watch(() => props.items, () => {
|
|
798
|
+
selectedItems.value = []
|
|
799
|
+
}, { deep: true })
|
|
800
|
+
|
|
801
|
+
// Initialize
|
|
802
|
+
onMounted(() => {
|
|
803
|
+
if (props.serverSideOptions) {
|
|
804
|
+
internalOptions.value = { ...internalOptions.value, ...props.serverSideOptions }
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
})
|
|
808
|
+
</script>
|
|
809
|
+
|
|
810
|
+
<style scoped>
|
|
811
|
+
/* Only essential styles - everything else uses Tailwind classes */
|
|
812
|
+
|
|
813
|
+
@keyframes loading-bar {
|
|
814
|
+
0% {
|
|
815
|
+
transform: translateX(-100%);
|
|
816
|
+
}
|
|
817
|
+
100% {
|
|
818
|
+
transform: translateX(100%);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
.animate-loading-bar {
|
|
824
|
+
animation: loading-bar 1.5s ease-in-out infinite;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/* Table collapse */
|
|
828
|
+
table {
|
|
829
|
+
/* border-collapse: separate; */
|
|
830
|
+
border-spacing: 0;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/* Checkbox indeterminate state */
|
|
834
|
+
input[type="checkbox"]:indeterminate::after {
|
|
835
|
+
content: '';
|
|
836
|
+
position: absolute;
|
|
837
|
+
width: 0.5rem;
|
|
838
|
+
height: 0.125rem;
|
|
839
|
+
background-color: currentColor;
|
|
840
|
+
border-radius: 0.125rem;
|
|
841
|
+
top: 50%;
|
|
842
|
+
left: 50%;
|
|
843
|
+
transform: translate(-50%, -50%);
|
|
844
|
+
}
|
|
845
|
+
</style>
|