fix: Update TablePersons component with improved layout and name type handling also for organizations
Some checks failed
build.yaml / fix: Update TablePersons component with improved layout and name type handling also for organizations (push) Failing after 0s
Some checks failed
build.yaml / fix: Update TablePersons component with improved layout and name type handling also for organizations (push) Failing after 0s
This commit is contained in:
commit
a4e6f88e07
2 changed files with 196 additions and 120 deletions
|
|
@ -2,8 +2,8 @@
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { mdiTrashCan } from '@mdi/js';
|
import { mdiTrashCan } from '@mdi/js';
|
||||||
import { mdiDragVariant, mdiChevronLeft, mdiChevronRight } from '@mdi/js';
|
import { mdiDragVariant, mdiChevronLeft, mdiChevronRight } from '@mdi/js';
|
||||||
|
import { mdiAccount, mdiDomain } from '@mdi/js';
|
||||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
|
||||||
import BaseButton from '@/Components/BaseButton.vue';
|
import BaseButton from '@/Components/BaseButton.vue';
|
||||||
import { Person } from '@/Dataset';
|
import { Person } from '@/Dataset';
|
||||||
import Draggable from 'vuedraggable';
|
import Draggable from 'vuedraggable';
|
||||||
|
|
@ -21,25 +21,6 @@ interface Props {
|
||||||
canReorder?: boolean;
|
canReorder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// const props = defineProps({
|
|
||||||
// checkable: Boolean,
|
|
||||||
// persons: {
|
|
||||||
// type: Array<Person>,
|
|
||||||
// default: () => [],
|
|
||||||
// },
|
|
||||||
// relation: {
|
|
||||||
// type: String,
|
|
||||||
// required: true,
|
|
||||||
// },
|
|
||||||
// contributortypes: {
|
|
||||||
// type: Object,
|
|
||||||
// default: () => ({}),
|
|
||||||
// },
|
|
||||||
// errors: {
|
|
||||||
// type: Object,
|
|
||||||
// default: () => ({}),
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
checkable: false,
|
checkable: false,
|
||||||
persons: () => [],
|
persons: () => [],
|
||||||
|
|
@ -63,15 +44,18 @@ const perPage = ref(5);
|
||||||
const currentPage = ref(0);
|
const currentPage = ref(0);
|
||||||
const dragEnabled = ref(props.canReorder);
|
const dragEnabled = ref(props.canReorder);
|
||||||
|
|
||||||
|
// Name type options
|
||||||
|
const nameTypeOptions = {
|
||||||
|
'Personal': 'Personal',
|
||||||
|
'Organizational': 'Org'
|
||||||
|
};
|
||||||
|
|
||||||
// Computed properties
|
// Computed properties
|
||||||
const items = computed({
|
const items = computed({
|
||||||
get() {
|
get() {
|
||||||
return props.persons;
|
return props.persons;
|
||||||
},
|
},
|
||||||
// setter
|
|
||||||
set(value) {
|
set(value) {
|
||||||
// Note: we are using destructuring assignment syntax here.
|
|
||||||
|
|
||||||
props.persons.length = 0;
|
props.persons.length = 0;
|
||||||
props.persons.push(...value);
|
props.persons.push(...value);
|
||||||
},
|
},
|
||||||
|
|
@ -122,19 +106,19 @@ const pagesList = computed(() => {
|
||||||
return pages;
|
return pages;
|
||||||
});
|
});
|
||||||
|
|
||||||
// const removeAuthor = (key: number) => {
|
|
||||||
// items.value.splice(key, 1);
|
|
||||||
// };
|
|
||||||
// Methods
|
// Methods
|
||||||
const removeAuthor = (index: number) => {
|
const removeAuthor = (index: number) => {
|
||||||
const actualIndex = perPage.value * currentPage.value + index;
|
const actualIndex = perPage.value * currentPage.value + index;
|
||||||
const person = items.value[actualIndex];
|
const person = items.value[actualIndex];
|
||||||
|
|
||||||
if (confirm(`Are you sure you want to remove ${person.first_name || ''} ${person.last_name || person.email}?`)) {
|
const displayName = person.name_type === 'Organizational'
|
||||||
|
? person.last_name || person.email
|
||||||
|
: `${person.first_name || ''} ${person.last_name || person.email}`.trim();
|
||||||
|
|
||||||
|
if (confirm(`Are you sure you want to remove ${displayName}?`)) {
|
||||||
items.value.splice(actualIndex, 1);
|
items.value.splice(actualIndex, 1);
|
||||||
emit('remove-person', actualIndex, person);
|
emit('remove-person', actualIndex, person);
|
||||||
|
|
||||||
// Adjust current page if needed
|
|
||||||
if (itemsPaginated.value.length === 0 && currentPage.value > 0) {
|
if (itemsPaginated.value.length === 0 && currentPage.value > 0) {
|
||||||
currentPage.value--;
|
currentPage.value--;
|
||||||
}
|
}
|
||||||
|
|
@ -144,6 +128,12 @@ const removeAuthor = (index: number) => {
|
||||||
const updatePerson = (index: number, field: keyof Person, value: any) => {
|
const updatePerson = (index: number, field: keyof Person, value: any) => {
|
||||||
const actualIndex = perPage.value * currentPage.value + index;
|
const actualIndex = perPage.value * currentPage.value + index;
|
||||||
const person = items.value[actualIndex];
|
const person = items.value[actualIndex];
|
||||||
|
|
||||||
|
// Handle name_type change - clear first_name if switching to Organizational
|
||||||
|
if (field === 'name_type' && value === 'Organizational') {
|
||||||
|
person.first_name = '';
|
||||||
|
}
|
||||||
|
|
||||||
(person as any)[field] = value;
|
(person as any)[field] = value;
|
||||||
emit('person-updated', actualIndex, person);
|
emit('person-updated', actualIndex, person);
|
||||||
};
|
};
|
||||||
|
|
@ -170,7 +160,6 @@ const handleDragEnd = (evt: any) => {
|
||||||
watch(
|
watch(
|
||||||
() => props.persons.length,
|
() => props.persons.length,
|
||||||
() => {
|
() => {
|
||||||
// Reset to first page if current page is out of bounds
|
|
||||||
if (currentPage.value >= numPages.value && numPages.value > 0) {
|
if (currentPage.value >= numPages.value && numPages.value > 0) {
|
||||||
currentPage.value = numPages.value - 1;
|
currentPage.value = numPages.value - 1;
|
||||||
}
|
}
|
||||||
|
|
@ -189,18 +178,16 @@ const perPageOptions = [
|
||||||
<template>
|
<template>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<!-- Table Controls -->
|
<!-- Table Controls -->
|
||||||
<div v-if="hasMultiplePages" class="flex justify-between items-center p-3 border-b border-gray-200 dark:border-slate-700">
|
<div v-if="hasMultiplePages" class="flex justify-between items-center px-4 py-2.5 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-800/50">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">
|
<span class="text-xs text-gray-600 dark:text-gray-400">
|
||||||
Showing {{ currentPage * perPage + 1 }} to
|
{{ currentPage * perPage + 1 }}-{{ Math.min((currentPage + 1) * perPage, items.length) }} of {{ items.length }}
|
||||||
{{ Math.min((currentPage + 1) * perPage, items.length) }}
|
|
||||||
of {{ items.length }} entries
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
v-model="perPage"
|
v-model="perPage"
|
||||||
@change="currentPage = 0"
|
@change="currentPage = 0"
|
||||||
class="px-3 py-1 text-sm border rounded-md dark:bg-slate-800 dark:border-slate-600"
|
class="px-2 py-1 text-xs border rounded dark:bg-slate-800 dark:border-slate-600 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
>
|
>
|
||||||
<option v-for="option in perPageOptions" :key="option.value" :value="option.value">
|
<option v-for="option in perPageOptions" :key="option.value" :value="option.value">
|
||||||
{{ option.label }}
|
{{ option.label }}
|
||||||
|
|
@ -210,22 +197,22 @@ const perPageOptions = [
|
||||||
|
|
||||||
<!-- Table -->
|
<!-- Table -->
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="w-full">
|
<table class="w-full table-compact">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="border-b border-gray-200 dark:border-slate-700">
|
<tr class="bg-gray-50 dark:bg-slate-800/50 border-b border-gray-200 dark:border-slate-700">
|
||||||
<th v-if="canReorder" class="w-10 p-3" />
|
<th v-if="canReorder" class="w-8 px-2 py-2" />
|
||||||
<th scope="col" class="text-left p-3">#</th>
|
<th scope="col" class="text-left px-2 py-2 text-xs font-semibold text-gray-600 dark:text-gray-300 w-10">#</th>
|
||||||
<th scope="col">Id</th>
|
<th class="text-left px-2 py-2 text-[10px] font-semibold text-gray-600 dark:text-gray-300 w-40">Type</th>
|
||||||
<th>First Name</th>
|
<th class="text-left px-2 py-2 text-xs font-semibold text-gray-600 dark:text-gray-300 min-w-[120px]">First Name</th>
|
||||||
<th>Last Name / Organization</th>
|
<th class="text-left px-2 py-2 text-xs font-semibold text-gray-600 dark:text-gray-300 min-w-[160px]">Last Name / Org</th>
|
||||||
<th>Orcid</th>
|
<th class="text-left px-2 py-2 text-xs font-semibold text-gray-600 dark:text-gray-300 min-w-[140px]">ORCID</th>
|
||||||
<th>Email</th>
|
<th class="text-left px-2 py-2 text-xs font-semibold text-gray-600 dark:text-gray-300 min-w-[160px]">Email</th>
|
||||||
<th v-if="showContributorTypes" scope="col" class="text-left p-3">Type</th>
|
<th v-if="showContributorTypes" scope="col" class="text-left px-2 py-2 text-xs font-semibold text-gray-600 dark:text-gray-300 w-32">Role</th>
|
||||||
<th v-if="canDelete" class="w-20 p-3">Actions</th>
|
<th v-if="canDelete" class="w-16 px-2 py-2 text-xs font-semibold text-gray-600 dark:text-gray-300">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<!-- <tbody> -->
|
|
||||||
<!-- <tr v-for="(client, index) in itemsPaginated" :key="client.id"> -->
|
<!-- Draggable tbody for non-paginated view -->
|
||||||
<draggable
|
<draggable
|
||||||
v-if="canReorder && !hasMultiplePages"
|
v-if="canReorder && !hasMultiplePages"
|
||||||
tag="tbody"
|
tag="tbody"
|
||||||
|
|
@ -236,114 +223,167 @@ const perPageOptions = [
|
||||||
handle=".drag-handle"
|
handle=".drag-handle"
|
||||||
>
|
>
|
||||||
<template #item="{ index, element }">
|
<template #item="{ index, element }">
|
||||||
<tr class="border-b border-gray-100 dark:border-slate-800 hover:bg-gray-50 dark:hover:bg-slate-800">
|
<tr class="border-b border-gray-100 dark:border-slate-800 hover:bg-blue-50 dark:hover:bg-slate-800/70 transition-colors">
|
||||||
<td class="p-3">
|
<td v-if="canReorder" class="px-2 py-2">
|
||||||
<div class="drag-handle cursor-move text-gray-400 hover:text-gray-600">
|
<div class="drag-handle cursor-move text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||||
<BaseIcon :path="mdiDragVariant" />
|
<BaseIcon :path="mdiDragVariant" :size="18" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="p-3">{{ index + 1 }}</td>
|
<td class="px-2 py-2 text-xs text-gray-600 dark:text-gray-400">{{ index + 1 }}</td>
|
||||||
<td data-label="Id" class="p-3 text-sm text-gray-600">{{ element.id || '-' }}</td>
|
|
||||||
|
|
||||||
<!-- First Name - Hidden for Organizational -->
|
<!-- Name Type Selector -->
|
||||||
<td class="p-3" data-label="First Name" v-if="element.name_type !== 'Organizational'">
|
<td class="px-2 py-2">
|
||||||
|
<div class="flex items-center gap-1.5">
|
||||||
|
<BaseIcon
|
||||||
|
:path="element.name_type === 'Organizational' ? mdiDomain : mdiAccount"
|
||||||
|
:size="16"
|
||||||
|
:class="element.name_type === 'Organizational' ? 'text-purple-500' : 'text-blue-500'"
|
||||||
|
:title="element.name_type"
|
||||||
|
/>
|
||||||
<FormControl
|
<FormControl
|
||||||
|
required
|
||||||
|
v-model="element.name_type"
|
||||||
|
type="select"
|
||||||
|
:options="nameTypeOptions"
|
||||||
|
:is-read-only="element.status == true"
|
||||||
|
class="text-[8px] compact-select-mini flex-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="text-red-500 text-[8px] mt-0.5" v-if="errors && Array.isArray(errors[`${relation}.${index}.name_type`])">
|
||||||
|
{{ errors[`${relation}.${index}.name_type`][0] }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- First Name - Only shown for Personal type -->
|
||||||
|
<td class="px-2 py-2">
|
||||||
|
<FormControl
|
||||||
|
v-if="element.name_type !== 'Organizational'"
|
||||||
required
|
required
|
||||||
v-model="element.first_name"
|
v-model="element.first_name"
|
||||||
type="text"
|
type="text"
|
||||||
:is-read-only="element.status == true"
|
:is-read-only="element.status == true"
|
||||||
placeholder="[FIRST NAME]"
|
placeholder="First name"
|
||||||
|
class="text-xs compact-input"
|
||||||
/>
|
/>
|
||||||
<div class="text-red-400 text-sm" v-if="errors && Array.isArray(errors[`${relation}.${index}.first_name`])">
|
<span v-else class="text-gray-400 text-xs italic">—</span>
|
||||||
{{ errors[`${relation}.${index}.first_name`].join(', ') }}
|
<div class="text-red-500 text-xs mt-0.5" v-if="errors && Array.isArray(errors[`${relation}.${index}.first_name`])">
|
||||||
|
{{ errors[`${relation}.${index}.first_name`][0] }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td v-else></td>
|
|
||||||
<!-- Empty cell for organizational entries -->
|
|
||||||
|
|
||||||
<!-- Last Name / Organization Name -->
|
<!-- Last Name / Organization Name -->
|
||||||
<td :data-label="element.name_type === 'Organizational' ? 'Organization Name' : 'Last Name'">
|
<td class="px-2 py-2">
|
||||||
<FormControl
|
<FormControl
|
||||||
required
|
required
|
||||||
v-model="element.last_name"
|
v-model="element.last_name"
|
||||||
type="text"
|
type="text"
|
||||||
:is-read-only="element.status == true"
|
:is-read-only="element.status == true"
|
||||||
:placeholder="element.name_type === 'Organizational' ? '[ORGANIZATION NAME]' : '[LAST NAME]'"
|
:placeholder="element.name_type === 'Organizational' ? 'Organization' : 'Last name'"
|
||||||
|
class="text-xs compact-input"
|
||||||
/>
|
/>
|
||||||
<div class="text-red-400 text-sm" v-if="errors && Array.isArray(errors[`${relation}.${index}.last_name`])">
|
<div class="text-red-500 text-xs mt-0.5" v-if="errors && Array.isArray(errors[`${relation}.${index}.last_name`])">
|
||||||
{{ errors[`${relation}.${index}.last_name`].join(', ') }}
|
{{ errors[`${relation}.${index}.last_name`][0] }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Orcid -->
|
<!-- Orcid -->
|
||||||
<td data-label="Orcid">
|
<td class="px-2 py-2">
|
||||||
<FormControl
|
<FormControl
|
||||||
v-model="element.identifier_orcid"
|
v-model="element.identifier_orcid"
|
||||||
type="text"
|
type="text"
|
||||||
:is-read-only="element.status == true"
|
:is-read-only="element.status == true"
|
||||||
|
placeholder="0000-0000-0000-0000"
|
||||||
|
class="text-xs compact-input font-mono"
|
||||||
/>
|
/>
|
||||||
<div class="text-red-400 text-sm" v-if="errors && Array.isArray(errors[`${relation}.${index}.identifier_orcid`])">
|
<div class="text-red-500 text-xs mt-0.5" v-if="errors && Array.isArray(errors[`${relation}.${index}.identifier_orcid`])">
|
||||||
{{ errors[`${relation}.${index}.identifier_orcid`].join(', ') }}
|
{{ errors[`${relation}.${index}.identifier_orcid`][0] }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Email -->
|
<!-- Email -->
|
||||||
<td data-label="Email">
|
<td class="px-2 py-2">
|
||||||
<FormControl
|
<FormControl
|
||||||
required
|
required
|
||||||
v-model="element.email"
|
v-model="element.email"
|
||||||
type="text"
|
type="email"
|
||||||
:is-read-only="element.status == true"
|
:is-read-only="element.status == true"
|
||||||
placeholder="[EMAIL]"
|
placeholder="email@example.com"
|
||||||
|
class="text-xs compact-input"
|
||||||
/>
|
/>
|
||||||
<div class="text-red-400 text-sm" v-if="errors && Array.isArray(errors[`${relation}.${index}.email`])">
|
<div class="text-red-500 text-xs mt-0.5" v-if="errors && Array.isArray(errors[`${relation}.${index}.email`])">
|
||||||
{{ errors[`${relation}.${index}.email`].join(', ') }}
|
{{ errors[`${relation}.${index}.email`][0] }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Contributor Type -->
|
<!-- Contributor Type -->
|
||||||
<td v-if="Object.keys(contributortypes).length">
|
<td v-if="Object.keys(contributortypes).length" class="px-2 py-2">
|
||||||
<FormControl
|
<FormControl
|
||||||
required
|
required
|
||||||
v-model="element.pivot_contributor_type"
|
v-model="element.pivot_contributor_type"
|
||||||
type="select"
|
type="select"
|
||||||
:options="contributortypes"
|
:options="contributortypes"
|
||||||
placeholder="[relation type]"
|
placeholder="Role"
|
||||||
>
|
class="text-xs compact-select"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
class="text-red-400 text-sm"
|
class="text-red-500 text-xs mt-0.5"
|
||||||
v-if="errors && Array.isArray(errors[`${relation}.${index}.pivot_contributor_type`])"
|
v-if="errors && Array.isArray(errors[`${relation}.${index}.pivot_contributor_type`])"
|
||||||
>
|
>
|
||||||
{{ errors[`${relation}.${index}.pivot_contributor_type`].join(', ') }}
|
{{ errors[`${relation}.${index}.pivot_contributor_type`][0] }}
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<td class="before:hidden lg:w-1 whitespace-nowrap">
|
<td class="px-2 py-2 whitespace-nowrap">
|
||||||
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
<BaseButton
|
||||||
<BaseButton color="danger" :icon="mdiTrashCan" small @click.prevent="removeAuthor(index)" />
|
color="danger"
|
||||||
</BaseButtons>
|
:icon="mdiTrashCan"
|
||||||
|
small
|
||||||
|
@click.prevent="removeAuthor(index)"
|
||||||
|
class="compact-button"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
<!-- </tbody> -->
|
|
||||||
<!-- Non-draggable tbody for paginated view -->
|
<!-- Non-draggable tbody for paginated view -->
|
||||||
<tbody v-else>
|
<tbody v-else>
|
||||||
<tr
|
<tr
|
||||||
v-for="(element, index) in itemsPaginated"
|
v-for="(element, index) in itemsPaginated"
|
||||||
:key="element.id || index"
|
:key="element.id || index"
|
||||||
class="border-b border-gray-100 dark:border-slate-800 hover:bg-gray-50 dark:hover:bg-slate-800"
|
class="border-b border-gray-100 dark:border-slate-800 hover:bg-blue-50 dark:hover:bg-slate-800/70 transition-colors"
|
||||||
>
|
>
|
||||||
<td v-if="canReorder" class="p-3 text-gray-400">
|
<td v-if="canReorder" class="px-2 py-2 text-gray-400">
|
||||||
<BaseIcon :path="mdiDragVariant" />
|
<BaseIcon :path="mdiDragVariant" :size="18" />
|
||||||
</td>
|
</td>
|
||||||
<td class="p-3">{{ currentPage * perPage + index + 1 }}</td>
|
<td class="px-2 py-2 text-xs text-gray-600 dark:text-gray-400">{{ currentPage * perPage + index + 1 }}</td>
|
||||||
<td class="p-3 text-sm text-gray-600">{{ element.id || '-' }}</td>
|
|
||||||
|
|
||||||
<!-- Same field structure as draggable version -->
|
<!-- Name Type Selector -->
|
||||||
<td class="p-3">
|
<td class="px-2 py-2">
|
||||||
|
<BaseIcon
|
||||||
|
:path="element.name_type === 'Organizational' ? mdiDomain : mdiAccount"
|
||||||
|
:size="16"
|
||||||
|
:class="element.name_type === 'Organizational' ? 'text-purple-500' : 'text-blue-500'"
|
||||||
|
:title="element.name_type"
|
||||||
|
/>
|
||||||
|
<FormControl
|
||||||
|
required
|
||||||
|
:model-value="element.name_type"
|
||||||
|
@update:model-value="updatePerson(index, 'name_type', $event)"
|
||||||
|
type="select"
|
||||||
|
:options="nameTypeOptions"
|
||||||
|
:is-read-only="element.status || !canEdit"
|
||||||
|
class="text-xs compact-select"
|
||||||
|
:error="getFieldError(index, 'name_type')"
|
||||||
|
/>
|
||||||
|
<div v-if="getFieldError(index, 'name_type')" class="text-red-500 text-xs mt-0.5">
|
||||||
|
{{ getFieldError(index, 'name_type') }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- First Name -->
|
||||||
|
<td class="px-2 py-2">
|
||||||
<FormControl
|
<FormControl
|
||||||
v-if="element.name_type !== 'Organizational'"
|
v-if="element.name_type !== 'Organizational'"
|
||||||
required
|
required
|
||||||
|
|
@ -351,59 +391,68 @@ const perPageOptions = [
|
||||||
@update:model-value="updatePerson(index, 'first_name', $event)"
|
@update:model-value="updatePerson(index, 'first_name', $event)"
|
||||||
type="text"
|
type="text"
|
||||||
:is-read-only="element.status || !canEdit"
|
:is-read-only="element.status || !canEdit"
|
||||||
placeholder="[FIRST NAME]"
|
placeholder="First name"
|
||||||
|
class="text-xs compact-input"
|
||||||
:error="getFieldError(index, 'first_name')"
|
:error="getFieldError(index, 'first_name')"
|
||||||
/>
|
/>
|
||||||
<span v-else class="text-gray-400">-</span>
|
<span v-else class="text-gray-400 text-xs italic">—</span>
|
||||||
<div v-if="getFieldError(index, 'first_name')" class="text-red-400 text-sm">
|
<div v-if="getFieldError(index, 'first_name')" class="text-red-500 text-xs mt-0.5">
|
||||||
{{ getFieldError(index, 'first_name') }}
|
{{ getFieldError(index, 'first_name') }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="p-3">
|
<!-- Last Name / Organization -->
|
||||||
|
<td class="px-2 py-2">
|
||||||
<FormControl
|
<FormControl
|
||||||
required
|
required
|
||||||
:model-value="element.last_name"
|
:model-value="element.last_name"
|
||||||
@update:model-value="updatePerson(index, 'last_name', $event)"
|
@update:model-value="updatePerson(index, 'last_name', $event)"
|
||||||
type="text"
|
type="text"
|
||||||
:is-read-only="element.status || !canEdit"
|
:is-read-only="element.status || !canEdit"
|
||||||
:placeholder="element.name_type === 'Organizational' ? '[ORGANIZATION NAME]' : '[LAST NAME]'"
|
:placeholder="element.name_type === 'Organizational' ? 'Organization' : 'Last name'"
|
||||||
|
class="text-xs compact-input"
|
||||||
:error="getFieldError(index, 'last_name')"
|
:error="getFieldError(index, 'last_name')"
|
||||||
/>
|
/>
|
||||||
<div v-if="getFieldError(index, 'last_name')" class="text-red-400 text-sm">
|
<div v-if="getFieldError(index, 'last_name')" class="text-red-500 text-xs mt-0.5">
|
||||||
{{ getFieldError(index, 'last_name') }}
|
{{ getFieldError(index, 'last_name') }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="p-3">
|
<!-- Orcid -->
|
||||||
|
<td class="px-2 py-2">
|
||||||
<FormControl
|
<FormControl
|
||||||
:model-value="element.identifier_orcid"
|
:model-value="element.identifier_orcid"
|
||||||
@update:model-value="updatePerson(index, 'identifier_orcid', $event)"
|
@update:model-value="updatePerson(index, 'identifier_orcid', $event)"
|
||||||
type="text"
|
type="text"
|
||||||
:is-read-only="element.status || !canEdit"
|
:is-read-only="element.status || !canEdit"
|
||||||
|
placeholder="0000-0000-0000-0000"
|
||||||
|
class="text-xs compact-input font-mono"
|
||||||
:error="getFieldError(index, 'identifier_orcid')"
|
:error="getFieldError(index, 'identifier_orcid')"
|
||||||
/>
|
/>
|
||||||
<div v-if="getFieldError(index, 'identifier_orcid')" class="text-red-400 text-sm">
|
<div v-if="getFieldError(index, 'identifier_orcid')" class="text-red-500 text-xs mt-0.5">
|
||||||
{{ getFieldError(index, 'identifier_orcid') }}
|
{{ getFieldError(index, 'identifier_orcid') }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="p-3">
|
<!-- Email -->
|
||||||
|
<td class="px-2 py-2">
|
||||||
<FormControl
|
<FormControl
|
||||||
required
|
required
|
||||||
:model-value="element.email"
|
:model-value="element.email"
|
||||||
@update:model-value="updatePerson(index, 'email', $event)"
|
@update:model-value="updatePerson(index, 'email', $event)"
|
||||||
type="email"
|
type="email"
|
||||||
:is-read-only="element.status || !canEdit"
|
:is-read-only="element.status || !canEdit"
|
||||||
placeholder="[EMAIL]"
|
placeholder="email@example.com"
|
||||||
|
class="text-xs compact-input"
|
||||||
:error="getFieldError(index, 'email')"
|
:error="getFieldError(index, 'email')"
|
||||||
/>
|
/>
|
||||||
<div v-if="getFieldError(index, 'email')" class="text-red-400 text-sm">
|
<div v-if="getFieldError(index, 'email')" class="text-red-500 text-xs mt-0.5">
|
||||||
{{ getFieldError(index, 'email') }}
|
{{ getFieldError(index, 'email') }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td v-if="showContributorTypes" class="p-3">
|
<!-- Contributor Type -->
|
||||||
|
<td v-if="showContributorTypes" class="px-2 py-2">
|
||||||
<FormControl
|
<FormControl
|
||||||
required
|
required
|
||||||
:model-value="element.pivot_contributor_type"
|
:model-value="element.pivot_contributor_type"
|
||||||
|
|
@ -411,15 +460,39 @@ const perPageOptions = [
|
||||||
type="select"
|
type="select"
|
||||||
:options="contributortypes"
|
:options="contributortypes"
|
||||||
:is-read-only="element.status || !canEdit"
|
:is-read-only="element.status || !canEdit"
|
||||||
placeholder="[Select type]"
|
placeholder="Role"
|
||||||
|
class="text-xs compact-select"
|
||||||
:error="getFieldError(index, 'pivot_contributor_type')"
|
:error="getFieldError(index, 'pivot_contributor_type')"
|
||||||
/>
|
/>
|
||||||
<div v-if="getFieldError(index, 'pivot_contributor_type')" class="text-red-400 text-sm">
|
<div v-if="getFieldError(index, 'pivot_contributor_type')" class="text-red-500 text-xs mt-0.5">
|
||||||
{{ getFieldError(index, 'pivot_contributor_type') }}
|
{{ getFieldError(index, 'pivot_contributor_type') }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td v-if="canDelete" class="p-3">
|
<!-- Actions -->
|
||||||
|
<td v-if="canDelete" class="px-2 py-2 whitespace-nowrap">
|
||||||
|
<BaseButton
|
||||||
|
color="danger"
|
||||||
|
:icon="mdiTrashCan"
|
||||||
|
small
|
||||||
|
@click="removeAuthor(index)"
|
||||||
|
:disabled="element.status || !canEdit"
|
||||||
|
title="Remove person"
|
||||||
|
class="compact-button"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<!-- <tr v-if="items.length === 0">
|
||||||
|
<td :colspan="showContributorTypes ? 9 : 8" class="text-center py-12 text-gray-400">
|
||||||
|
<div class="flex flex-col items-center gap-2">
|
||||||
|
<BaseIcon :path="mdiBookOpenPageVariant" :size="32" class="text-gray-300" />
|
||||||
|
<span class="text-sm">No persons added yet</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>-if="canDelete" class="p-3">
|
||||||
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
color="danger"
|
color="danger"
|
||||||
|
|
@ -433,10 +506,12 @@ const perPageOptions = [
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- Empty State -->
|
|
||||||
<tr v-if="items.length === 0">
|
<tr v-if="items.length === 0">
|
||||||
<td :colspan="canReorder ? 8 : 7" class="text-center p-8 text-gray-500">No persons added yet</td>
|
<td :colspan="showContributorTypes ? 10 : 9" class="text-center p-8 text-gray-500">
|
||||||
</tr>
|
No persons added yet
|
||||||
|
</td>
|
||||||
|
</tr> -->
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -467,7 +542,9 @@ const perPageOptions = [
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400"> Page {{ currentPageHuman }} of {{ numPages }} </span>
|
<span class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
Page {{ currentPageHuman }} of {{ numPages }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -481,7 +558,6 @@ const perPageOptions = [
|
||||||
@apply bg-white dark:bg-slate-900 rounded-lg shadow-sm;
|
@apply bg-white dark:bg-slate-900 rounded-lg shadow-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Improve table responsiveness */
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
table {
|
table {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ async function scanFileForViruses(filePath: string | undefined, options: Options
|
||||||
active: true, // If true, this module will consider using the clamdscan binary
|
active: true, // If true, this module will consider using the clamdscan binary
|
||||||
host: options.host, // IP of host to connect to TCP interface,
|
host: options.host, // IP of host to connect to TCP interface,
|
||||||
port: options.port, // Port of host to use when connecting to TCP interface
|
port: options.port, // Port of host to use when connecting to TCP interface
|
||||||
socket: '/var/run/clamav/clamd.socket', // Socket file for connecting via socket
|
// socket: '/var/run/clamav/clamd.socket', // Socket file for connecting via socket
|
||||||
localFallback: false, // Use local clamscan binary if socket/tcp fails
|
// localFallback: false, // Use local clamscan binary if socket/tcp fails
|
||||||
// port: options.port,
|
// port: options.port,
|
||||||
multiscan: true, // Scan using all available cores! Yay!
|
multiscan: true, // Scan using all available cores! Yay!
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue