chore(front): refactor ActionTable to use new table component
This commit is contained in:
parent
996758170f
commit
6d5f46f416
|
@ -9,6 +9,7 @@ import useLogger from '~/composables/useLogger'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import Section from '~/components/ui/Section.vue'
|
import Section from '~/components/ui/Section.vue'
|
||||||
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
import Toggle from '~/components/ui/Toggle.vue'
|
import Toggle from '~/components/ui/Toggle.vue'
|
||||||
import Input from '~/components/ui/Input.vue'
|
import Input from '~/components/ui/Input.vue'
|
||||||
import Alert from '~/components/ui/Alert.vue'
|
import Alert from '~/components/ui/Alert.vue'
|
||||||
|
@ -117,28 +118,6 @@ const save = async () => {
|
||||||
style="grid-column: 1 / -1;"
|
style="grid-column: 1 / -1;"
|
||||||
@submit.prevent="save"
|
@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" />
|
<Spacer :size="16" />
|
||||||
<div
|
<div
|
||||||
v-for="(setting, key) in settings"
|
v-for="(setting, key) in settings"
|
||||||
|
@ -255,13 +234,40 @@ const save = async () => {
|
||||||
</div>
|
</div>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Layout flex>
|
||||||
type="submit"
|
<Spacer grow />
|
||||||
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
|
<Button
|
||||||
primary
|
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') }}
|
<h4 class="header">
|
||||||
</Button>
|
{{ 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>
|
</form>
|
||||||
</Section>
|
</Section>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { BackendError } from '~/types'
|
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 { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import DangerousButton from '~/components/common/DangerousButton.vue'
|
import DangerousButton from '~/components/common/DangerousButton.vue'
|
||||||
import Layout from '~/components/ui/Layout.vue'
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
|
import Spacer from '~/components/ui/Spacer.vue'
|
||||||
import Button from '~/components/ui/Button.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'
|
import axios from 'axios'
|
||||||
|
|
||||||
|
@ -158,190 +161,218 @@ const launchAction = async () => {
|
||||||
|
|
||||||
isLoading.value = false
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="table-wrapper component-action-table">
|
<div class="table-wrapper component-action-table">
|
||||||
<table class="ui compact very basic unstackable table">
|
<Table
|
||||||
<thead>
|
v-if="objectsData.count > 0"
|
||||||
<tr>
|
:grid-template-columns="gridColumns"
|
||||||
<th colspan="1000">
|
class="ui compact very basic unstackable table"
|
||||||
<div
|
>
|
||||||
v-if="refreshable"
|
<template #header>
|
||||||
class="right floated"
|
<label v-if="actions.length > 0">
|
||||||
>
|
<div class="ui checkbox">
|
||||||
<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"
|
|
||||||
>
|
|
||||||
<!-- TODO (wvffle): Check if we don't have to migrate to v-model -->
|
<!-- TODO (wvffle): Check if we don't have to migrate to v-model -->
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:aria-label="labels.selectItem"
|
:aria-label="labels.selectAllItems"
|
||||||
:disabled="checkable.indexOf(obj[idField]) === -1"
|
:disabled="checkable.length === 0"
|
||||||
:checked="checked.indexOf(obj[idField]) > -1"
|
:checked="checkable.length > 0 && checked.length === checkable.length"
|
||||||
@click="toggleCheck($event, obj[idField], index)"
|
@change="toggleCheckAll"
|
||||||
>
|
>
|
||||||
</td>
|
</div>
|
||||||
<slot
|
</label>
|
||||||
name="row-cells"
|
<slot name="header-cells" />
|
||||||
:obj="obj"
|
</template>
|
||||||
/>
|
|
||||||
</tr>
|
<div
|
||||||
</tbody>
|
v-if="actionUrl && actions.length > 0 || refreshable"
|
||||||
</table>
|
: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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in New Issue