mirror of
https://github.com/zammad/zammad
synced 2026-05-24 09:48:36 +00:00
204 lines
7.9 KiB
Vue
204 lines
7.9 KiB
Vue
<!-- Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/ -->
|
|
|
|
<script setup lang="ts">
|
|
import { computed, toRef } from 'vue'
|
|
|
|
import type { Props as CommonLinkProps } from '#shared/components/CommonLink/CommonLink.vue'
|
|
|
|
import CommonActionMenu from '#desktop/components/CommonActionMenu/CommonActionMenu.vue'
|
|
import { useTableCheckboxes } from '#desktop/components/CommonTable/composables/useTableCheckboxes.ts'
|
|
import TableCaption from '#desktop/components/CommonTable/TableCaption.vue'
|
|
import TableRow from '#desktop/components/CommonTable/TableRow.vue'
|
|
|
|
import { useCellContent } from './composables/useCellContent.ts'
|
|
|
|
import type { SimpleTableProps, TableSimpleHeader, TableItem, TableItemLinkValue } from './types.ts'
|
|
|
|
const props = withDefaults(defineProps<SimpleTableProps>(), {
|
|
showCaption: false,
|
|
})
|
|
|
|
defineEmits<{
|
|
'click-row': [TableItem]
|
|
}>()
|
|
|
|
// Styling
|
|
const cellAlignmentClasses = {
|
|
right: 'text-right',
|
|
center: 'text-center',
|
|
left: 'text-left',
|
|
}
|
|
|
|
const tableHeaders = computed(() => props.headers)
|
|
|
|
const columnSeparatorClasses = 'border-r border-neutral-100 dark:border-gray-900'
|
|
|
|
const getTooltipText = (item: TableItem, header: TableSimpleHeader) => {
|
|
return header.truncate ? item[header.key] : undefined
|
|
}
|
|
|
|
const rowHandlers = computed(() =>
|
|
props.onClickRow
|
|
? {
|
|
'click-row': (event: TableItem) => {
|
|
if (props.onClickRow) props.onClickRow(event)
|
|
},
|
|
}
|
|
: {},
|
|
)
|
|
|
|
const { getCellContentComponent } = useCellContent()
|
|
|
|
const checkedRows = defineModel<Array<TableItem>>('checkedRows', {
|
|
required: false,
|
|
default: (props: SimpleTableProps) => props.items.filter((item) => item.checked), // is not reactive by default and making it reactive causes other issues.
|
|
})
|
|
|
|
const { hasCheckboxId, allCheckboxRowsSelected, selectAllRowCheckboxes, handleCheckboxUpdate } =
|
|
useTableCheckboxes(checkedRows, toRef(props, 'items'))
|
|
</script>
|
|
|
|
<template>
|
|
<table class="pb-3">
|
|
<TableCaption :show="showCaption">{{ caption }}</TableCaption>
|
|
<thead>
|
|
<tr>
|
|
<th
|
|
v-for="header in tableHeaders"
|
|
:key="header.key"
|
|
class="h-10 p-2.5 text-xs ltr:text-left rtl:text-right"
|
|
:class="[header.headerClass, header.columnSeparator && columnSeparatorClasses]"
|
|
>
|
|
<FormKit
|
|
v-if="hasCheckboxColumn && header.key === 'checkbox'"
|
|
name="checkbox-all-rows"
|
|
:aria-label="
|
|
allCheckboxRowsSelected ? $t('Deselect all entries') : $t('Select all entries')
|
|
"
|
|
type="checkbox"
|
|
:model-value="allCheckboxRowsSelected"
|
|
@update:model-value="selectAllRowCheckboxes($event as boolean)"
|
|
/>
|
|
|
|
<slot v-else :name="`column-header-${header.key}`" :header="header">
|
|
<CommonLabel
|
|
class="font-normal text-stone-200 dark:text-neutral-500"
|
|
:class="[
|
|
cellAlignmentClasses[header.alignContent || 'left'],
|
|
header.labelClass || '',
|
|
]"
|
|
size="small"
|
|
>
|
|
{{ $t(header.label, ...(header.labelPlaceholder || [])) }}
|
|
</CommonLabel>
|
|
</slot>
|
|
|
|
<slot :name="`header-suffix-${header.key}`" :item="header" />
|
|
</th>
|
|
<th v-if="actions" class="h-10 w-0 p-2.5 text-center">
|
|
<CommonLabel class="font-normal text-stone-200! dark:text-neutral-500!" size="small"
|
|
>{{ $t('Actions') }}
|
|
</CommonLabel>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<TableRow
|
|
v-for="item in items"
|
|
:key="item.id"
|
|
:item="item"
|
|
:is-row-selected="!hasCheckboxColumn && item.id === props.selectedRowId"
|
|
:has-checkbox="hasCheckboxColumn"
|
|
v-on="rowHandlers"
|
|
>
|
|
<template #default="{ isRowSelected }">
|
|
<td
|
|
v-for="header in tableHeaders"
|
|
:key="`${item.id}-${header.key}`"
|
|
class="h-10 p-2.5 text-sm"
|
|
:class="[
|
|
header.columnSeparator && columnSeparatorClasses,
|
|
cellAlignmentClasses[header.alignContent || 'left'],
|
|
{
|
|
'max-w-32 truncate text-black dark:text-white': header.truncate,
|
|
'size-10': hasCheckboxColumn && header.key === 'checkbox',
|
|
},
|
|
]"
|
|
>
|
|
<FormKit
|
|
v-if="hasCheckboxColumn && header.key === 'checkbox'"
|
|
:key="`checkbox-${item.id}-${header.key}`"
|
|
:name="`checkbox-${item.id}`"
|
|
:aria-label="
|
|
hasCheckboxId(item.id) ? $t('Deselect this entry') : $t('Select this entry')
|
|
"
|
|
type="checkbox"
|
|
alternative-background
|
|
:classes="{
|
|
decorator:
|
|
'group-active:formkit-checked:border-white group-hover:dark:border-white group-hover:group-active:border-white group-hover:group-active:peer-hover:border-white group-hover:formkit-checked:border-black group-hover:dark:formkit-checked:border-white group-hover:dark:peer-hover:border-white ltr:group-hover:dark:group-hover:peer-hover:formkit-checked:border-white ltr:group-hover:peer-hover:dark:border-white rtl:group-hover:peer-hover:dark:border-white ltr:group-hover:peer-hover:border-black rtl:group-hover:peer-hover:border-black group-hover:border-black',
|
|
decoratorIcon:
|
|
'group-active:formkit-checked:text-white group-hover:formkit-checked:text-black group-hover:formkit-checked:dark:text-white',
|
|
}"
|
|
:disabled="!!item.disabled"
|
|
:model-value="hasCheckboxId(item.id)"
|
|
@click="handleCheckboxUpdate(item)"
|
|
@keydown.enter="handleCheckboxUpdate(item)"
|
|
@keydown.space="handleCheckboxUpdate(item)"
|
|
/>
|
|
<slot
|
|
v-else
|
|
:name="`column-cell-${header.key}`"
|
|
:item="item"
|
|
:is-row-selected="isRowSelected"
|
|
:header="header"
|
|
>
|
|
<CommonLink
|
|
v-if="header.type === 'link'"
|
|
v-tooltip.truncate="getTooltipText(item, header)"
|
|
v-bind="item[header.key] as CommonLinkProps"
|
|
:class="{
|
|
'ltr:text-black rtl:text-black dark:text-white': isRowSelected,
|
|
}"
|
|
class="truncate text-sm group-hover:text-black! group-focus-visible:text-white group-active:text-white hover:no-underline! group-hover:dark:text-white!"
|
|
@click.stop
|
|
@keydown.stop
|
|
>{{ (item[header.key] as TableItemLinkValue).label }}
|
|
</CommonLink>
|
|
<CommonLabel
|
|
v-else
|
|
v-tooltip.truncate="getTooltipText(item, header)"
|
|
class="inline! text-gray-100 group-hover:text-black group-focus-visible:text-white group-active:text-white dark:text-neutral-400 group-hover:dark:text-white"
|
|
:class="[
|
|
{
|
|
'text-black dark:text-white': isRowSelected,
|
|
},
|
|
]"
|
|
>
|
|
<template v-if="!item[header.key]">-</template>
|
|
<component
|
|
:is="getCellContentComponent(header.type)"
|
|
v-else
|
|
:value="item[header.key] as string"
|
|
:is-row-selected="isRowSelected"
|
|
/>
|
|
</CommonLabel>
|
|
</slot>
|
|
|
|
<slot :name="`item-suffix-${header.key}`" :item="item" />
|
|
</td>
|
|
<td v-if="actions" class="h-10 p-2.5 text-center">
|
|
<slot name="actions" v-bind="{ actions, item }">
|
|
<CommonActionMenu
|
|
class="flex items-center justify-center"
|
|
:actions="actions"
|
|
:entity="item"
|
|
button-size="medium"
|
|
/>
|
|
</slot>
|
|
</td>
|
|
</template>
|
|
</TableRow>
|
|
</tbody>
|
|
</table>
|
|
</template>
|