| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  | <template> | 
					
						
							|  |  |  |   <div> | 
					
						
							|  |  |  |     <v-card-actions> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       <v-menu | 
					
						
							|  |  |  |         v-if="tableConfig.hideColumns" | 
					
						
							|  |  |  |         offset-y | 
					
						
							|  |  |  |         bottom | 
					
						
							|  |  |  |         nudge-bottom="6" | 
					
						
							|  |  |  |         :close-on-content-click="false" | 
					
						
							|  |  |  |       > | 
					
						
							|  |  |  |         <template #activator="{ props }"> | 
					
						
							|  |  |  |           <v-btn | 
					
						
							|  |  |  |             color="accent" | 
					
						
							|  |  |  |             variant="elevated" | 
					
						
							|  |  |  |             v-bind="props" | 
					
						
							|  |  |  |           > | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |             <v-icon> | 
					
						
							|  |  |  |               {{ $globals.icons.cog }} | 
					
						
							|  |  |  |             </v-icon> | 
					
						
							|  |  |  |           </v-btn> | 
					
						
							|  |  |  |         </template> | 
					
						
							|  |  |  |         <v-card> | 
					
						
							|  |  |  |           <v-card-text> | 
					
						
							|  |  |  |             <v-checkbox | 
					
						
							|  |  |  |               v-for="itemValue in headers" | 
					
						
							|  |  |  |               :key="itemValue.text + itemValue.show" | 
					
						
							|  |  |  |               v-model="filteredHeaders" | 
					
						
							|  |  |  |               :value="itemValue.value" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |               density="compact" | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |               flat | 
					
						
							|  |  |  |               inset | 
					
						
							|  |  |  |               :label="itemValue.text" | 
					
						
							|  |  |  |               hide-details | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |             /> | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |           </v-card-text> | 
					
						
							|  |  |  |         </v-card> | 
					
						
							|  |  |  |       </v-menu> | 
					
						
							|  |  |  |       <BaseOverflowButton | 
					
						
							|  |  |  |         v-if="bulkActions.length > 0" | 
					
						
							|  |  |  |         :disabled="selected.length < 1" | 
					
						
							|  |  |  |         mode="event" | 
					
						
							|  |  |  |         color="info" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         variant="elevated" | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |         :items="bulkActions" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         v-bind="bulkActionListener" | 
					
						
							|  |  |  |       /> | 
					
						
							|  |  |  |       <slot name="button-row" /> | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |     </v-card-actions> | 
					
						
							|  |  |  |     <div class="mx-2 clip-width"> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       <v-text-field | 
					
						
							|  |  |  |         v-model="search" | 
					
						
							|  |  |  |         variant="underlined" | 
					
						
							|  |  |  |         :label="$t('search.search')" | 
					
						
							|  |  |  |       /> | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |     </div> | 
					
						
							|  |  |  |     <v-data-table | 
					
						
							|  |  |  |       v-model="selected" | 
					
						
							|  |  |  |       item-key="id" | 
					
						
							|  |  |  |       :headers="activeHeaders" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       :show-select="bulkActions.length > 0" | 
					
						
							|  |  |  |       :sort-by="sortBy" | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |       :items="data || []" | 
					
						
							|  |  |  |       :items-per-page="15" | 
					
						
							|  |  |  |       :search="search" | 
					
						
							|  |  |  |       class="elevation-2" | 
					
						
							|  |  |  |     > | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       <template | 
					
						
							|  |  |  |         v-for="header in headersWithoutActions" | 
					
						
							|  |  |  |         #[`item.${header.value}`]="{ item }" | 
					
						
							|  |  |  |       > | 
					
						
							|  |  |  |         <slot | 
					
						
							|  |  |  |           :name="'item.' + header.value" | 
					
						
							|  |  |  |           v-bind="{ item }" | 
					
						
							|  |  |  |         > | 
					
						
							|  |  |  |           {{ item[header.value] }} | 
					
						
							|  |  |  |         </slot> | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |       </template> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       <template #[`item.actions`]="{ item }"> | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |         <BaseButtonGroup | 
					
						
							|  |  |  |           :buttons="[ | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               icon: $globals.icons.edit, | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |               text: $t('general.edit'), | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |               event: 'edit', | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               icon: $globals.icons.delete, | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |               text: $t('general.delete'), | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |               event: 'delete', | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |           ]" | 
					
						
							|  |  |  |           @delete="$emit('delete-one', item)" | 
					
						
							|  |  |  |           @edit="$emit('edit-one', item)" | 
					
						
							|  |  |  |         /> | 
					
						
							|  |  |  |       </template> | 
					
						
							|  |  |  |     </v-data-table> | 
					
						
							|  |  |  |     <v-card-actions class="justify-end"> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       <slot name="button-bottom" /> | 
					
						
							|  |  |  |       <BaseButton | 
					
						
							|  |  |  |         color="info" | 
					
						
							|  |  |  |         @click="downloadAsJson(data, 'export.json')" | 
					
						
							|  |  |  |       > | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |         <template #icon> | 
					
						
							|  |  |  |           {{ $globals.icons.download }} | 
					
						
							|  |  |  |         </template> | 
					
						
							| 
									
										
										
										
											2022-08-15 23:55:51 +02:00
										 |  |  |         {{ $t("general.download") }} | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |       </BaseButton> | 
					
						
							|  |  |  |     </v-card-actions> | 
					
						
							|  |  |  |   </div> | 
					
						
							|  |  |  | </template> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <script lang="ts"> | 
					
						
							|  |  |  | import { downloadAsJson } from "~/composables/use-utils"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export interface TableConfig { | 
					
						
							|  |  |  |   hideColumns: boolean; | 
					
						
							|  |  |  |   canExport: boolean; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export interface TableHeaders { | 
					
						
							|  |  |  |   text: string; | 
					
						
							|  |  |  |   value: string; | 
					
						
							|  |  |  |   show: boolean; | 
					
						
							|  |  |  |   align?: string; | 
					
						
							| 
									
										
										
										
											2024-12-10 08:10:07 -06:00
										 |  |  |   sortable?: boolean; | 
					
						
							|  |  |  |   sort?: (a: any, b: any) => number; | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export interface BulkAction { | 
					
						
							|  |  |  |   icon: string; | 
					
						
							|  |  |  |   text: string; | 
					
						
							|  |  |  |   event: string; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | export default defineNuxtComponent({ | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |   props: { | 
					
						
							|  |  |  |     tableConfig: { | 
					
						
							|  |  |  |       type: Object as () => TableConfig, | 
					
						
							|  |  |  |       default: () => ({ | 
					
						
							|  |  |  |         hideColumns: false, | 
					
						
							|  |  |  |         canExport: false, | 
					
						
							|  |  |  |       }), | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     headers: { | 
					
						
							|  |  |  |       type: Array as () => TableHeaders[], | 
					
						
							|  |  |  |       required: true, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     data: { | 
					
						
							|  |  |  |       type: Array as () => any[], | 
					
						
							|  |  |  |       required: true, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     bulkActions: { | 
					
						
							|  |  |  |       type: Array as () => BulkAction[], | 
					
						
							|  |  |  |       default: () => [], | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2024-09-22 09:59:20 -05:00
										 |  |  |     initialSort: { | 
					
						
							|  |  |  |       type: String, | 
					
						
							|  |  |  |       default: "id", | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     initialSortDesc: { | 
					
						
							|  |  |  |       type: Boolean, | 
					
						
							|  |  |  |       default: false, | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |   }, | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |   emits: ["delete-one", "edit-one"], | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |   setup(props, context) { | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     const i18n = useI18n(); | 
					
						
							|  |  |  |     const sortBy = computed(() => [{ | 
					
						
							|  |  |  |       key: props.initialSort, | 
					
						
							|  |  |  |       order: props.initialSortDesc ? "desc" : "asc", | 
					
						
							|  |  |  |     }]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |     // ===========================================================
 | 
					
						
							|  |  |  |     // Reactive Headers
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     const filteredHeaders = computed<string[]>(() => { | 
					
						
							|  |  |  |       return props.headers.filter(header => header.show).map(header => header.value); | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     const headersWithoutActions = computed(() => | 
					
						
							|  |  |  |       props.headers | 
					
						
							|  |  |  |         .filter(header => filteredHeaders.value.includes(header.value)) | 
					
						
							|  |  |  |         .map(header => ({ | 
					
						
							|  |  |  |           ...header, | 
					
						
							|  |  |  |           title: i18n.t(header.text), | 
					
						
							|  |  |  |         })), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const activeHeaders = computed(() => [ | 
					
						
							|  |  |  |       ...headersWithoutActions.value, | 
					
						
							|  |  |  |       { title: "", value: "actions", show: true, align: "end" }, | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |     const selected = ref<any[]>([]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // ===========================================================
 | 
					
						
							|  |  |  |     // Bulk Action Event Handler
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const bulkActionListener = computed(() => { | 
					
						
							|  |  |  |       const handlers: { [key: string]: () => void } = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       props.bulkActions.forEach((action) => { | 
					
						
							|  |  |  |         handlers[action.event] = () => { | 
					
						
							|  |  |  |           context.emit(action.event, selected.value); | 
					
						
							| 
									
										
										
										
											2024-02-04 19:55:14 +01:00
										 |  |  |           // clear selection
 | 
					
						
							|  |  |  |           selected.value = []; | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |         }; | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return handlers; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const search = ref(""); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       sortBy, | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |       selected, | 
					
						
							|  |  |  |       filteredHeaders, | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       headersWithoutActions, | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  |       activeHeaders, | 
					
						
							|  |  |  |       bulkActionListener, | 
					
						
							|  |  |  |       search, | 
					
						
							|  |  |  |       downloadAsJson, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | </script> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <style> | 
					
						
							|  |  |  | .clip-width { | 
					
						
							|  |  |  |   max-width: 400px; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | .v-btn--disabled { | 
					
						
							|  |  |  |   opacity: 0.5 !important; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-03-17 10:30:10 -08:00
										 |  |  | </style> |