feat: enhance user management, mimetype creation, and validation
Some checks failed
CI Pipeline / japa-tests (push) Failing after 1m8s
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:
parent
2235f3905a
commit
49bd96ee77
24 changed files with 1548 additions and 945 deletions
|
@ -1,50 +1,50 @@
|
|||
<template>
|
||||
<div class="flex items-center relative">
|
||||
<!-- v-bind:class="{ 'text-white bg-teal-600 border-teal-600': isCurrent, 'border-teal-600': isChecked }" -->
|
||||
<div
|
||||
class="text-gray-500 rounded-full transition duration-500 ease-in-out h-12 w-12 py-3 border-2"
|
||||
:class="[
|
||||
isCurrent ? 'text-white bg-teal-600 border-teal-600' : 'border-gray-300',
|
||||
isChecked && 'text-teal-600 border-teal-600',
|
||||
]"
|
||||
>
|
||||
<!-- <svg class="my-svg-component" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437l1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008z"
|
||||
/>
|
||||
</svg> -->
|
||||
<!-- The main circular icon with dynamic classes based on the state of the step -->
|
||||
<div class="text-gray-500 rounded-full transition duration-500 ease-in-out h-12 w-12 py-3 border-2"
|
||||
:class="[
|
||||
isCurrent ? 'text-white bg-teal-600 border-teal-600' : 'border-gray-300',
|
||||
isChecked && 'text-teal-600 border-teal-600',
|
||||
]">
|
||||
<!-- Slot for custom content inside the circle -->
|
||||
<slot></slot>
|
||||
<div class="absolute top-0 -ml-10 text-center mt-16 w-32 text-xs font-medium uppercase invisible sm:visible">
|
||||
<!-- Label displayed above the icon, visibility controlled by isCurrent and isDark -->
|
||||
<div class="absolute top-0 -ml-10 text-center mt-16 w-32 text-xs font-medium uppercase invisible sm:visible"
|
||||
:class="[!isDark && isCurrent ? 'font-black text-green-600' : '']">
|
||||
{{ label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!isLastStep"
|
||||
class="flex-auto border-t-2 transition duration-500 ease-in-out invisible sm:visible"
|
||||
:class="[isChecked ? 'border-teal-600' : 'border-gray-300']"
|
||||
></div>
|
||||
<!-- Divider line between steps, only visible if this is not the last step -->
|
||||
<div v-if="!isLastStep" class="flex-auto border-t-2 transition duration-500 ease-in-out invisible sm:visible"
|
||||
:class="[isChecked ? 'border-teal-600' : 'border-gray-300']"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
|
||||
const styleService = StyleService();
|
||||
|
||||
export default {
|
||||
name: 'Icon_Multistep',
|
||||
|
||||
props: {
|
||||
isCurrent: Boolean,
|
||||
isChecked: Boolean,
|
||||
isLastStep: Boolean,
|
||||
label: String,
|
||||
isCurrent: Boolean, // Indicates if this step is the current one
|
||||
isChecked: Boolean, // Indicates if this step has been checked
|
||||
isLastStep: Boolean, // Indicates if this step is the last one
|
||||
label: String, // Label to display above the icon
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
mode: 'light',
|
||||
checkedClass: 'border-teal-600',
|
||||
uncheckedClass: 'border-gray-300',
|
||||
mode: 'light', // Light mode setting
|
||||
checkedClass: 'border-teal-600', // Class for checked state
|
||||
uncheckedClass: 'border-gray-300', // Class for unchecked state
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// Computed property to determine if dark mode is enabled
|
||||
isDark() {
|
||||
return styleService.darkMode === true; // Return true if dark mode is active
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -103,9 +103,9 @@
|
|||
|
||||
<svg class="w-4 h-4 absolute left-2.5 top-3.5" v-show="computedValue.length >= 2"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" @click="() => {
|
||||
computedValue = '';
|
||||
data.isOpen = false;
|
||||
}
|
||||
computedValue = '';
|
||||
data.isOpen = false;
|
||||
}
|
||||
">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
|
@ -181,17 +181,6 @@ let computedValue = computed({
|
|||
},
|
||||
});
|
||||
|
||||
// const dropDownClass = computed(() => {
|
||||
// const base = [
|
||||
// 'z-10 bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700',
|
||||
// props.borderless ? 'border-0' : 'border',
|
||||
// props.transparent ? 'bg-transparent' : 'bg-white dark:bg-slate-800',
|
||||
// // props.isReadOnly ? 'bg-gray-50 dark:bg-slate-600' : 'bg-white dark:bg-slate-800',
|
||||
// statesToggle.value == true ? 'block' : 'hidden',
|
||||
// ];
|
||||
// return base;
|
||||
// });
|
||||
|
||||
const inputElClass = computed(() => {
|
||||
// class="block p-2.5 w-full z-20 text-sm text-gray-900 bg-gray-50 rounded-r-lg border-l-gray-50 border-l-2 border border-gray-300
|
||||
// focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-l-gray-700 dark:border-gray-600
|
||||
|
@ -272,7 +261,7 @@ const setLanguage = (item) => {
|
|||
data.isOpen = false;
|
||||
};
|
||||
|
||||
const ENDPOINT = 'https://resource.geolba.ac.at/PoolParty/sparql/geoera';
|
||||
const ENDPOINT = 'https://resource.geolba.ac.at/PoolParty/sparql/keyword';
|
||||
// const USER_LANG = 'de';
|
||||
|
||||
// watch(search, async () => {
|
||||
|
@ -306,7 +295,34 @@ async function handleInput(e: Event) {
|
|||
// }
|
||||
// }
|
||||
|
||||
async function request(url, param) {
|
||||
async function request(url: string, param: string) {
|
||||
try {
|
||||
let query = encodeURIComponent(`
|
||||
PREFIX skos:<http://www.w3.org/2004/02/skos/core#>
|
||||
select distinct (?label as ?title) ?s (strlen(str(?label)) as ?strlen)
|
||||
where
|
||||
{
|
||||
VALUES ?n {"${sparqlEncode(param.toLowerCase())}"}
|
||||
?s skos:prefLabel ?label .
|
||||
filter(lang(?label)='${language.value}')
|
||||
filter(regex(?label, ?n, "i")) # Case-insensitive regex match
|
||||
}
|
||||
order by ?label ?strlen
|
||||
|
||||
`);
|
||||
let response = await searchTerm(url + '?query=' + query + '&format=application/json');
|
||||
error.value = '';
|
||||
data.results = getResults(response);
|
||||
// // this.results = res.data;
|
||||
// // this.loading = false;
|
||||
} catch (error) {
|
||||
error.value = error.message;
|
||||
// this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function requestOriginal(url: string, param: string) {
|
||||
try {
|
||||
let query = encodeURIComponent(`
|
||||
PREFIX dcterms:<http://purl.org/dc/terms/>
|
||||
|
@ -316,8 +332,15 @@ async function request(url, param) {
|
|||
VALUES ?n {"${sparqlEncode(param.toLowerCase())}"}
|
||||
VALUES ?p { skos:prefLabel skos:altLabel }
|
||||
?s a skos:Concept; ?p ?lEN . FILTER((lang(?lEN)="en"))
|
||||
|
||||
# FILTER(regex(str(?s), 'ncl/geoera/keyword'))
|
||||
|
||||
OPTIONAL{?s ?p ?l . FILTER(lang(?l)="${language.value}")}
|
||||
BIND(COALESCE(?l, ?lEN) AS ?L) . FILTER(regex(?L,?n,"i"))
|
||||
|
||||
FILTER(!regex(?L, "\(category\)", "i"))
|
||||
FILTER(!regex(?L, "\(kategorie\)", "i"))
|
||||
|
||||
?s skos:prefLabel ?plEN . FILTER((lang(?plEN)="en"))
|
||||
OPTIONAL{?s skos:prefLabel ?pl . FILTER(lang(?pl)="${language.value}")}
|
||||
BIND(COALESCE(?pl, ?plEN) AS ?title)
|
||||
|
@ -327,6 +350,7 @@ async function request(url, param) {
|
|||
ORDER BY ?sort
|
||||
LIMIT 100`);
|
||||
|
||||
|
||||
let response = await searchTerm(url + '?query=' + query + '&format=application/json');
|
||||
error.value = '';
|
||||
data.results = getResults(response);
|
||||
|
@ -351,7 +375,7 @@ function getResults(response) {
|
|||
return [];
|
||||
}
|
||||
|
||||
function sparqlEncode(str) {
|
||||
function sparqlEncode(str: string) {
|
||||
var hex, i;
|
||||
str = str.toLowerCase();
|
||||
var result = '';
|
||||
|
@ -383,14 +407,4 @@ function clear() {
|
|||
// this.$emit("clear");
|
||||
}
|
||||
|
||||
// function onEnter() {
|
||||
// if (Array.isArray(data.results) && data.results.length && selectedIndex.value !== -1 && selectedIndex.value < data.results.length) {
|
||||
// //this.display = this.results[this.selectedIndex];
|
||||
// const person = data.results[selectedIndex.value];
|
||||
// // this.$emit('person', person);
|
||||
// emit('person', person);
|
||||
// clear();
|
||||
// selectedIndex.value = -1;
|
||||
// }
|
||||
// }
|
||||
</script>
|
||||
|
|
|
@ -61,25 +61,6 @@ const removeItem = (key) => {
|
|||
items.value.splice(key, 1);
|
||||
};
|
||||
|
||||
// const remove = (arr, cb) => {
|
||||
// const newArr = [];
|
||||
|
||||
// arr.forEach((item) => {
|
||||
// if (!cb(item)) {
|
||||
// newArr.push(item);
|
||||
// }
|
||||
// });
|
||||
|
||||
// return newArr;
|
||||
// };
|
||||
|
||||
// const checked = (isChecked, client) => {
|
||||
// if (isChecked) {
|
||||
// checkedRows.value.push(client);
|
||||
// } else {
|
||||
// checkedRows.value = remove(checkedRows.value, (row) => row.id === client.id);
|
||||
// }
|
||||
// };
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -114,10 +95,7 @@ const removeItem = (key) => {
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in itemsPaginated" :key="index">
|
||||
<!-- <TableCheckboxCell v-if="checkable" @checked="checked($event, client)" /> -->
|
||||
<!-- <td class="border-b-0 lg:w-6 before:hidden hidden lg:table-cell">
|
||||
<UserAvatar :username="client.value" class="w-24 h-24 mx-auto lg:w-6 lg:h-6" />
|
||||
</td> -->
|
||||
|
||||
<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">
|
||||
<div class="text-red-400 text-sm" v-if="errors[`subjects.${index}.type`]">
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue