26807984ee
Extracts duplication that built up as shot/asset/task features reached parity: CheckableCommandItem/ColumnToggleList replace 14+ hand-rolled checkbox-list blocks, shared toolbar pieces (debounced search, detail-panel toggle, clear-filters, segmented view/context toggle) replace copy-pasted markup in the three table toolbars, icon-only buttons standardize on the icon-sm size, TaskBulkActionsMenu's Assign To submenu matches Set Status, and a shared DetailPanelOverlay/Header/Loading/Error shell backs all three detail panels (adding a previously-missing error state to the task panel).
100 lines
2.9 KiB
Vue
100 lines
2.9 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import { Settings2 } from 'lucide-vue-next'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from '@/components/ui/popover'
|
|
import {
|
|
Command,
|
|
CommandEmpty,
|
|
CommandGroup,
|
|
CommandInput,
|
|
CommandList,
|
|
CommandSeparator,
|
|
CheckableCommandItem,
|
|
} from '@/components/ui/command'
|
|
import type { VisibilityState } from '@tanstack/vue-table'
|
|
|
|
export interface ColumnOption {
|
|
id: string
|
|
label: string
|
|
/** Optional section label; columns without one are grouped first, unlabeled. */
|
|
group?: string
|
|
}
|
|
|
|
interface Props {
|
|
columns: ColumnOption[]
|
|
modelValue: VisibilityState
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: VisibilityState]
|
|
}>()
|
|
|
|
const isVisible = (columnId: string) => props.modelValue[columnId] !== false
|
|
|
|
const toggle = (columnId: string, value: boolean) => {
|
|
emit('update:modelValue', { ...props.modelValue, [columnId]: value })
|
|
}
|
|
|
|
const hiddenCount = computed(() => props.columns.filter(col => !isVisible(col.id)).length)
|
|
|
|
const groupedColumns = computed(() => {
|
|
const groups = new Map<string | undefined, ColumnOption[]>()
|
|
for (const column of props.columns) {
|
|
const key = column.group
|
|
if (!groups.has(key)) groups.set(key, [])
|
|
groups.get(key)!.push(column)
|
|
}
|
|
return Array.from(groups.entries()).map(([label, columns]) => ({ label, columns }))
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<Popover>
|
|
<PopoverTrigger as-child>
|
|
<Button variant="outline" size="sm" class="h-8 border-dashed">
|
|
<Settings2 class="mr-2 h-4 w-4" />
|
|
View
|
|
<Badge
|
|
v-if="hiddenCount > 0"
|
|
variant="secondary"
|
|
class="ml-2 rounded-sm px-1 font-normal"
|
|
>
|
|
{{ hiddenCount }}
|
|
</Badge>
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent class="w-[200px] p-0" align="end">
|
|
<Command>
|
|
<CommandInput placeholder="Search columns..." />
|
|
<CommandList>
|
|
<CommandEmpty>No columns found.</CommandEmpty>
|
|
<template v-for="group in groupedColumns" :key="group.label ?? '__default__'">
|
|
<CommandGroup>
|
|
<div v-if="group.label" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
|
|
{{ group.label }}
|
|
</div>
|
|
<CommandSeparator v-if="group.label" />
|
|
<CheckableCommandItem
|
|
v-for="column in group.columns"
|
|
:key="column.id"
|
|
:value="column.id"
|
|
:model-value="isVisible(column.id)"
|
|
@update:model-value="(value) => toggle(column.id, value)"
|
|
>
|
|
<span>{{ column.label }}</span>
|
|
</CheckableCommandItem>
|
|
</CommandGroup>
|
|
</template>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</template>
|