chore(front): refactor ActionTable to use new table component

This commit is contained in:
ArneBo 2025-03-20 13:02:32 +01:00
parent 996758170f
commit 6d5f46f416
2 changed files with 244 additions and 207 deletions

View File

@ -9,6 +9,7 @@ import useLogger from '~/composables/useLogger'
import { useI18n } from 'vue-i18n'
import Section from '~/components/ui/Section.vue'
import Layout from '~/components/ui/Layout.vue'
import Toggle from '~/components/ui/Toggle.vue'
import Input from '~/components/ui/Input.vue'
import Alert from '~/components/ui/Alert.vue'
@ -117,28 +118,6 @@ const save = async () => {
style="grid-column: 1 / -1;"
@submit.prevent="save"
>
<Alert
v-if="errors.length > 0"
red
>
<h4 class="header">
{{ t('components.admin.SettingsGroup.header.error') }}
</h4>
<ul class="list">
<li
v-for="(error, key) in errors"
:key="key"
>
{{ error }}
</li>
</ul>
</Alert>
<Alert
v-if="result"
green
>
{{ t('components.admin.SettingsGroup.message.success') }}
</Alert>
<Spacer :size="16" />
<div
v-for="(setting, key) in settings"
@ -255,13 +234,40 @@ const save = async () => {
</div>
<Spacer />
</div>
<Button
type="submit"
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
primary
<Layout flex>
<Spacer grow />
<Button
type="submit"
:class="[{'loading': isLoading}]"
primary
>
{{ t('components.admin.SettingsGroup.button.save') }}
</Button>
</Layout>
<Spacer />
<Alert
v-if="errors.length > 0"
red
>
{{ t('components.admin.SettingsGroup.button.save') }}
</Button>
<h4 class="header">
{{ group.label }}:
{{ t('components.admin.SettingsGroup.header.error') }}
</h4>
<ul class="list">
<li
v-for="(error, key) in errors"
:key="key"
>
{{ error }}
</li>
</ul>
</Alert>
<Alert
v-if="result"
green
>
{{ t('components.admin.SettingsGroup.message.success') }}
</Alert>
</form>
</Section>
<Spacer />

View File

@ -1,12 +1,15 @@
<script setup lang="ts">
import type { BackendError } from '~/types'
import { ref, computed, reactive, watch } from 'vue'
import { ref, computed, reactive, watch, useSlots } from 'vue'
import { useI18n } from 'vue-i18n'
import DangerousButton from '~/components/common/DangerousButton.vue'
import Layout from '~/components/ui/Layout.vue'
import Spacer from '~/components/ui/Spacer.vue'
import Button from '~/components/ui/Button.vue'
import Table from '~/components/ui/Table.vue'
import Alert from '~/components/ui/Alert.vue'
import axios from 'axios'
@ -158,190 +161,218 @@ const launchAction = async () => {
isLoading.value = false
}
const slots = useSlots();
// Count the number of elements inside the "header-cells" slot
const columnCount = computed(() => {
return slots['header-cells'] ? slots['header-cells']().length : 0;
});
// Compute the correct number of columns
const gridColumns = computed(() => {
let columns = columnCount.value;
// Add 1 column if for the checkbox if there are more than one action
if (props.actions.length > 0) {
columns += 1;
}
return Array.from({ length: columns }, () => 'auto');
})
</script>
<template>
<div class="table-wrapper component-action-table">
<table class="ui compact very basic unstackable table">
<thead>
<tr>
<th colspan="1000">
<div
v-if="refreshable"
class="right floated"
>
<span v-if="needsRefresh">
{{ t('components.common.ActionTable.message.needsRefresh') }}
</span>
<Button
tiny
icon="bi-arrow-clockwise"
:title="labels.refresh"
:aria-label="labels.refresh"
@click="$emit('refresh')"
/>
</div>
<Layout
v-if="actionUrl && actions.length > 0"
stack
no-gap
class="ui form"
>
<label for="actions-select">{{ t('components.common.ActionTable.label.actions') }}</label>
<Layout flex>
<select
id="actions-select"
v-model="currentActionName"
class="ui dropdown"
>
<option
v-for="action in actions"
:key="action.name"
:value="action.name"
>
{{ action.label }}
</option>
</select>
<dangerous-button
v-if="selectAll || currentAction?.isDangerous"
:disabled="checked.length === 0 || undefined"
:is-loading="isLoading"
:confirm-color="currentAction?.confirmColor ?? 'success'"
style="margin-top: 7px;"
:aria-label="labels.performAction"
:title="t('components.common.ActionTable.modal.performAction.header', { action: currentActionName }, affectedObjectsCount)"
@confirm="launchAction"
>
{{ t('components.common.ActionTable.button.go') }}
<template #modal-content>
<template v-if="currentAction?.confirmationMessage">
{{ currentAction?.confirmationMessage }}
</template>
<span v-else>
{{ t('components.common.ActionTable.modal.performAction.content.warning') }}
</span>
</template>
<template #modal-confirm>
<span :aria-label="labels.performAction">
{{ t('components.common.ActionTable.button.launch') }}
</span>
</template>
</dangerous-button>
<Button
v-else
primary
:disabled="checked.length === 0"
:aria-label="labels.performAction"
:class="[{disabled: checked.length === 0}, {'loading': isLoading}]"
style="margin-top: 7px;"
@click="launchAction"
>
{{ t('components.common.ActionTable.button.go') }}
</Button>
<div class="count field">
<span v-if="selectAll">
{{ t('components.common.ActionTable.button.allSelected', objectsData.count) }}
</span>
<span v-else>
{{ t('components.common.ActionTable.button.selected', { total: objectsData.count }, checked.length) }}
</span>
<template v-if="currentAction?.allowAll && checkable.length > 0 && checkable.length === checked.length">
<a
v-if="!selectAll"
href=""
@click.prevent="selectAll = true"
>
<span key="3">
{{ t('components.common.ActionTable.button.selectElement', objectsData.count) }}
</span>
</a>
<a
v-else
href=""
@click.prevent="selectAll = false"
>
<span key="4">
{{ t('components.common.ActionTable.button.selectCurrentPage') }}
</span>
</a>
</template>
</div>
</Layout>
<Alert
v-if="errors.length > 0"
red
>
<h4 class="header">
{{ t('components.common.ActionTable.header.error') }}
</h4>
<ul class="list">
<li
v-for="(error, key) in errors"
:key="key"
>
{{ error }}
</li>
</ul>
</Alert>
<Alert
v-if="result"
green
>
<p>
<span>
{{ t('components.common.ActionTable.message.success', { action: result.action }, result.updated) }}
</span>
</p>
<slot
name="action-success-footer"
:result="result"
/>
</Alert>
</Layout>
</th>
</tr>
<tr>
<th v-if="actions.length > 0">
<div class="ui checkbox">
<!-- TODO (wvffle): Check if we don't have to migrate to v-model -->
<input
type="checkbox"
:aria-label="labels.selectAllItems"
:disabled="checkable.length === 0"
:checked="checkable.length > 0 && checked.length === checkable.length"
@change="toggleCheckAll"
>
</div>
</th>
<slot name="header-cells" />
</tr>
</thead>
<tbody v-if="objectsData.count > 0">
<tr
v-for="(obj, index) in objects"
:key="index"
>
<td
v-if="actions.length > 0"
class="collapsing"
>
<Table
v-if="objectsData.count > 0"
:grid-template-columns="gridColumns"
class="ui compact very basic unstackable table"
>
<template #header>
<label v-if="actions.length > 0">
<div class="ui checkbox">
<!-- TODO (wvffle): Check if we don't have to migrate to v-model -->
<input
type="checkbox"
:aria-label="labels.selectItem"
:disabled="checkable.indexOf(obj[idField]) === -1"
:checked="checked.indexOf(obj[idField]) > -1"
@click="toggleCheck($event, obj[idField], index)"
type="checkbox"
:aria-label="labels.selectAllItems"
:disabled="checkable.length === 0"
:checked="checkable.length > 0 && checked.length === checkable.length"
@change="toggleCheckAll"
>
</td>
<slot
name="row-cells"
:obj="obj"
/>
</tr>
</tbody>
</table>
</div>
</label>
<slot name="header-cells" />
</template>
<div
v-if="actionUrl && actions.length > 0 || refreshable"
:style="{ gridColumn: `span ${gridColumns.length}`, height: '128px' }"
>
<Layout
stack
no-gap
v-if="actionUrl && actions.length > 0"
>
<label for="actions-select">{{ t('components.common.ActionTable.label.actions') }}</label>
<Layout flex class="ui form">
<select
id="actions-select"
v-model="currentActionName"
class="dropdown"
>
<option
v-for="action in actions"
:key="action.name"
:value="action.name"
>
{{ action.label }}
</option>
</select>
<dangerous-button
v-if="selectAll || currentAction?.isDangerous"
:disabled="checked.length === 0 || undefined"
:is-loading="isLoading"
:confirm-color="currentAction?.confirmColor ?? 'success'"
style="margin-top: 7px;"
:aria-label="labels.performAction"
:title="t('components.common.ActionTable.modal.performAction.header', { action: currentActionName }, affectedObjectsCount)"
@confirm="launchAction"
>
{{ t('components.common.ActionTable.button.go') }}
<template #modal-content>
<template v-if="currentAction?.confirmationMessage">
{{ currentAction?.confirmationMessage }}
</template>
<span v-else>
{{ t('components.common.ActionTable.modal.performAction.content.warning') }}
</span>
</template>
<template #modal-confirm>
<span :aria-label="labels.performAction">
{{ t('components.common.ActionTable.button.launch') }}
</span>
</template>
</dangerous-button>
<Button
v-else
primary
:disabled="checked.length === 0"
:aria-label="labels.performAction"
:class="[{disabled: checked.length === 0}, {'loading': isLoading}]"
style="margin-top: 7px;"
@click="launchAction"
>
{{ t('components.common.ActionTable.button.go') }}
</Button>
<div class="count field">
<span v-if="selectAll">
{{ t('components.common.ActionTable.button.allSelected', objectsData.count) }}
</span>
<span v-else>
{{ t('components.common.ActionTable.button.selected', { total: objectsData.count }, checked.length) }}
</span>
<template v-if="currentAction?.allowAll && checkable.length > 0 && checkable.length === checked.length">
<a
v-if="!selectAll"
href=""
@click.prevent="selectAll = true"
>
<span key="3">
{{ t('components.common.ActionTable.button.selectElement', objectsData.count) }}
</span>
</a>
<a
v-else
href=""
@click.prevent="selectAll = false"
>
<span key="4">
{{ t('components.common.ActionTable.button.selectCurrentPage') }}
</span>
</a>
</template>
</div>
<Alert
v-if="errors.length > 0"
red
>
<h4 class="header">
{{ t('components.common.ActionTable.header.error') }}
</h4>
<ul class="list">
<li
v-for="(error, key) in errors"
:key="key"
>
{{ error }}
</li>
</ul>
</Alert>
<Alert
v-if="result"
green
>
<p>
<span>
{{ t('components.common.ActionTable.message.success', { action: result.action }, result.updated) }}
</span>
</p>
<slot
name="action-success-footer"
:result="result"
/>
</Alert>
</Layout>
</Layout>
<Spacer grow />
<Layout
label
v-if="refreshable"
class="right floated"
>
<span v-if="needsRefresh">
{{ t('components.common.ActionTable.message.needsRefresh') }}
</span>
<Button
primary
icon="bi-arrow-clockwise"
:title="labels.refresh"
:aria-label="labels.refresh"
@click="$emit('refresh')"
style="align-self: end;"
>
{{ labels.refresh }}
</Button>
</Layout>
</div>
<template
v-for="(obj, index) in objects"
:key="index"
>
<label
v-if="actions.length > 0"
class="collapsing"
>
<!-- TODO (wvffle): Check if we don't have to migrate to v-model -->
<input
type="checkbox"
:aria-label="labels.selectItem"
:disabled="checkable.indexOf(obj[idField]) === -1"
:checked="checked.indexOf(obj[idField]) > -1"
@click="toggleCheck($event, obj[idField], index)"
>
</label>
<slot
name="row-cells"
:obj="obj"
/>
</template>
</Table>
</div>
</template>