hotfix: enhance editor dataset management and UI improvements
- Implemented dataset editing functionality for editor roles, including fetching, updating, and categorizing datasets. - Added routes and controller actions for editing, updating, and categorizing datasets within the editor interface. - Integrated UI components for managing dataset metadata, subjects, references, and files. - Enhanced keyword management with features for adding, editing, and deleting keywords, including handling keywords used by multiple datasets. - Improved reference management with features for adding, editing, and deleting dataset references. - Added validation for dataset updates using the `updateEditorDatasetValidator`. - Updated the dataset edit form to include components for managing titles, descriptions, authors, contributors, licenses, coverage, subjects, references, and files. - Implemented transaction management for dataset updates to ensure data consistency. - Added a download route for files associated with datasets. - Improved the UI for displaying and interacting with datasets in the editor index view, including adding edit and categorize buttons. - Fixed an issue where the file size was not correctly calculated. - Added a tooltip to the keyword value column in the TableKeywords component to explain the editability of keywords. - Added a section to display keywords that are marked for deletion. - Added a section to display references that are marked for deletion. - Added a restore button to the references to delete section to restore references. - Updated the SearchCategoryAutocomplete component to support read-only mode. - Updated the FormControl component to support read-only mode. - Added icons and styling improvements to various components. - Added a default value for subjectsToDelete and referencesToDelete in the dataset model. - Updated the FooterBar component to use the JustboilLogo component. - Updated the app.ts file to fetch chart data without a year parameter. - Updated the Login.vue file to invert the logo in dark mode. - Updated the AccountInfo.vue file to add a Head component.
This commit is contained in:
parent
10d159a57a
commit
f04c1f6327
30 changed files with 2284 additions and 539 deletions
|
@ -12,6 +12,7 @@ import { Subject } from '@/Dataset';
|
|||
// import FormField from '@/Components/FormField.vue';
|
||||
import FormControl from '@/Components/FormControl.vue';
|
||||
import SearchCategoryAutocomplete from '@/Components/SearchCategoryAutocomplete.vue';
|
||||
import { mdiRefresh } from '@mdi/js';
|
||||
|
||||
const props = defineProps({
|
||||
checkable: Boolean,
|
||||
|
@ -27,6 +28,22 @@ const props = defineProps({
|
|||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
subjectsToDelete: {
|
||||
type: Array<Subject>,
|
||||
default: [],
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:subjectsToDelete']);
|
||||
|
||||
// Create a computed property for subjectsToDelete with getter and setter
|
||||
const deletetSubjects = computed({
|
||||
get: () => props.subjectsToDelete,
|
||||
set: (values: Array<Subject>) => {
|
||||
props.subjectsToDelete.length = 0;
|
||||
props.subjectsToDelete.push(...values);
|
||||
emit('update:subjectsToDelete', values);
|
||||
}
|
||||
});
|
||||
|
||||
const styleService = StyleService();
|
||||
|
@ -58,21 +75,45 @@ const pagesList = computed(() => {
|
|||
});
|
||||
|
||||
const removeItem = (key: number) => {
|
||||
// items.value.splice(key, 1);
|
||||
const item = items.value[key];
|
||||
|
||||
// If the item has an ID, add it to the delete list
|
||||
if (item.id) {
|
||||
addToDeleteList(item);
|
||||
}
|
||||
|
||||
// Remove from the visible list
|
||||
items.value.splice(key, 1);
|
||||
};
|
||||
|
||||
// Helper function to add a subject to the delete list
|
||||
const addToDeleteList = (subject: Subject) => {
|
||||
if (subject.id) {
|
||||
const newList = [...props.subjectsToDelete, subject];
|
||||
deletetSubjects.value = newList;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Helper function to reactivate a subject (remove from delete list)
|
||||
const reactivateSubject = (index: number) => {
|
||||
const newList = [...props.subjectsToDelete];
|
||||
const removedSubject = newList.splice(index, 1)[0];
|
||||
deletetSubjects.value = newList;
|
||||
|
||||
// Add the subject back to the keywords list if it's not already there
|
||||
if (removedSubject && !props.keywords.some(k => k.id === removedSubject.id)) {
|
||||
props.keywords.push(removedSubject);
|
||||
}
|
||||
};
|
||||
|
||||
const isKeywordReadOnly = (item: Subject) => {
|
||||
return (item.dataset_count ?? 0) > 1 || item.type !== 'uncontrolled';
|
||||
};
|
||||
</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>
|
||||
|
||||
<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> -->
|
||||
|
||||
<!-- <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"
|
||||
|
@ -87,17 +128,34 @@ const removeItem = (key: number) => {
|
|||
<!-- <th v-if="checkable" /> -->
|
||||
<!-- <th class="hidden lg:table-cell"></th> -->
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Value</th>
|
||||
<th scope="col" class="relative">
|
||||
Value
|
||||
<div class="inline-block relative ml-1 group">
|
||||
<button
|
||||
class="w-4 h-4 rounded-full bg-gray-200 text-gray-600 text-xs flex items-center justify-center focus:outline-none hover:bg-gray-300">
|
||||
i
|
||||
</button>
|
||||
<div
|
||||
class="absolute left-0 top-full mt-1 w-64 bg-white shadow-lg rounded-md p-3 text-xs text-left z-50 transform scale-0 origin-top-left transition-transform duration-100 group-hover:scale-100">
|
||||
<p class="text-gray-700">
|
||||
Keywords are only editable if they are used by a single dataset (Usage Count = 1)".
|
||||
</p>
|
||||
<div class="absolute -top-1 left-1 w-2 h-2 bg-white transform rotate-45"></div>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col">Language</th>
|
||||
|
||||
<th scope="col">Usage Count</th>
|
||||
<th scope="col" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in itemsPaginated" :key="index">
|
||||
|
||||
|
||||
<td data-label="Type" scope="row">
|
||||
<FormControl required v-model="item.type" @update:modelValue="() => {item.external_key = undefined; item.value= '';}" :type="'select'" placeholder="[Enter Language]" :options="props.subjectTypes">
|
||||
<FormControl required v-model="item.type"
|
||||
@update:modelValue="() => { item.external_key = undefined; item.value = ''; }" :type="'select'"
|
||||
placeholder="[Enter Language]" :options="props.subjectTypes">
|
||||
<div class="text-red-400 text-sm" v-if="errors[`subjects.${index}.type`]">
|
||||
{{ errors[`subjects.${index}.type`].join(', ') }}
|
||||
</div>
|
||||
|
@ -105,22 +163,19 @@ const removeItem = (key: number) => {
|
|||
</td>
|
||||
|
||||
<td data-label="Value" scope="row">
|
||||
<SearchCategoryAutocomplete
|
||||
v-if="item.type !== 'uncontrolled'"
|
||||
v-model="item.value"
|
||||
@subject="
|
||||
(result) => {
|
||||
item.language = result.language;
|
||||
item.external_key = result.uri;
|
||||
}
|
||||
"
|
||||
>
|
||||
<SearchCategoryAutocomplete v-if="item.type !== 'uncontrolled'" v-model="item.value" @subject="
|
||||
(result) => {
|
||||
item.language = result.language;
|
||||
item.external_key = result.uri;
|
||||
}
|
||||
" :is-read-only="item.dataset_count > 1">
|
||||
<div class="text-red-400 text-sm" v-if="errors[`subjects.${index}.value`]">
|
||||
{{ errors[`subjects.${index}.value`].join(', ') }}
|
||||
</div>
|
||||
</SearchCategoryAutocomplete>
|
||||
|
||||
<FormControl v-else required v-model="item.value" type="text" placeholder="[enter keyword value]" :borderless="true">
|
||||
<FormControl v-else required v-model="item.value" type="text" placeholder="[enter keyword value]"
|
||||
:borderless="true" :is-read-only="item.dataset_count > 1">
|
||||
<div class="text-red-400 text-sm" v-if="errors[`subjects.${index}.value`]">
|
||||
{{ errors[`subjects.${index}.value`].join(', ') }}
|
||||
</div>
|
||||
|
@ -128,23 +183,24 @@ const removeItem = (key: number) => {
|
|||
</td>
|
||||
|
||||
<td data-label="Language" scope="row">
|
||||
<FormControl
|
||||
required
|
||||
v-model="item.language"
|
||||
:type="'select'"
|
||||
placeholder="[Enter Lang]"
|
||||
:options="{ de: 'de', en: 'en' }"
|
||||
:is-read-only="item.type != 'uncontrolled'"
|
||||
>
|
||||
<FormControl required v-model="item.language" :type="'select'" placeholder="[Enter Lang]"
|
||||
:options="{ de: 'de', en: 'en' }" :is-read-only="isKeywordReadOnly(item)">
|
||||
<div class="text-red-400 text-sm" v-if="errors[`subjects.${index}.language`]">
|
||||
{{ errors[`subjects.${index}.language`].join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</td>
|
||||
|
||||
<td data-label="Usage Count" scope="row">
|
||||
<div class="text-center">
|
||||
{{ item.dataset_count || 1 }}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="before:hidden lg:w-1 whitespace-nowrap" scope="row">
|
||||
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
||||
<!-- <BaseButton color="info" :icon="mdiEye" small @click="isModalActive = true" /> -->
|
||||
<BaseButton v-if="index > 2" color="danger" :icon="mdiTrashCan" small @click.prevent="removeItem(index)" />
|
||||
<BaseButton color="danger" :icon="mdiTrashCan" small @click.prevent="removeItem(index)" />
|
||||
</BaseButtons>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -155,15 +211,8 @@ const removeItem = (key: number) => {
|
|||
<div class="p-3 lg:px-6 border-t border-gray-100 dark:border-slate-800">
|
||||
<BaseLevel>
|
||||
<BaseButtons>
|
||||
<BaseButton
|
||||
v-for="page in pagesList"
|
||||
:key="page"
|
||||
:active="page === currentPage"
|
||||
:label="page + 1"
|
||||
small
|
||||
:outline="styleService.darkMode"
|
||||
@click="currentPage = page"
|
||||
/>
|
||||
<BaseButton v-for="page in pagesList" :key="page" :active="page === currentPage" :label="page + 1" small
|
||||
:outline="styleService.darkMode" @click="currentPage = page" />
|
||||
</BaseButtons>
|
||||
<small>Page {{ currentPageHuman }} of {{ numPages }}</small>
|
||||
</BaseLevel>
|
||||
|
@ -172,6 +221,47 @@ const removeItem = (key: number) => {
|
|||
<div class="text-red-400 text-sm" v-if="errors.subjects && Array.isArray(errors.subjects)">
|
||||
{{ errors.subjects.join(', ') }}
|
||||
</div>
|
||||
|
||||
<!-- Subjects to delete section -->
|
||||
<div v-if="deletetSubjects.length > 0" class="mt-8">
|
||||
<h1 class="pt-8 pb-3 font-semibold sm:text-lg text-gray-900">Keywords To Delete</h1>
|
||||
<ul id="deleteSubjects" tag="ul" class="flex flex-1 flex-wrap -m-1">
|
||||
<li v-for="(element, index) in deletetSubjects" :key="index"
|
||||
class="block p-1 w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/6 xl:w-1/8 h-32">
|
||||
<article tabindex="0"
|
||||
class="bg-red-100 group w-full h-full rounded-md cursor-pointer relative shadow-sm overflow-hidden">
|
||||
<section
|
||||
class="flex flex-col rounded-md text-xs break-words w-full h-full z-20 absolute top-0 py-2 px-3">
|
||||
<h1 class="flex-1 text-gray-700 group-hover:text-blue-800 font-medium text-sm mb-1">{{
|
||||
element.value }}</h1>
|
||||
<div class="flex items-center justify-between mt-auto">
|
||||
<div class="flex flex-col">
|
||||
<p class="p-1 size text-xs text-gray-700">
|
||||
<span class="font-semibold">Type:</span> {{ element.type }}
|
||||
</p>
|
||||
<p class="p-1 size text-xs text-gray-700" v-if="element.dataset_count">
|
||||
<span class="font-semibold">Used by:</span>
|
||||
<span
|
||||
class="inline-flex items-center justify-center bg-gray-200 text-gray-800 rounded-full w-5 h-5 text-xs">
|
||||
{{ element.dataset_count }}
|
||||
</span> datasets
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md text-gray-800"
|
||||
@click.prevent="reactivateSubject(index)">
|
||||
<svg viewBox="0 0 24 24" class="w-5 h-5">
|
||||
<path fill="currentColor" :d="mdiRefresh"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue