Squashed commit of the following:
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 40s
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 40s
commit 579f0878e5240dc17db69be1e0b0c0f5af7ef9fe
Author: Arno Kaimbacher <arno.kaimbacher@geosphere.at>
Date: Tue Jun 9 09:25:44 2026 +0200
feat: Refactor error handling in Dataset Edit form and improve validation messages
- Updated error handling in the Dataset Edit form to use a centralized formatError function for displaying validation messages.
- Enhanced user feedback by ensuring that error messages are displayed consistently across various fields.
- Modified the validation rule for arrayContainsTypes to provide clearer error messages for missing main and translated titles/abstracts.
- Introduced a new ValidationService to manage manual construction of validation errors.
- Updated Vite configuration to streamline asset loading and improve performance.
- Adjusted Inertia setup to utilize dynamic imports for page-specific assets.
- Cleaned up unnecessary comments and code in various files for better readability.
commit 5efddc2a58c0e164fef585cc7344c06155dbc2c1
Author: Arno Kaimbacher <arno.kaimbacher@geosphere.at>
Date: Mon Jan 12 17:02:47 2026 +0100
feat: add dataset change detection and form submission composables
- Implemented `useDatasetChangeDetection` for tracking unsaved changes in dataset forms, including comparisons for licenses, basic properties, files, coverage, and more.
- Added `useDatasetFormSubmission` for handling dataset form submissions with validation, success/error handling, and auto-save functionality.
This commit is contained in:
parent
0680879e2f
commit
9368a0dd8d
38 changed files with 5588 additions and 6181 deletions
|
|
@ -1,15 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
// import { MainService } from '@/Stores/main';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import { mdiTrashCan } from '@mdi/js';
|
||||
// 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 { 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';
|
||||
|
|
@ -47,14 +43,10 @@ const deletetSubjects = computed({
|
|||
});
|
||||
|
||||
const styleService = StyleService();
|
||||
// const mainService = MainService();
|
||||
const items = computed(() => props.keywords);
|
||||
|
||||
// const isModalActive = ref(false);
|
||||
// const isModalDangerActive = ref(false);
|
||||
const perPage = ref(5);
|
||||
const currentPage = ref(0);
|
||||
// const checkedRows = ref([]);
|
||||
|
||||
const itemsPaginated = computed(() => {
|
||||
return items.value.slice(perPage.value * currentPage.value, perPage.value * (currentPage.value + 1));
|
||||
|
|
@ -75,7 +67,6 @@ 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
|
||||
|
|
@ -95,7 +86,6 @@ const addToDeleteList = (subject: Subject) => {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
// Helper function to reactivate a subject (remove from delete list)
|
||||
const reactivateSubject = (index: number) => {
|
||||
const newList = [...props.subjectsToDelete];
|
||||
|
|
@ -111,22 +101,18 @@ const reactivateSubject = (index: number) => {
|
|||
const isKeywordReadOnly = (item: Subject) => {
|
||||
return (item.dataset_count ?? 0) > 1 || item.type !== 'uncontrolled';
|
||||
};
|
||||
|
||||
const formatError = (error: string | string[] | undefined | null): string => {
|
||||
if (!error) return '';
|
||||
return Array.isArray(error) ? error.join(', ') : error;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<!-- <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> -->
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- <th v-if="checkable" /> -->
|
||||
<!-- <th class="hidden lg:table-cell"></th> -->
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col" class="relative">
|
||||
Value
|
||||
|
|
@ -150,14 +136,14 @@ const isKeywordReadOnly = (item: Subject) => {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in itemsPaginated" :key="index">
|
||||
<tr v-for="(item, index) in itemsPaginated" :key="item.id ?? index">
|
||||
|
||||
<td data-label="Type" scope="row">
|
||||
<FormControl required v-model="item.type"
|
||||
@update:modelValue="() => { 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(', ') }}
|
||||
{{ formatError(errors[`subjects.${index}.type`]) }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</td>
|
||||
|
|
@ -170,14 +156,14 @@ const isKeywordReadOnly = (item: Subject) => {
|
|||
}
|
||||
" :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(', ') }}
|
||||
{{ formatError(errors[`subjects.${index}.value`]) }}
|
||||
</div>
|
||||
</SearchCategoryAutocomplete>
|
||||
|
||||
<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(', ') }}
|
||||
{{ formatError(errors[`subjects.${index}.value`]) }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</td>
|
||||
|
|
@ -186,7 +172,7 @@ const isKeywordReadOnly = (item: Subject) => {
|
|||
<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(', ') }}
|
||||
{{ formatError(errors[`subjects.${index}.language`]) }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</td>
|
||||
|
|
@ -199,7 +185,6 @@ const isKeywordReadOnly = (item: Subject) => {
|
|||
|
||||
<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 color="danger" :icon="mdiTrashCan" small @click.prevent="removeItem(index)" />
|
||||
</BaseButtons>
|
||||
</td>
|
||||
|
|
@ -207,7 +192,6 @@ const isKeywordReadOnly = (item: Subject) => {
|
|||
</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>
|
||||
|
|
@ -218,8 +202,9 @@ const isKeywordReadOnly = (item: Subject) => {
|
|||
</BaseLevel>
|
||||
</div>
|
||||
|
||||
<div class="text-red-400 text-sm" v-if="errors.subjects && Array.isArray(errors.subjects)">
|
||||
{{ errors.subjects.join(', ') }}
|
||||
<!-- Aggregate error for the whole subjects collection, e.g. "at least 3 keywords must be defined" -->
|
||||
<div class="text-red-400 text-sm" v-if="errors.subjects">
|
||||
{{ formatError(errors.subjects) }}
|
||||
</div>
|
||||
|
||||
<!-- Subjects to delete section -->
|
||||
|
|
@ -261,7 +246,7 @@ const isKeywordReadOnly = (item: Subject) => {
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
@ -269,7 +254,7 @@ const isKeywordReadOnly = (item: Subject) => {
|
|||
background: gray;
|
||||
}
|
||||
|
||||
tr:nth-child(od) {
|
||||
tr:nth-child(od) {
|
||||
background: white;
|
||||
} */
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue