feat: Enhance Person data structure and improve TablePersons component
- Updated Person interface to include first_name and last_name fields for better clarity and organization handling. - Modified TablePersons.vue to support new fields, including improved pagination and drag-and-drop functionality. - Added loading states and error handling for form controls within the table. - Enhanced the visual layout of the table with responsive design adjustments. - Updated solr.xslt to correctly reference ServerDateModified and EmbargoDate attributes. - updated AvatarController - improved download method for editor, and reviewer - improved security for officlial download file file API: filterd by server_state
This commit is contained in:
parent
e1ccf0ddc8
commit
06ed2f3625
12 changed files with 3143 additions and 1387 deletions
|
@ -1,45 +1,69 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
// import { MainService } from '@/Stores/main';
|
||||
// import { StyleService } from '@/Stores/style.service';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { mdiTrashCan } from '@mdi/js';
|
||||
import { mdiDragVariant } from '@mdi/js';
|
||||
import { mdiDragVariant, mdiChevronLeft, mdiChevronRight } from '@mdi/js';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
// import CardBoxModal from '@/Components/CardBoxModal.vue';
|
||||
// import TableCheckboxCell from '@/Components/TableCheckboxCell.vue';
|
||||
// import BaseLevel from '@/Components/BaseLevel.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
// import UserAvatar from '@/Components/UserAvatar.vue';
|
||||
// import Person from 'App/Models/Person';
|
||||
import { Person } from '@/Dataset';
|
||||
import Draggable from 'vuedraggable';
|
||||
import FormControl from '@/Components/FormControl.vue';
|
||||
|
||||
const props = defineProps({
|
||||
checkable: Boolean,
|
||||
persons: {
|
||||
type: Array<Person>,
|
||||
default: () => [],
|
||||
},
|
||||
relation: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
contributortypes: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
errors: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
interface Props {
|
||||
checkable?: boolean;
|
||||
persons?: Person[];
|
||||
relation: string;
|
||||
contributortypes?: Record<string, string>;
|
||||
errors?: Record<string, string[]>;
|
||||
isLoading?: boolean;
|
||||
canDelete?: boolean;
|
||||
canEdit?: 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>(), {
|
||||
checkable: false,
|
||||
persons: () => [],
|
||||
contributortypes: () => ({}),
|
||||
errors: () => ({}),
|
||||
isLoading: false,
|
||||
canDelete: true,
|
||||
canEdit: true,
|
||||
canReorder: true,
|
||||
});
|
||||
|
||||
// const styleService = StyleService();
|
||||
// const mainService = MainService();
|
||||
// const items = computed(() => props.persons);
|
||||
const emit = defineEmits<{
|
||||
'update:persons': [value: Person[]];
|
||||
'remove-person': [index: number, person: Person];
|
||||
'person-updated': [index: number, person: Person];
|
||||
'reorder': [oldIndex: number, newIndex: number];
|
||||
}>();
|
||||
|
||||
// Local state
|
||||
const perPage = ref(5);
|
||||
const currentPage = ref(0);
|
||||
const dragEnabled = ref(props.canReorder);
|
||||
|
||||
// Computed properties
|
||||
const items = computed({
|
||||
get() {
|
||||
return props.persons;
|
||||
|
@ -53,221 +77,393 @@ const items = computed({
|
|||
},
|
||||
});
|
||||
|
||||
// const isModalActive = ref(false);
|
||||
// const isModalDangerActive = ref(false);
|
||||
const perPage = ref(5);
|
||||
const currentPage = ref(0);
|
||||
// const checkedRows = ref([]);
|
||||
|
||||
const itemsPaginated = computed({
|
||||
get() {
|
||||
return items.value.slice(perPage.value * currentPage.value, perPage.value * (currentPage.value + 1));
|
||||
},
|
||||
// setter
|
||||
set(value) {
|
||||
// Note: we are using destructuring assignment syntax here.
|
||||
|
||||
props.persons.length = 0;
|
||||
props.persons.push(...value);
|
||||
},
|
||||
const itemsPaginated = computed(() => {
|
||||
const start = perPage.value * currentPage.value;
|
||||
const end = perPage.value * (currentPage.value + 1);
|
||||
return items.value.slice(start, end);
|
||||
});
|
||||
|
||||
const numPages = computed(() => Math.ceil(items.value.length / perPage.value));
|
||||
|
||||
const currentPageHuman = computed(() => currentPage.value + 1);
|
||||
const hasMultiplePages = computed(() => numPages.value > 1);
|
||||
const showContributorTypes = computed(() => Object.keys(props.contributortypes).length > 0);
|
||||
|
||||
const pagesList = computed(() => {
|
||||
const pagesList: Array<number> = [];
|
||||
const pages: number[] = [];
|
||||
const maxVisible = 10;
|
||||
|
||||
for (let i = 0; i < numPages.value; i++) {
|
||||
pagesList.push(i);
|
||||
if (numPages.value <= maxVisible) {
|
||||
for (let i = 0; i < numPages.value; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
// Smart pagination with ellipsis
|
||||
if (currentPage.value <= 2) {
|
||||
for (let i = 0; i < 4; i++) pages.push(i);
|
||||
pages.push(-1); // Ellipsis marker
|
||||
pages.push(numPages.value - 1);
|
||||
} else if (currentPage.value >= numPages.value - 3) {
|
||||
pages.push(0);
|
||||
pages.push(-1);
|
||||
for (let i = numPages.value - 4; i < numPages.value; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
pages.push(0);
|
||||
pages.push(-1);
|
||||
for (let i = currentPage.value - 1; i <= currentPage.value + 1; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
pages.push(-1);
|
||||
pages.push(numPages.value - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return pagesList;
|
||||
return pages;
|
||||
});
|
||||
|
||||
const removeAuthor = (key: number) => {
|
||||
items.value.splice(key, 1);
|
||||
// const removeAuthor = (key: number) => {
|
||||
// items.value.splice(key, 1);
|
||||
// };
|
||||
// Methods
|
||||
const removeAuthor = (index: number) => {
|
||||
const actualIndex = perPage.value * currentPage.value + index;
|
||||
const person = items.value[actualIndex];
|
||||
|
||||
if (confirm(`Are you sure you want to remove ${person.first_name || ''} ${person.last_name || person.email}?`)) {
|
||||
items.value.splice(actualIndex, 1);
|
||||
emit('remove-person', actualIndex, person);
|
||||
|
||||
// Adjust current page if needed
|
||||
if (itemsPaginated.value.length === 0 && currentPage.value > 0) {
|
||||
currentPage.value--;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// const remove = (arr, cb) => {
|
||||
// const newArr = [];
|
||||
const updatePerson = (index: number, field: keyof Person, value: any) => {
|
||||
const actualIndex = perPage.value * currentPage.value + index;
|
||||
const person = items.value[actualIndex];
|
||||
(person as any)[field] = value;
|
||||
emit('person-updated', actualIndex, person);
|
||||
};
|
||||
|
||||
// arr.forEach((item) => {
|
||||
// if (!cb(item)) {
|
||||
// newArr.push(item);
|
||||
// }
|
||||
// });
|
||||
const goToPage = (page: number) => {
|
||||
if (page >= 0 && page < numPages.value) {
|
||||
currentPage.value = page;
|
||||
}
|
||||
};
|
||||
|
||||
// return newArr;
|
||||
// };
|
||||
const getFieldError = (index: number, field: string): string => {
|
||||
const actualIndex = perPage.value * currentPage.value + index;
|
||||
const errorKey = `${props.relation}.${actualIndex}.${field}`;
|
||||
return props.errors[errorKey]?.join(', ') || '';
|
||||
};
|
||||
|
||||
// const checked = (isChecked, client) => {
|
||||
// if (isChecked) {
|
||||
// checkedRows.value.push(client);
|
||||
// } else {
|
||||
// checkedRows.value = remove(checkedRows.value, (row) => row.id === client.id);
|
||||
// }
|
||||
// };
|
||||
const handleDragEnd = (evt: any) => {
|
||||
if (evt.oldIndex !== evt.newIndex) {
|
||||
emit('reorder', evt.oldIndex, evt.newIndex);
|
||||
}
|
||||
};
|
||||
|
||||
// Watchers
|
||||
watch(
|
||||
() => props.persons.length,
|
||||
() => {
|
||||
// Reset to first page if current page is out of bounds
|
||||
if (currentPage.value >= numPages.value && numPages.value > 0) {
|
||||
currentPage.value = numPages.value - 1;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Pagination helper
|
||||
const perPageOptions = [
|
||||
{ value: 5, label: '5 per page' },
|
||||
{ value: 10, label: '10 per page' },
|
||||
{ value: 20, label: '20 per page' },
|
||||
{ value: 50, label: '50 per page' },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- <CardBoxModal v-model="isModalActive" title="Sample modal">
|
||||
<p>Lorem ipsum dolor sit amet <b>adipiscing elit</b></p>
|
||||
<p>This is sample modal</p>
|
||||
</CardBoxModal>
|
||||
<div class="card">
|
||||
<!-- Table Controls -->
|
||||
<div v-if="hasMultiplePages" class="flex justify-between items-center p-3 border-b border-gray-200 dark:border-slate-700">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Showing {{ currentPage * perPage + 1 }} to
|
||||
{{ Math.min((currentPage + 1) * perPage, items.length) }}
|
||||
of {{ items.length }} entries
|
||||
</span>
|
||||
</div>
|
||||
<select
|
||||
v-model="perPage"
|
||||
@change="currentPage = 0"
|
||||
class="px-3 py-1 text-sm border rounded-md dark:bg-slate-800 dark:border-slate-600"
|
||||
>
|
||||
<option v-for="option in perPageOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<CardBoxModal v-model="isModalDangerActive" large-title="Please confirm" button="danger" has-cancel>
|
||||
<p>Lorem ipsum dolor sit amet <b>adipiscing elit</b></p>
|
||||
<p>This is sample modal</p>
|
||||
</CardBoxModal> -->
|
||||
<!-- Table -->
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 dark:border-slate-700">
|
||||
<th v-if="canReorder" class="w-10 p-3" />
|
||||
<th scope="col" class="text-left p-3">#</th>
|
||||
<th scope="col">Id</th>
|
||||
<th>First Name</th>
|
||||
<th>Last Name / Organization</th>
|
||||
<th>Email</th>
|
||||
<th v-if="showContributorTypes" scope="col" class="text-left p-3">Type</th>
|
||||
<th v-if="canDelete" class="w-20 p-3">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<!-- <tbody> -->
|
||||
<!-- <tr v-for="(client, index) in itemsPaginated" :key="client.id"> -->
|
||||
<draggable
|
||||
v-if="canReorder && !hasMultiplePages"
|
||||
tag="tbody"
|
||||
v-model="items"
|
||||
item-key="id"
|
||||
:disabled="!dragEnabled || isLoading"
|
||||
@end="handleDragEnd"
|
||||
handle=".drag-handle"
|
||||
>
|
||||
<template #item="{ index, element }">
|
||||
<tr class="border-b border-gray-100 dark:border-slate-800 hover:bg-gray-50 dark:hover:bg-slate-800">
|
||||
<td class="p-3">
|
||||
<div class="drag-handle cursor-move text-gray-400 hover:text-gray-600">
|
||||
<BaseIcon :path="mdiDragVariant" />
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-3">{{ index + 1 }}</td>
|
||||
<td data-label="Id" class="p-3 text-sm text-gray-600">{{ element.id || '-' }}</td>
|
||||
|
||||
<!-- <div v-if="checkedRows.length" class="p-3 bg-gray-100/50 dark:bg-slate-800">
|
||||
<span v-for="checkedRow in checkedRows" :key="checkedRow.id"
|
||||
class="inline-block px-2 py-1 rounded-sm mr-2 text-sm bg-gray-100 dark:bg-slate-700">
|
||||
{{ checkedRow.name }}
|
||||
</span>
|
||||
</div> -->
|
||||
<!-- First Name - Hidden for Organizational -->
|
||||
<td class="p-3" data-label="First Name" v-if="element.name_type !== 'Organizational'">
|
||||
<FormControl
|
||||
required
|
||||
v-model="element.first_name"
|
||||
type="text"
|
||||
:is-read-only="element.status == true"
|
||||
placeholder="[FIRST NAME]"
|
||||
/>
|
||||
<div class="text-red-400 text-sm" v-if="errors && Array.isArray(errors[`${relation}.${index}.first_name`])">
|
||||
{{ errors[`${relation}.${index}.first_name`].join(', ') }}
|
||||
</div>
|
||||
</td>
|
||||
<td v-else></td>
|
||||
<!-- Empty cell for organizational entries -->
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- <th v-if="checkable" /> -->
|
||||
<th />
|
||||
<th scope="col">Sort</th>
|
||||
<th scope="col">Id</th>
|
||||
<!-- <th class="hidden lg:table-cell"></th> -->
|
||||
<th>First Name</th>
|
||||
<th>Last Name</th>
|
||||
<th>Email</th>
|
||||
<th scope="col" v-if="Object.keys(contributortypes).length">
|
||||
<span>Type</span>
|
||||
</th>
|
||||
<!-- Last Name / Organization Name -->
|
||||
<td :data-label="element.name_type === 'Organizational' ? 'Organization Name' : 'Last Name'">
|
||||
<FormControl
|
||||
required
|
||||
v-model="element.last_name"
|
||||
type="text"
|
||||
:is-read-only="element.status == true"
|
||||
:placeholder="element.name_type === 'Organizational' ? '[ORGANIZATION NAME]' : '[LAST NAME]'"
|
||||
/>
|
||||
<div class="text-red-400 text-sm" v-if="errors && Array.isArray(errors[`${relation}.${index}.last_name`])">
|
||||
{{ errors[`${relation}.${index}.last_name`].join(', ') }}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- <th>Name Type</th> -->
|
||||
<!-- <th>Progress</th> -->
|
||||
<!-- <th>Created</th> -->
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<!-- <tbody> -->
|
||||
<!-- <tr v-for="(client, index) in itemsPaginated" :key="client.id"> -->
|
||||
<draggable id="galliwasery" tag="tbody" v-model="items" item-key="id">
|
||||
<template #item="{ index, element }">
|
||||
<tr>
|
||||
<td class="drag-icon">
|
||||
<BaseIcon :path="mdiDragVariant" />
|
||||
</td>
|
||||
<td scope="row">{{ index + 1 }}</td>
|
||||
<td data-label="Id">{{ element.id }}</td>
|
||||
<!-- <TableCheckboxCell v-if="checkable" @checked="checked($event, client)" /> -->
|
||||
<!-- <td v-if="element.name" class="border-b-0 lg:w-6 before:hidden hidden lg:table-cell">
|
||||
<UserAvatar :username="element.name" class="w-24 h-24 mx-auto lg:w-6 lg:h-6" />
|
||||
</td> -->
|
||||
<td data-label="First Name">
|
||||
<!-- {{ element.first_name }} -->
|
||||
<FormControl
|
||||
required
|
||||
v-model="element.first_name"
|
||||
type="text" :is-read-only="element.status==true"
|
||||
placeholder="[FIRST NAME]"
|
||||
>
|
||||
<div
|
||||
class="text-red-400 text-sm"
|
||||
v-if="errors && Array.isArray(errors[`${relation}.${index}.first_name`])"
|
||||
>
|
||||
{{ errors[`${relation}.${index}.first_name`].join(', ') }}
|
||||
<!-- Email -->
|
||||
<td data-label="Email">
|
||||
<FormControl
|
||||
required
|
||||
v-model="element.email"
|
||||
type="text"
|
||||
:is-read-only="element.status == true"
|
||||
placeholder="[EMAIL]"
|
||||
/>
|
||||
<div class="text-red-400 text-sm" v-if="errors && Array.isArray(errors[`${relation}.${index}.email`])">
|
||||
{{ errors[`${relation}.${index}.email`].join(', ') }}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Contributor Type -->
|
||||
<td v-if="Object.keys(contributortypes).length">
|
||||
<FormControl
|
||||
required
|
||||
v-model="element.pivot_contributor_type"
|
||||
type="select"
|
||||
:options="contributortypes"
|
||||
placeholder="[relation type]"
|
||||
>
|
||||
<div
|
||||
class="text-red-400 text-sm"
|
||||
v-if="errors && Array.isArray(errors[`${relation}.${index}.pivot_contributor_type`])"
|
||||
>
|
||||
{{ errors[`${relation}.${index}.pivot_contributor_type`].join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</td>
|
||||
|
||||
<!-- Actions -->
|
||||
<td class="before:hidden lg:w-1 whitespace-nowrap">
|
||||
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
||||
<BaseButton color="danger" :icon="mdiTrashCan" small @click.prevent="removeAuthor(index)" />
|
||||
</BaseButtons>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</draggable>
|
||||
<!-- </tbody> -->
|
||||
<!-- Non-draggable tbody for paginated view -->
|
||||
<tbody v-else>
|
||||
<tr
|
||||
v-for="(element, index) in itemsPaginated"
|
||||
:key="element.id || index"
|
||||
class="border-b border-gray-100 dark:border-slate-800 hover:bg-gray-50 dark:hover:bg-slate-800"
|
||||
>
|
||||
<td v-if="canReorder" class="p-3 text-gray-400">
|
||||
<BaseIcon :path="mdiDragVariant" />
|
||||
</td>
|
||||
<td class="p-3">{{ currentPage * perPage + index + 1 }}</td>
|
||||
<td class="p-3 text-sm text-gray-600">{{ element.id || '-' }}</td>
|
||||
|
||||
<!-- Same field structure as draggable version -->
|
||||
<td class="p-3">
|
||||
<FormControl
|
||||
v-if="element.name_type !== 'Organizational'"
|
||||
required
|
||||
:model-value="element.first_name"
|
||||
@update:model-value="updatePerson(index, 'first_name', $event)"
|
||||
type="text"
|
||||
:is-read-only="element.status || !canEdit"
|
||||
placeholder="[FIRST NAME]"
|
||||
:error="getFieldError(index, 'first_name')"
|
||||
/>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
<div v-if="getFieldError(index, 'first_name')" class="text-red-400 text-sm">
|
||||
{{ getFieldError(index, 'first_name') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</td>
|
||||
<td data-label="Last Name">
|
||||
<FormControl
|
||||
required
|
||||
v-model="element.last_name"
|
||||
type="text" :is-read-only="element.status==true"
|
||||
placeholder="[LAST NAME]"
|
||||
>
|
||||
<div
|
||||
class="text-red-400 text-sm"
|
||||
v-if="errors && Array.isArray(errors[`${relation}.${index}.last_name`])"
|
||||
>
|
||||
{{ errors[`${relation}.${index}.last_name`].join(', ') }}
|
||||
</td>
|
||||
|
||||
<td class="p-3">
|
||||
<FormControl
|
||||
required
|
||||
:model-value="element.last_name"
|
||||
@update:model-value="updatePerson(index, 'last_name', $event)"
|
||||
type="text"
|
||||
:is-read-only="element.status || !canEdit"
|
||||
:placeholder="element.name_type === 'Organizational' ? '[ORGANIZATION NAME]' : '[LAST NAME]'"
|
||||
:error="getFieldError(index, 'last_name')"
|
||||
/>
|
||||
<div v-if="getFieldError(index, 'last_name')" class="text-red-400 text-sm">
|
||||
{{ getFieldError(index, 'last_name') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</td>
|
||||
<td data-label="Email">
|
||||
<FormControl
|
||||
required
|
||||
v-model="element.email"
|
||||
type="text" :is-read-only="element.status==true"
|
||||
placeholder="[EMAIL]"
|
||||
>
|
||||
<div
|
||||
class="text-red-400 text-sm"
|
||||
v-if="errors && Array.isArray(errors[`${relation}.${index}.email`])"
|
||||
>
|
||||
{{ errors[`${relation}.${index}.email`].join(', ') }}
|
||||
</td>
|
||||
|
||||
<td class="p-3">
|
||||
<FormControl
|
||||
required
|
||||
:model-value="element.email"
|
||||
@update:model-value="updatePerson(index, 'email', $event)"
|
||||
type="email"
|
||||
:is-read-only="element.status || !canEdit"
|
||||
placeholder="[EMAIL]"
|
||||
:error="getFieldError(index, 'email')"
|
||||
/>
|
||||
<div v-if="getFieldError(index, 'email')" class="text-red-400 text-sm">
|
||||
{{ getFieldError(index, 'email') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</td>
|
||||
<td v-if="Object.keys(contributortypes).length">
|
||||
<!-- <select type="text" v-model="element.pivot.contributor_type">
|
||||
<option v-for="(option, i) in contributortypes" :value="option" :key="i">
|
||||
{{ option }}
|
||||
</option>
|
||||
</select> -->
|
||||
<FormControl
|
||||
required
|
||||
v-model="element.pivot_contributor_type"
|
||||
type="select"
|
||||
:options="contributortypes"
|
||||
placeholder="[relation type]"
|
||||
>
|
||||
<div
|
||||
class="text-red-400 text-sm"
|
||||
v-if="errors && Array.isArray(errors[`${relation}.${index}.pivot_contributor_type`])"
|
||||
>
|
||||
{{ errors[`${relation}.${index}.pivot_contributor_type`].join(', ') }}
|
||||
</td>
|
||||
|
||||
<td v-if="showContributorTypes" class="p-3">
|
||||
<FormControl
|
||||
required
|
||||
:model-value="element.pivot_contributor_type"
|
||||
@update:model-value="updatePerson(index, 'pivot_contributor_type', $event)"
|
||||
type="select"
|
||||
:options="contributortypes"
|
||||
:is-read-only="element.status || !canEdit"
|
||||
placeholder="[Select type]"
|
||||
:error="getFieldError(index, 'pivot_contributor_type')"
|
||||
/>
|
||||
<div v-if="getFieldError(index, 'pivot_contributor_type')" class="text-red-400 text-sm">
|
||||
{{ getFieldError(index, 'pivot_contributor_type') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</td>
|
||||
<!-- <td data-label="Name Type">
|
||||
{{ client.name_type }}
|
||||
</td> -->
|
||||
<!-- <td data-label="Orcid">
|
||||
{{ client.identifier_orcid }}
|
||||
</td> -->
|
||||
<!-- <td data-label="Progress" class="lg:w-32">
|
||||
<progress class="flex w-2/5 self-center lg:w-full" max="100" v-bind:value="client.progress">
|
||||
{{ client.progress }}
|
||||
</progress>
|
||||
</td> -->
|
||||
<td class="before:hidden lg:w-1 whitespace-nowrap">
|
||||
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
||||
<!-- <BaseButton color="info" :icon="mdiEye" small @click="isModalActive = true" /> -->
|
||||
<BaseButton color="danger" :icon="mdiTrashCan" small @click.prevent="removeAuthor(index)" />
|
||||
</BaseButtons>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</draggable>
|
||||
<!-- </tbody> -->
|
||||
</table>
|
||||
<!-- :class="[ pagesList.length > 1 ? 'block' : 'hidden']" -->
|
||||
<div class="p-3 lg:px-6 border-t border-gray-100 dark:border-slate-800">
|
||||
<!-- <BaseLevel>
|
||||
<BaseButtons>
|
||||
</td>
|
||||
|
||||
<td v-if="canDelete" class="p-3">
|
||||
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
||||
<BaseButton
|
||||
color="danger"
|
||||
:icon="mdiTrashCan"
|
||||
small
|
||||
@click="removeAuthor(index)"
|
||||
:disabled="element.status || !canEdit"
|
||||
title="Remove person"
|
||||
/>
|
||||
</BaseButtons>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Empty State -->
|
||||
<tr v-if="items.length === 0">
|
||||
<td :colspan="canReorder ? 8 : 7" class="text-center p-8 text-gray-500">No persons added yet</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div v-if="hasMultiplePages" class="flex justify-between items-center p-3 border-t border-gray-200 dark:border-slate-700">
|
||||
<div class="flex gap-1">
|
||||
<BaseButton :disabled="currentPage === 0" @click="goToPage(currentPage - 1)" :icon="mdiChevronLeft" small outline />
|
||||
|
||||
<template v-for="(page, i) in pagesList" :key="i">
|
||||
<span v-if="page === -1" class="px-3 py-1">...</span>
|
||||
<BaseButton
|
||||
v-else
|
||||
@click="goToPage(page)"
|
||||
:label="String(page + 1)"
|
||||
:color="page === currentPage ? 'info' : 'whiteDark'"
|
||||
small
|
||||
:outline="page !== currentPage"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<BaseButton
|
||||
v-for="page in pagesList"
|
||||
:key="page"
|
||||
:active="page === currentPage"
|
||||
:label="page + 1"
|
||||
:disabled="currentPage >= numPages - 1"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
:icon="mdiChevronRight"
|
||||
small
|
||||
:outline="styleService.darkMode"
|
||||
@click="currentPage = page"
|
||||
outline
|
||||
/>
|
||||
</BaseButtons>
|
||||
<small>Page {{ currentPageHuman }} of {{ numPages }}</small>
|
||||
</BaseLevel> -->
|
||||
</div>
|
||||
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400"> Page {{ currentPageHuman }} of {{ numPages }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.drag-handle {
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply bg-white dark:bg-slate-900 rounded-lg shadow-sm;
|
||||
}
|
||||
|
||||
/* Improve table responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
table {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 0.5rem !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue