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:
Kaimbacher 2025-04-08 14:16:35 +02:00
parent 10d159a57a
commit f04c1f6327
30 changed files with 2284 additions and 539 deletions

View file

@ -295,7 +295,7 @@ const fetchCollections = async (collectionId: number) => {
return alreadyDropped ? { ...collection, inUse: true } : { ...collection, inUse: false };
});
// Check if selected collection is in the selected list
if (selectedCollection.value && selectedCollectionList.value.find(dc => dc.id === selectedCollection.value.id)) {
if (selectedCollection.value && selectedCollectionList.value.find(dc => dc.id === selectedCollection.value?.id)) {
selectedCollection.value = { ...selectedCollection.value, inUse: true };
} else if (selectedCollection.value) {
selectedCollection.value = { ...selectedCollection.value, inUse: false };

View file

@ -544,15 +544,15 @@ Removes a selected keyword
<div class="flex items-center">
<!-- <label>{{ form.titles[0].language }}</label>
<label>{{ form.language }}</label> -->
<icon-wizard :is-current="formStep == 1" :is-checked="formStep > 1" :label="'Language'">
<icon-wizard :is-current="formStep == 1" :is-checked="formStep > 1" :label="'Step 1'">
<icon-language></icon-language>
</icon-wizard>
<icon-wizard :is-current="formStep == 2" :is-checked="formStep > 2" :label="'Mandatory'">
<icon-wizard :is-current="formStep == 2" :is-checked="formStep > 2" :label="'Step 2'">
<icon-mandatory></icon-mandatory>
</icon-wizard>
<icon-wizard :is-current="formStep == 3" :is-checked="formStep > 3" :label="'Recommended'">
<icon-wizard :is-current="formStep == 3" :is-checked="formStep > 3" :label="'Step 3'">
<icon-recommendet></icon-recommendet>
</icon-wizard>
@ -588,7 +588,7 @@ Removes a selected keyword
<input class="form-checkbox" name="rights" id="rights" type="checkbox" v-model="dataset.rights" />
terms and conditions
</label> -->
<FormField label="Rights" help="You must agree to continue" wrap-body
<FormField label="Rights" help="You must agree that you have read the Terms and Conditions. Please click on the 'i' icon to find a read the policy" wrap-body
:class="{ 'text-red-400': form.errors.rights }" class="mt-8 w-full mx-2 flex-1 flex-col">
<label for="rights" class="checkbox mr-6 mb-3 last:mr-0">
<input type="checkbox" id="rights" required v-model="form.rights" />

View file

@ -42,7 +42,8 @@
<!-- (2) licenses -->
<FormField label="Licenses" wrap-body :class="{ 'text-red-400': form.errors.licenses }"
class="mt-8 w-full mx-2 flex-1">
<FormCheckRadioGroup type="radio" v-model="form.licenses" name="licenses" is-column :options="licenses" />
<FormCheckRadioGroup type="radio" v-model="form.licenses" name="licenses" is-column
:options="licenses" />
</FormField>
<div class="flex flex-col md:flex-row">
@ -163,7 +164,8 @@
:class="{ 'text-red-400': form.errors['descriptions.0.value'] }"
class="w-full mr-1 flex-1">
<FormControl required v-model="form.descriptions[0].value" type="textarea"
placeholder="[enter main abstract]" :show-char-count="true" :max-input-length="2500">
placeholder="[enter main abstract]" :show-char-count="true"
:max-input-length="2500">
<div class="text-red-400 text-sm"
v-if="form.errors['descriptions.0.value'] && Array.isArray(form.errors['descriptions.0.value'])">
{{ form.errors['descriptions.0.value'].join(', ') }}
@ -176,7 +178,7 @@
<FormControl required v-model="form.descriptions[0].language" type="text"
:is-read-only="true">
<div class="text-red-400 text-sm" v-if="form.errors['descriptions.0.value'] && Array.isArray(form.errors['descriptions.0.language'])
">
">
{{ form.errors['descriptions.0.language'].join(', ') }}
</div>
</FormControl>
@ -243,8 +245,9 @@
<SearchAutocomplete source="/api/persons" :response-property="'first_name'"
placeholder="search in person table...." v-on:person="onAddAuthor"></SearchAutocomplete>
<TablePersons :persons="form.authors" v-if="form.authors.length > 0" :relation="'authors'"/>
<div class="text-red-400 text-sm" v-if="form.errors.authors && Array.isArray(form.errors.authors)">
<TablePersons :persons="form.authors" v-if="form.authors.length > 0" :relation="'authors'" />
<div class="text-red-400 text-sm"
v-if="form.errors.authors && Array.isArray(form.errors.authors)">
{{ form.errors.authors.join(', ') }}
</div>
</CardBox>
@ -334,8 +337,8 @@
</FormField>
</div>
<CardBox class="mb-6 shadow" has-table title="Dataset References" :icon="mdiEarthPlus" :header-icon="mdiPlusCircle"
v-on:header-icon-click="addReference">
<CardBox class="mb-6 shadow" has-table title="Dataset References" :icon="mdiEarthPlus"
:header-icon="mdiPlusCircle" v-on:header-icon-click="addReference">
<!-- Message when no references exist -->
<div v-if="form.references.length === 0" class="text-center py-4">
<p class="text-gray-600">No references added yet.</p>
@ -408,6 +411,42 @@
</tr>
</tbody>
</table>
<!-- References to delete section -->
<div v-if="form.referencesToDelete && form.referencesToDelete.length > 0" class="mt-8">
<h1 class="pt-8 pb-3 font-semibold sm:text-lg text-gray-900">References To Delete</h1>
<ul class="flex flex-1 flex-wrap -m-1">
<li v-for="(element, index) in form.referencesToDelete" :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-40">
<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 truncate overflow-hidden whitespace-nowrap">
{{ element.value }}
</h1>
<div class="flex flex-col mt-auto">
<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">
<span class="font-semibold">Relation:</span> {{ element.relation }}
</p>
<div class="flex justify-end mt-1">
<button
class="restore ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md text-gray-800"
@click.prevent="restoreReference(index)">
<svg viewBox="0 0 24 24" class="w-5 h-5">
<path fill="currentColor" :d="mdiRestore"></path>
</svg>
</button>
</div>
</div>
</section>
</article>
</li>
</ul>
</div>
</CardBox>
<BaseDivider />
@ -420,7 +459,7 @@
</li>
</ul> -->
<TableKeywords :keywords="form.subjects" :errors="form.errors" :subjectTypes="subjectTypes"
v-if="form.subjects.length > 0" />
v-model:subjects-to-delete="form.subjectsToDelete" v-if="form.subjects.length > 0" />
</CardBox>
</div>
@ -447,7 +486,9 @@
</select> -->
</div>
<FileUploadComponent v-model:files="form.files" v-model:filesToDelete="form.filesToDelete" :showClearButton="false"></FileUploadComponent>
<FileUploadComponent v-model:files="form.files" v-model:filesToDelete="form.filesToDelete"
:showClearButton="false">
</FileUploadComponent>
<div class="text-red-400 text-sm" v-if="form.errors['file'] && Array.isArray(form.errors['files'])">
{{ form.errors['files'].join(', ') }}
@ -475,8 +516,8 @@
</BaseButtons>
</template>
</CardBox>
<!-- Loading Spinner -->
<div v-if="form.processing"
<!-- Loading Spinner -->
<div v-if="form.processing"
class="fixed inset-0 flex items-center justify-center bg-gray-500 bg-opacity-50 z-50">
<svg class="animate-spin h-12 w-12 text-white" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
@ -527,6 +568,7 @@ import {
mdiBookOpenPageVariant,
mdiEarthPlus,
mdiAlertBoxOutline,
mdiRestore
} from '@mdi/js';
import { notify } from '@/notiwind';
import NotificationBar from '@/Components/NotificationBar.vue';
@ -633,6 +675,8 @@ const mapId = 'test';
// }
props.dataset.filesToDelete = [];
props.dataset.subjectsToDelete = [];
props.dataset.referencesToDelete = [];
let form = useForm<Dataset>(props.dataset as Dataset);
// const mainService = MainService();
@ -701,13 +745,13 @@ const submit = async (): Promise<void> => {
// const metadata = JSON.stringify({ sort_order: obj.sort_order });
// const metadataBlob = new Blob([metadata + '\n'], { type: 'application/json' });
const file = new File([obj.blob], `${obj.label}?sortorder=${obj.sort_order}`, options,);
// const file = new File([obj.blob], `${obj.label}`, options);
// fileUploads[obj.sort_order] = file;
fileUploads.push(file);
} else {
} else {
// return normal request input
fileInputs.push(obj);
}
@ -744,7 +788,9 @@ const submit = async (): Promise<void> => {
// formStep.value++;
// form.filesToDelete = [];
// Clear the array using splice
form.filesToDelete?.splice(0, form.filesToDelete.length);
form.filesToDelete?.splice(0, form.filesToDelete.length);
form.subjectsToDelete?.splice(0, form.subjectsToDelete.length);
form.referencesToDelete?.splice(0, form.referencesToDelete.length);
},
});
};
@ -794,7 +840,7 @@ const onAddContributor = (person: Person) => {
};
const addKeyword = () => {
let newSubject: Subject = { value: 'test', language: '', type: 'uncontrolled', dataset_count: 0 };
let newSubject: Subject = { value: '', language: '', type: 'uncontrolled' };
//this.dataset.files.push(uploadedFiles[i]);
form.subjects.push(newSubject);
};
@ -806,9 +852,35 @@ const addReference = () => {
};
const removeReference = (key: any) => {
const reference = form.references[key];
// If the reference has an ID, it exists in the database
// and should be added to referencesToDelete
if (reference.id) {
// Initialize referencesToDelete array if it doesn't exist
if (!form.referencesToDelete) {
form.referencesToDelete = [];
}
// Add to referencesToDelete
form.referencesToDelete.push(reference);
}
// Remove from form.references array
form.references.splice(key, 1);
};
const restoreReference = (index: number) => {
// Get the reference from referencesToDelete
const reference = form.referencesToDelete[index];
// Add it back to form.references
form.references.push(reference);
// Remove it from referencesToDelete
form.referencesToDelete.splice(index, 1);
};
const onMapInitialized = (newItem: any) => {
console.log(newItem);
};