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
390
resources/js/Pages/Admin/Mimetype/Create.vue
Normal file
390
resources/js/Pages/Admin/Mimetype/Create.vue
Normal file
|
@ -0,0 +1,390 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref, watch, computed, Ref, reactive } from 'vue';
|
||||
import { Head, useForm } from '@inertiajs/vue3';
|
||||
import { mdiAccountKey, mdiArrowLeftBoldOutline } from '@mdi/js';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import BaseDivider from '@/Components/BaseDivider.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import mime from 'mime';
|
||||
import FormField from '@/Components/FormField.vue';
|
||||
import FormControl from '@/Components/FormControl.vue';
|
||||
import standardTypes from 'mime/types/standard.js';
|
||||
import otherTypes from 'mime/types/other.js';
|
||||
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue';
|
||||
|
||||
const props = defineProps({
|
||||
permissions: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
borderless: Boolean,
|
||||
transparent: Boolean,
|
||||
ctrlKFocus: Boolean,
|
||||
});
|
||||
|
||||
const isReadOnly = true;
|
||||
// Get keys from standardTypes and otherTypes
|
||||
const standardMimeTypes = Object.keys(standardTypes);
|
||||
const otherMimeTypes = Object.keys(otherTypes);
|
||||
// MIME types list (you can expand this as needed)
|
||||
// const mimeTypes = Object.keys(standardTypes);
|
||||
|
||||
// Concatenate the keys from both types
|
||||
const mimeTypes = [...standardMimeTypes, ...otherMimeTypes];
|
||||
|
||||
const file_extensions = reactive({});
|
||||
const form = useForm({
|
||||
name: '',
|
||||
file_extension: [] as Array<string>,
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
// Function to reset the object
|
||||
function resetFileExtensions() {
|
||||
// Reset to an empty object
|
||||
Object.keys(file_extensions).forEach(key => {
|
||||
delete file_extensions[key];
|
||||
});
|
||||
}
|
||||
|
||||
const inputElClass = computed(() => {
|
||||
const base = [
|
||||
'block p-2.5 w-full z-20 text-sm text-gray-900 bg-gray-50 rounded-r-lg',
|
||||
'dark:placeholder-gray-400 dark:text-white dark:focus:border-blue-500',
|
||||
'h-12',
|
||||
props.borderless ? 'border-0' : 'border',
|
||||
props.transparent ? 'bg-transparent' : 'bg-white dark:bg-slate-800',
|
||||
];
|
||||
// if (props.icon) {
|
||||
base.push('pl-10');
|
||||
// }
|
||||
return base;
|
||||
});
|
||||
|
||||
|
||||
// Handle form submission
|
||||
const submit = async () => {
|
||||
if (isValidForm()) {
|
||||
await form.post(stardust.route('settings.mimetype.store'), form);
|
||||
}
|
||||
};
|
||||
|
||||
// Form validation before submission
|
||||
const isValidForm = (): boolean => {
|
||||
if (!form.name) {
|
||||
form.errors.name = 'Name is required.';
|
||||
return false;
|
||||
} else {
|
||||
form.errors.name = '';
|
||||
}
|
||||
if (!form.file_extension.length) {
|
||||
form.errors.file_extension = ['At least one file extension is required.'];
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const newExtension: Ref = ref(''); //reactive([] as Array<string>);
|
||||
const filteredMimetypes = ref<string[]>([]); // Stores the filtered MIME types for the dropdown
|
||||
const showDropdown = ref(false); // Controls the visibility of the autocomplete dropdown
|
||||
const selectedIndex: Ref<number> = ref(0); // Track selected MIME type in the dropdown
|
||||
const ul: Ref<Array<HTMLLIElement>> = ref([]);
|
||||
watch(selectedIndex, (selectedIndex: number) => {
|
||||
if (selectedIndex != null && ul.value != null) {
|
||||
const currentElement: HTMLLIElement = ul.value[selectedIndex];
|
||||
currentElement &&
|
||||
currentElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
});
|
||||
}
|
||||
});
|
||||
// const extensionError = ref<string | null>(null);
|
||||
const mimetypeError = ref<string | null>(null);
|
||||
|
||||
// const addFileExtension = () => {
|
||||
// if (newExtension.value && !form.file_extensions.includes(newExtension.value)) {
|
||||
// if (isValidFileExtension(newExtension.value)) {
|
||||
// form.file_extensions.push(newExtension.value);
|
||||
// newExtension.value = ''; // Clear the input field
|
||||
// extensionError.value = null; // Clear any existing error
|
||||
// } else {
|
||||
// extensionError.value = 'Invalid file extension or MIME type.';
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
// // Helper to validate file extension
|
||||
// const isValidFileExtension = (extension: string): boolean => {
|
||||
// const mimeType = mime.getType(extension); // e.g. 'application/pdf'
|
||||
// return mimeType !== null;
|
||||
// };
|
||||
|
||||
|
||||
// const removeFileExtension = (index: number) => {
|
||||
// file_extensions.splice(index, 1); // Remove the extension at the given index
|
||||
// };
|
||||
|
||||
const isValidMimeType = (mimeType: string): boolean => {
|
||||
let extensions = mime.getExtension(mimeType)
|
||||
return extensions !== null;
|
||||
};
|
||||
|
||||
// watch(newExtension, (value) => {
|
||||
// if (value) {
|
||||
// filteredMimetypes.value = mimeTypes.filter(mimeType =>
|
||||
// mimeType.toLowerCase().includes(value.toLowerCase())
|
||||
// );
|
||||
// showDropdown.value = true;
|
||||
// } else {
|
||||
// showDropdown.value = false;
|
||||
// }
|
||||
// });
|
||||
async function handleInputChange(e: Event) {
|
||||
// const target = <HTMLInputElement>e.target;
|
||||
|
||||
|
||||
if (newExtension.value.length >= 2) {
|
||||
showDropdown.value = true;
|
||||
filteredMimetypes.value = mimeTypes.filter(mimeType =>
|
||||
mimeType.toLowerCase().includes(newExtension.value.toLowerCase())
|
||||
);
|
||||
} else {
|
||||
// data.results = [];
|
||||
showDropdown.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle MIME type selection from the dropdown
|
||||
const selectResult = (mimeType: string) => {
|
||||
form.name = mimeType;
|
||||
// file_extensions.values = [];
|
||||
resetFileExtensions();
|
||||
showDropdown.value = false;
|
||||
newExtension.value = ''; // Reset the input
|
||||
selectedIndex.value = -1;
|
||||
|
||||
if (form.name && isValidMimeType(form.name)) {
|
||||
const extensions = mime.getAllExtensions(form.name) as Set<string>;
|
||||
// Convert the Set to an array of objects
|
||||
// Convert the Set to an object
|
||||
// Iterate over each extension and set both key and value to the extension
|
||||
Array.from(extensions).forEach(extension => {
|
||||
file_extensions[extension] = extension;
|
||||
});
|
||||
|
||||
// file_extensions.push(...Array.from(formattedExtensions));
|
||||
|
||||
} else {
|
||||
mimetypeError.value = 'Invalid MIME type.';
|
||||
}
|
||||
};
|
||||
|
||||
function onArrowDown() {
|
||||
if (filteredMimetypes.value.length > 0) {
|
||||
selectedIndex.value = selectedIndex.value === filteredMimetypes.value.length - 1 ? 0 : selectedIndex.value + 1;
|
||||
// const currentElement: HTMLLIElement = ul.value[selectedIndex.value];
|
||||
}
|
||||
}
|
||||
|
||||
function onArrowUp() {
|
||||
if (filteredMimetypes.value.length > 0) {
|
||||
selectedIndex.value = selectedIndex.value == 0 || selectedIndex.value == -1 ? filteredMimetypes.value.length - 1 : selectedIndex.value - 1;
|
||||
}
|
||||
}
|
||||
|
||||
function onEnter() {
|
||||
if (Array.isArray(filteredMimetypes.value) && filteredMimetypes.value.length && selectedIndex.value !== -1 && selectedIndex.value < filteredMimetypes.value.length) {
|
||||
//this.display = this.results[this.selectedIndex];
|
||||
const mimeType = filteredMimetypes.value[selectedIndex.value];
|
||||
// this.$emit('person', person);
|
||||
form.name = mimeType;
|
||||
// reset form file extensions
|
||||
// file_extensions.values = [];
|
||||
resetFileExtensions();
|
||||
showDropdown.value = false;
|
||||
newExtension.value = ''; // Reset the input
|
||||
selectedIndex.value = -1;
|
||||
if (form.name) {
|
||||
// clear all loaded file extensions
|
||||
// file_extensions.values = [];
|
||||
resetFileExtensions();
|
||||
if (isValidMimeType(form.name)) {
|
||||
let extensions = mime.getAllExtensions(form.name) as Set<string>;
|
||||
// Convert the Set to an array of objects
|
||||
// Convert the Set to an object
|
||||
Array.from(extensions).forEach(extension => {
|
||||
file_extensions[extension] = extension;
|
||||
});
|
||||
|
||||
// file_extensions.push(...formattedExtensions);
|
||||
} else {
|
||||
mimetypeError.value = 'Invalid MIME type.';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
|
||||
<Head title="Add mimetype" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Add Mimetype" main>
|
||||
<BaseButton :route-name="stardust.route('settings.mimetype.index')" :icon="mdiArrowLeftBoldOutline"
|
||||
label="Back" color="white" rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
<!-- <CardBox form @submit.prevent="form.post(stardust.route('role.store'))"> -->
|
||||
<CardBox form @submit.prevent="submit()">
|
||||
|
||||
<!-- MIME Type Input Field with Autocomplete -->
|
||||
<div class="relative mb-4">
|
||||
|
||||
<input v-model="newExtension" type="text" placeholder="Enter Mimetype Name"
|
||||
class="block w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
:class="inputElClass" @input="handleInputChange" @keydown.down="onArrowDown"
|
||||
@keydown.up="onArrowUp" @keydown.prevent.enter="onEnter">
|
||||
</input>
|
||||
<svg class="w-4 h-4 absolute left-2.5 top-3.5" v-show="newExtension.length < 2"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
<svg class="w-4 h-4 absolute left-2.5 top-3.5" v-show="newExtension.length >= 2"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" @click="() => {
|
||||
newExtension = '';
|
||||
showDropdown = false;
|
||||
form.name = '';
|
||||
resetFileExtensions();
|
||||
}
|
||||
">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
|
||||
<!-- <button type="button" @click="addDefaultFileExtensions"
|
||||
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-300"
|
||||
:disabled="!form.name">
|
||||
+
|
||||
</button> -->
|
||||
<ul v-if="showDropdown && filteredMimetypes.length"
|
||||
class="bg-white dark:bg-slate-800 w-full mt-2 max-h-28 overflow-y-auto scroll-smooth">
|
||||
<li v-for="(mimeType, index) in filteredMimetypes" :key="index" @click="selectResult(mimeType)"
|
||||
class="pl-8 pr-2 py-1 border-b-2 border-gray-100 relative cursor-pointer hover:bg-blue-500 hover:text-white"
|
||||
:class="{
|
||||
'bg-blue-500 text-white': selectedIndex === index,
|
||||
'bg-white text-gray-900': selectedIndex !== index
|
||||
}" :ref="(el: HTMLLIElement) => {
|
||||
ul[index] = el;
|
||||
}
|
||||
">
|
||||
<svg class="absolute w-4 h-4 left-2 top-2" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
<span>{{ mimeType }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="mimetypeError" class="text-red-400 text-sm mt-1">
|
||||
{{ mimetypeError }}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Enabled (Boolean) Input Field -->
|
||||
<!-- <div class="mb-4 flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="form.enabled"
|
||||
id="enabled"
|
||||
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||||
/>
|
||||
<label for="enabled" class="ml-2 block text-sm font-medium text-gray-700">Enabled</label>
|
||||
</div> -->
|
||||
|
||||
<FormField v-if="form.name" label="Mimetype Name" :class="{ 'text-red-400': form.errors.name }">
|
||||
<FormControl v-model="form.name" name="display_name" :error="form.errors.name"
|
||||
:is-read-only="isReadOnly">
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.name">
|
||||
{{ form.errors.name }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
|
||||
<FormField v-if="form.name" help="Activate mimetype immediately?" wrap-body
|
||||
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.enabled" />
|
||||
<span class="check" />
|
||||
<a class="pl-2" target="_blank">Enable mimetype immediately </a>
|
||||
|
||||
</label>
|
||||
</FormField>
|
||||
|
||||
<!-- File Extensions Input Field -->
|
||||
<!-- <div class="space-y-2">
|
||||
<label for="newExtension" class="block text-sm font-medium text-gray-700">
|
||||
File Extensions
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input v-model="newExtension" type="text" id="newExtension"
|
||||
placeholder="Enter file extension (e.g., pdf)"
|
||||
class="block w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-blue-500 focus:border-blue-500" />
|
||||
<button type="button" @click="addFileExtension"
|
||||
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-300"
|
||||
:disabled="!newExtension">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="extensionError" class="text-red-400 text-sm mt-1">
|
||||
{{ extensionError }}
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- File Extensions List -->
|
||||
<!-- <div class="mt-4">
|
||||
<h3 v-if="form.file_extensions.length > 0" class="text-lg font-medium text-gray-900">Added File
|
||||
Extensions</h3>
|
||||
<ul class="space-y-2 mt-2">
|
||||
<li v-for="(extension, index) in form.file_extensions" :key="index"
|
||||
class="flex justify-between items-center p-2 bg-gray-100 rounded-lg">
|
||||
<span class="text-gray-800">{{ extension }}</span>
|
||||
<button @click.prevent="removeFileExtension(index)"
|
||||
class="text-red-500 hover:text-red-700 text-sm font-medium">
|
||||
Remove
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div> -->
|
||||
<FormField label="Permissions" wrap-body>
|
||||
<FormCheckRadioGroup v-model="form.file_extension" :options="file_extensions" name="file_extensions"
|
||||
is-column />
|
||||
</FormField>
|
||||
<div class="text-red-400 text-sm"
|
||||
v-if="form.errors.file_extension && Array.isArray(form.errors.file_extension)">
|
||||
<!-- {{ errors.password_confirmation }} -->
|
||||
{{ form.errors.file_extension.join(', ') }}
|
||||
</div>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Create" :class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing" />
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
|
@ -1,7 +1,8 @@
|
|||
<script lang="ts" setup>
|
||||
import { Head, usePage } from '@inertiajs/vue3';
|
||||
import { mdiAccountKey, mdiSquareEditOutline, mdiAlertBoxOutline } from '@mdi/js';
|
||||
import { mdiAccountKey, mdiSquareEditOutline, mdiAlertBoxOutline, mdiPlus } from '@mdi/js';
|
||||
import { computed, ComputedRef } from 'vue';
|
||||
import type { PropType } from "vue";
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
|
@ -9,23 +10,20 @@ import BaseButton from '@/Components/BaseButton.vue';
|
|||
import CardBox from '@/Components/CardBox.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import NotificationBar from '@/Components/NotificationBar.vue';
|
||||
// import Pagination from '@/Components/Admin/Pagination.vue';
|
||||
// import Sort from '@/Components/Admin/Sort.vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
// import CardBoxModal from '@/Components/CardBoxModal.vue';
|
||||
|
||||
// const isModalDangerActive = ref(false);
|
||||
// const deleteId = ref();
|
||||
interface MimeType {
|
||||
id: number;
|
||||
name: string;
|
||||
file_extension: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
defineProps({
|
||||
mimetypes: {
|
||||
type: Object,
|
||||
type: Array as PropType<MimeType[]>,
|
||||
default: () => ({}),
|
||||
},
|
||||
// filters: {
|
||||
// type: Object,
|
||||
// default: () => ({}),
|
||||
// },
|
||||
can: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
|
@ -33,8 +31,6 @@ defineProps({
|
|||
});
|
||||
|
||||
const flash: ComputedRef<any> = computed(() => {
|
||||
// let test = usePage();
|
||||
// console.log(test);
|
||||
return usePage().props.flash;
|
||||
});
|
||||
|
||||
|
@ -47,16 +43,19 @@ const flash: ComputedRef<any> = computed(() => {
|
|||
|
||||
<Head title="Mime Types" />
|
||||
<SectionMain>
|
||||
<!--
|
||||
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Mime Types" main>
|
||||
<!-- <BaseButton
|
||||
</SectionTitleLineWithButton> -->
|
||||
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Mime Types" main>
|
||||
<BaseButton
|
||||
v-if="can.create"
|
||||
:route-name="stardust.route('settings.role.create')"
|
||||
:route-name="stardust.route('settings.mimetype.create')"
|
||||
:icon="mdiPlus"
|
||||
label="Add"
|
||||
color="info"
|
||||
rounded-full
|
||||
small
|
||||
/> -->
|
||||
/>
|
||||
</SectionTitleLineWithButton>
|
||||
<NotificationBar v-if="flash.message" color="success" :icon="mdiAlertBoxOutline">
|
||||
{{ flash.message }}
|
||||
|
@ -83,13 +82,7 @@ const flash: ComputedRef<any> = computed(() => {
|
|||
<tbody>
|
||||
<tr v-for="mimetype in mimetypes" :key="mimetype.id">
|
||||
<td data-label="Name">
|
||||
<!-- <Link
|
||||
:href="stardust.route('settings.role.show', [role.id])"
|
||||
class="no-underline hover:underline text-cyan-600 dark:text-cyan-400"
|
||||
>
|
||||
{{ license.name }}
|
||||
</Link> -->
|
||||
{{ mimetype.name }}
|
||||
{{ mimetype.name }} ({{ mimetype.file_extension }})
|
||||
</td>
|
||||
<td data-label="Status">
|
||||
<template v-if="mimetype.enabled">Active</template>
|
||||
|
@ -102,16 +95,13 @@ const flash: ComputedRef<any> = computed(() => {
|
|||
:route-name="stardust.route('settings.mimetype.down', [mimetype.id])"
|
||||
color="warning" :icon="mdiSquareEditOutline" label="disable" small />
|
||||
<BaseButton v-else
|
||||
:route-name="stardust.route('settings.mimetype.up', [mimetype.id])" color="success"
|
||||
:icon="mdiSquareEditOutline" label="enable" small />
|
||||
:route-name="stardust.route('settings.mimetype.up', [mimetype.id])"
|
||||
color="success" :icon="mdiSquareEditOutline" label="enable" small />
|
||||
</BaseButtons>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- <div class="py-4">
|
||||
<Pagination v-bind:data="roles.meta" />
|
||||
</div> -->
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
|
|
|
@ -38,6 +38,8 @@ const props = defineProps({
|
|||
|
||||
const form = useForm({
|
||||
login: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
|
@ -74,6 +76,22 @@ const submit = async () => {
|
|||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="First Name" :class="{ 'text-red-400': errors.first_name }">
|
||||
<FormControl v-model="form.first_name" type="text" placeholder="Enter First Name" :errors="errors.first_name">
|
||||
<div class="text-red-400 text-sm" v-if="errors.first_name && Array.isArray(errors.first_name)">
|
||||
{{ errors.first_name.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Last Name" :class="{ 'text-red-400': errors.last_name }">
|
||||
<FormControl v-model="form.last_name" type="text" placeholder="Enter Last Name" :errors="errors.last_name">
|
||||
<div class="text-red-400 text-sm" v-if="errors.last_name && Array.isArray(errors.last_name)">
|
||||
{{ errors.last_name.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Email" :class="{ 'text-red-400': errors.email }">
|
||||
<FormControl v-model="form.email" type="text" placeholder="Enter Email" :errors="errors.email">
|
||||
<div class="text-red-400 text-sm" v-if="errors.email && Array.isArray(errors.email)">
|
||||
|
|
|
@ -39,6 +39,8 @@ const props = defineProps({
|
|||
const form = useForm({
|
||||
_method: 'put',
|
||||
login: props.user.login,
|
||||
first_name: props.user.first_name,
|
||||
last_name: props.user.last_name,
|
||||
email: props.user.email,
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
|
@ -82,6 +84,22 @@ const handleScore = (score: number) => {
|
|||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="First Name" :class="{ 'text-red-400': errors.first_name }">
|
||||
<FormControl v-model="form.first_name" type="text" placeholder="Enter First Name" :errors="errors.first_name">
|
||||
<div class="text-red-400 text-sm" v-if="errors.first_name && Array.isArray(errors.first_name)">
|
||||
{{ errors.first_name.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Last Name" :class="{ 'text-red-400': errors.last_name }">
|
||||
<FormControl v-model="form.last_name" type="text" placeholder="Enter Last Name" :errors="errors.last_name">
|
||||
<div class="text-red-400 text-sm" v-if="errors.last_name && Array.isArray(errors.last_name)">
|
||||
{{ errors.last_name.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Enter Email" :class="{ 'text-red-400': errors.email }">
|
||||
<FormControl v-model="form.email" type="text" placeholder="Email" :errors="errors.email">
|
||||
<div class="text-red-400 text-sm" v-if="errors.email && Array.isArray(errors.email)">
|
||||
|
|
|
@ -11,7 +11,7 @@ import BaseButton from '@/Components/BaseButton.vue';
|
|||
import CardBox from '@/Components/CardBox.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import NotificationBar from '@/Components/NotificationBar.vue';
|
||||
import Pagination from '@/Components/Admin/Pagination.vue';
|
||||
import Pagination from '@/Components/Pagination.vue';
|
||||
import Sort from '@/Components/Admin/Sort.vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
// import { Vue } from 'vue-facing-decorator';
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(', ') }}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue