feat: enhance user management, mimetype creation, and validation
Some checks failed
CI Pipeline / japa-tests (push) Failing after 1m8s

- **AdminuserController.ts**: enable editing `first_name` and `last_name` for user creation and updates
- **MimetypeController.ts**: add creation support for mimetypes with selectable extensions
- **Models**: add `Mimetype` model (mime_type.ts); add `SnakeCaseNamingStrategy` for User model
- **Validators**:
  - **updateDatasetValidator**: increase title length to 255 and description length to 2500
  - **User Validators**: refine `createUserValidator` and `updateUserValidator` to include `first_name` and `last_name`
- **vanilla_error_reporter**: improve error reporting for wildcard fields
- **SKOS Query**: refine keyword request in `SearchCategoryAutocomplete.vue`
- **UI Enhancements**:
  - improve icon design in wizard (Wizard.vue)
  - add components for mimetype creation (Create.vue and button in Index.vue)
- **Routes**: update `routes.ts` to include new AdonisJS routes
This commit is contained in:
Kaimbacher 2024-10-31 11:02:36 +01:00
parent 2235f3905a
commit 49bd96ee77
24 changed files with 1548 additions and 945 deletions

View file

@ -39,7 +39,8 @@ import { MainService } from '@/Stores/main';
import { notify } from '@/notiwind';
import MapComponent from '@/Components/Map/map.component.vue';
import { MapOptions } from '@/Components/Map/MapOptions';
import { LatLngBoundsExpression } from 'leaflet/src/geo/LatLngBounds';
// import { LatLngBoundsExpression } from 'leaflet/src/geo/LatLngBounds';
import { LatLngBoundsExpression } from 'leaflet';
import { LayerOptions } from '@/Components/Map/LayerOptions';
import TableKeywords from '@/Components/TableKeywords.vue';
import NotificationBar from '@/Components/NotificationBar.vue';
@ -277,7 +278,7 @@ const mapId = 'test';
// };
const nextStep = async () => {
let route ="";
let route = "";
if (formStep.value == 1) {
route = stardust.route('dataset.first.step');
} else if (formStep.value == 2) {
@ -504,7 +505,7 @@ Removes a selected keyword
<icon-mandatory></icon-mandatory>
</icon-wizard>
<icon-wizard :is-current="formStep == 3" :is-checked="formStep > 3" :label="'Recommendet'">
<icon-wizard :is-current="formStep == 3" :is-checked="formStep > 3" :label="'Recommended'">
<icon-recommendet></icon-recommendet>
</icon-wizard>
@ -595,7 +596,8 @@ Removes a selected keyword
:class="{ 'text-red-400': form.errors['titles.0.value'] }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.titles[0].value" type="textarea"
placeholder="[enter main title]" :show-char-count="true" :max-input-length="255">
placeholder="[enter main title]" :show-char-count="true"
:max-input-length="255">
<div class="text-red-400 text-sm"
v-if="form.errors['titles.0.value'] && Array.isArray(form.errors['titles.0.value'])">
{{ form.errors['titles.0.value'].join(', ') }}
@ -670,7 +672,8 @@ Removes a selected keyword
:class="{ 'text-red-400': form.errors['descriptions.0.value'] }"
class="w-full mx-2 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(', ') }}
@ -702,7 +705,8 @@ Removes a selected keyword
:class="{ 'text-red-400': form.errors[`descriptions.${index}.value`] }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.descriptions[index].value" type="text"
placeholder="[enter additional description]" :show-char-count="true" :max-input-length="2500">
placeholder="[enter additional description]" :show-char-count="true"
:max-input-length="2500">
<div class="text-red-400 text-sm" v-if="form.errors[`descriptions.${index}.value`] &&
Array.isArray(form.errors[`descriptions.${index}.value`])
">
@ -744,7 +748,8 @@ Removes a selected keyword
<SearchAutocomplete source="/api/persons" :response-property="'first_name'"
placeholder="search in person table...." v-on:person="onAddAuthor"></SearchAutocomplete>
<TablePersons :errors="form.errors" :persons="form.authors" :relation="'authors'" v-if="form.authors.length > 0" />
<TablePersons :errors="form.errors" :persons="form.authors" :relation="'authors'"
v-if="form.authors.length > 0" />
<div class="text-red-400 text-sm" v-if="errors.authors && Array.isArray(errors.authors)">
{{ errors.authors.join(', ') }}
</div>
@ -762,8 +767,9 @@ Removes a selected keyword
placeholder="search in person table...." v-on:person="onAddContributor">
</SearchAutocomplete>
<TablePersons :persons="form.contributors" :relation="'contributors'" v-if="form.contributors.length > 0"
:contributortypes="contributorTypes" :errors="form.errors" />
<TablePersons :persons="form.contributors" :relation="'contributors'"
v-if="form.contributors.length > 0" :contributortypes="contributorTypes"
:errors="form.errors" />
<div class="text-red-400 text-sm"
v-if="form.errors.contributors && Array.isArray(form.errors.contributors)">
{{ form.errors.contributors.join(', ') }}
@ -807,8 +813,8 @@ Removes a selected keyword
<FormField label="Coverage X Min"
:class="{ 'text-red-400': form.errors['coverage.x_min'] }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.x_min" type="text" inputmode="numeric" pattern="\d*"
placeholder="[enter x_min]">
<FormControl required v-model="form.coverage.x_min" type="text" inputmode="numeric"
pattern="\d*" placeholder="[enter x_min]">
<div class="text-red-400 text-sm"
v-if="form.errors['coverage.x_min'] && Array.isArray(form.errors['coverage.x_min'])">
{{ form.errors['coverage.x_min'].join(', ') }}
@ -1066,6 +1072,7 @@ Removes a selected keyword
</div>
</div>
<template #footer>
<div class="flex p-2 mt-4">
<button v-if="formStep > 1" @click="prevStep"
@ -1087,11 +1094,30 @@ Removes a selected keyword
</button>
</div>
</div>
<progress v-if="form.progress" :value="form.progress.percentage" max="100">{{
form.progress.percentage
}}%</progress>
<progress v-if="form.progress" :value="form.progress.percentage" max="100">
{{ form.progress.percentage }}%
</progress>
</template>
</CardBox>
<!-- 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">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M12 2a10 10 0 0110 10h-4a6 6 0 00-6-6V2z"></path>
</svg>
</div>
<!-- <div
class="fixed inset-0 flex items-center justify-center bg-gray-500 bg-opacity-50">
<svg class="animate-spin h-10 w-10 text-white" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 0116 0 8 8 0 01-16 0zm2 0a6 6 0 0112 0 6 6 0 01-12 0z"></path>
</svg>
</div> -->
</SectionMain>
</LayoutAuthenticated>
</template>

View file

@ -79,7 +79,7 @@
<FormField label="Main Title *" help="required: main title"
:class="{ 'text-red-400': form.errors['titles.0.value'] }" class="w-full mr-1 flex-1">
<FormControl required v-model="form.titles[0].value" type="text"
placeholder="[enter main title]">
placeholder="[enter main title]" :show-char-count="true" :max-input-length="255">
<div class="text-red-400 text-sm"
v-if="form.errors['titles.0.value'] && Array.isArray(form.errors['titles.0.value'])">
{{ form.errors['titles.0.value'].join(', ') }}
@ -163,7 +163,7 @@
: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]">
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(', ') }}

View file

@ -92,7 +92,7 @@ const formatServerState = (state: string) => {
<!-- table -->
<CardBox class="mb-6" has-table>
<table class="">
<table class="w-full table-fixed">
<thead>
<tr>
<th scope="col" class="py-3 text-left text-xs font-medium uppercase tracking-wider">
@ -114,13 +114,13 @@ const formatServerState = (state: string) => {
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="dataset in props.datasets.data" :key="dataset.id" :class="getRowClass(dataset)">
<td data-label="Login" class="py-4 whitespace-nowrap text-gray-700 dark:text-white">
<td data-label="Login" class="py-4 whitespace-nowrap text-gray-700 dark:text-white table-title">
<!-- <Link v-bind:href="stardust.route('settings.user.show', [user.id])"
class="no-underline hover:underline text-cyan-600 dark:text-cyan-400">
{{ user.login }}
</Link> -->
<!-- {{ user.id }} -->
{{ dataset.main_title }}
{{ dataset.main_title }}
</td>
<td class="py-4 whitespace-nowrap text-gray-700 dark:text-white">
{{ formatServerState(dataset.server_state) }}
@ -156,44 +156,55 @@ const formatServerState = (state: string) => {
</LayoutAuthenticated>
</template>
<!-- <style scoped lang="css">
.pure-table tr.released {
background-color: rgb(52 211 153);
color: gray;
<style scoped lang="css">
.table-title {
max-width: 200px; /* set a maximum width */
overflow: hidden; /* hide overflow */
text-overflow: ellipsis; /* show ellipsis for overflowed text */
white-space: nowrap; /* prevent wrapping */
}
.table-fixed {
table-layout: fixed;
}
.pure-table tr.inprogress {
/* .pure-table tr.released {
background-color: rgb(52 211 153);
color: gray;
} */
/* .pure-table tr.inprogress {
padding: 0.8em;
background-color: rgb(94 234 212);
color: gray;
}
} */
.pure-table tr.editor_accepted {
/* .pure-table tr.editor_accepted {
background-color: rgb(125 211 252);
color: gray;
}
} */
.pure-table tr.rejected_reviewer {
/* .pure-table tr.rejected_reviewer {
padding: 0.8em;
background-color: orange;
color: gray;
}
} */
.pure-table tr.rejected_editor {
/* .pure-table tr.rejected_editor {
background-color: orange;
color: gray;
}
} */
.pure-table tr.reviewed {
/* .pure-table tr.reviewed {
background-color: yellow;
color: gray;
}
} */
.pure-table tr.approved {
/* .pure-table tr.approved {
background-color: rgb(86, 86, 241);
color: whitesmoke;
color: whitesmoke;
}
}*/
</style> -->
</style>