- add key word table for submitter with adeqate validations ind DatasetController.ts an model Subject.ts
All checks were successful
CI Pipeline / japa-tests (push) Successful in 51s
All checks were successful
CI Pipeline / japa-tests (push) Successful in 51s
- npm updates
This commit is contained in:
parent
43fd349301
commit
440fdb9fa7
|
@ -199,6 +199,17 @@ export default class DatasetController {
|
||||||
|
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
|
subjects: schema.array([rules.minLength(3)]).members(
|
||||||
|
schema.object().members({
|
||||||
|
value: schema.string({ trim: true }, [rules.minLength(3), rules.maxLength(255)]),
|
||||||
|
// type: schema.enum(Object.values(TitleTypes)),
|
||||||
|
language: schema.string({ trim: true }, [
|
||||||
|
rules.minLength(2),
|
||||||
|
rules.maxLength(255),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -262,5 +273,11 @@ export default class DatasetController {
|
||||||
'authors.minLength': 'at least {{ options.minLength }} author must be defined',
|
'authors.minLength': 'at least {{ options.minLength }} author must be defined',
|
||||||
|
|
||||||
'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`,
|
'after': `{{ field }} must be older than ${dayjs().add(10, 'day')}`,
|
||||||
|
|
||||||
|
'subjects.minLength': 'at least {{ options.minLength }} keywords must be defined',
|
||||||
|
'subjects.*.value.required': 'keyword value is required',
|
||||||
|
'subjects.*.value.minLength': 'keyword value must be at least {{ options.minLength }} characters long',
|
||||||
|
'subjects.*.type.required': 'keyword type is required',
|
||||||
|
'subjects.*.language.required': 'language of keyword is required',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
76
app/Models/Subject.ts
Normal file
76
app/Models/Subject.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import { column, BaseModel, SnakeCaseNamingStrategy, manyToMany, ManyToMany, beforeCreate, beforeUpdate } from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import Dataset from './Dataset';
|
||||||
|
|
||||||
|
export default class Subject extends BaseModel {
|
||||||
|
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||||
|
public static table = 'dataset_subjects';
|
||||||
|
public static selfAssignPrimaryKey = false;
|
||||||
|
|
||||||
|
@column({
|
||||||
|
isPrimary: true,
|
||||||
|
})
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public language: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public type: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public value: string;
|
||||||
|
|
||||||
|
@column({})
|
||||||
|
public external_key: string;
|
||||||
|
|
||||||
|
@column.dateTime({
|
||||||
|
serialize: (value: Date | null) => {
|
||||||
|
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
|
||||||
|
},
|
||||||
|
autoCreate: true,
|
||||||
|
})
|
||||||
|
public created_at: DateTime;
|
||||||
|
|
||||||
|
@column.dateTime({
|
||||||
|
serialize: (value: Date | null) => {
|
||||||
|
return value ? dayjs(value).format('MMMM D YYYY HH:mm a') : value;
|
||||||
|
},
|
||||||
|
autoCreate: true,
|
||||||
|
autoUpdate: true,
|
||||||
|
})
|
||||||
|
public updated_at: DateTime;
|
||||||
|
|
||||||
|
@beforeCreate()
|
||||||
|
@beforeUpdate()
|
||||||
|
public static async resetDate(role) {
|
||||||
|
role.created_at = this.formatDateTime(role.created_at);
|
||||||
|
role.updated_at = this.formatDateTime(role.updated_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static formatDateTime(datetime) {
|
||||||
|
let value = new Date(datetime);
|
||||||
|
return datetime
|
||||||
|
? value.getFullYear() +
|
||||||
|
'-' +
|
||||||
|
(value.getMonth() + 1) +
|
||||||
|
'-' +
|
||||||
|
value.getDate() +
|
||||||
|
' ' +
|
||||||
|
value.getHours() +
|
||||||
|
':' +
|
||||||
|
value.getMinutes() +
|
||||||
|
':' +
|
||||||
|
value.getSeconds()
|
||||||
|
: datetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@manyToMany(() => Dataset, {
|
||||||
|
pivotForeignKey: 'subject_id',
|
||||||
|
pivotRelatedForeignKey: 'document_id',
|
||||||
|
pivotTable: 'link_dataset_subjects',
|
||||||
|
})
|
||||||
|
public datasets: ManyToMany<typeof Dataset>;
|
||||||
|
}
|
645
package-lock.json
generated
645
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -49,7 +49,7 @@
|
||||||
"@types/node": "^20.1.1",
|
"@types/node": "^20.1.1",
|
||||||
"@types/proxy-addr": "^2.0.0",
|
"@types/proxy-addr": "^2.0.0",
|
||||||
"@types/source-map-support": "^0.5.6",
|
"@types/source-map-support": "^0.5.6",
|
||||||
"@vue/tsconfig": "^0.3.2",
|
"@vue/tsconfig": "^0.4.0",
|
||||||
"adonis-preset-ts": "^2.1.0",
|
"adonis-preset-ts": "^2.1.0",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"babel-preset-typescript-vue3": "^2.0.17",
|
"babel-preset-typescript-vue3": "^2.0.17",
|
||||||
|
|
|
@ -40,6 +40,10 @@ table {
|
||||||
@apply lg:bg-gray-50 lg:dark:bg-slate-800;
|
@apply lg:bg-gray-50 lg:dark:bg-slate-800;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* tbody tr:nth-child(even) {
|
||||||
|
@apply lg:bg-gray-50 lg:dark:bg-slate-800 lg:bg-opacity-25;
|
||||||
|
} */
|
||||||
|
|
||||||
td:before {
|
td:before {
|
||||||
content: attr(data-label);
|
content: attr(data-label);
|
||||||
@apply font-semibold pr-3 text-left lg:hidden;
|
@apply font-semibold pr-3 text-left lg:hidden;
|
||||||
|
|
172
resources/js/Components/TableKeywords.vue
Normal file
172
resources/js/Components/TableKeywords.vue
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
// import { MainService } from '@/Stores/main';
|
||||||
|
import { StyleService } from '@/Stores/style';
|
||||||
|
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 Person from 'App/Models/Person';
|
||||||
|
import { Subject } from '@/Dataset';
|
||||||
|
// import FormField from '@/Components/FormField.vue';
|
||||||
|
import FormControl from '@/Components/FormControl.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
checkable: Boolean,
|
||||||
|
keywords: {
|
||||||
|
type: Array<Subject>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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(() => items.value.slice(perPage.value * currentPage.value, perPage.value * (currentPage.value + 1)));
|
||||||
|
|
||||||
|
const numPages = computed(() => Math.ceil(items.value.length / perPage.value));
|
||||||
|
|
||||||
|
const currentPageHuman = computed(() => currentPage.value + 1);
|
||||||
|
|
||||||
|
const pagesList = computed(() => {
|
||||||
|
const pagesList: Array<number> = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < numPages.value; i++) {
|
||||||
|
pagesList.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pagesList;
|
||||||
|
});
|
||||||
|
|
||||||
|
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>
|
||||||
|
<!-- <CardBoxModal v-model="isModalActive" title="Sample modal">
|
||||||
|
<p>Lorem ipsum dolor sit amet <b>adipiscing elit</b></p>
|
||||||
|
<p>This is sample modal</p>
|
||||||
|
</CardBoxModal>
|
||||||
|
|
||||||
|
<CardBoxModal v-model="isModalDangerActive" large-title="Please confirm" button="danger" has-cancel>
|
||||||
|
<p>Lorem ipsum dolor sit amet <b>adipiscing elit</b></p>
|
||||||
|
<p>This is sample modal</p>
|
||||||
|
</CardBoxModal> -->
|
||||||
|
|
||||||
|
<!-- <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>Value</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Language</th>
|
||||||
|
|
||||||
|
<th />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(item, index) in itemsPaginated" :key="index" class="bg-gray-50">
|
||||||
|
<!-- <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="Value">
|
||||||
|
<FormControl required v-model="item.value" type="text" placeholder="[enter keyword value]" :borderless="true">
|
||||||
|
<!-- <div class="text-red-400 text-sm" v-if="form.errors[`titles.${index}.value`]">
|
||||||
|
{{ form.errors[`titles.${index}.value`].join(', ') }}
|
||||||
|
</div> -->
|
||||||
|
<div class="text-red-400 text-sm" v-if="errors[`subjects.${index}.value`]">
|
||||||
|
{{ errors[`subjects.${index}.value`].join(', ') }}
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
</td>
|
||||||
|
<td data-label="Type">
|
||||||
|
{{ item.type }}
|
||||||
|
</td>
|
||||||
|
<td data-label="Type">
|
||||||
|
<FormControl
|
||||||
|
required
|
||||||
|
v-model="item.language"
|
||||||
|
:type="'select'"
|
||||||
|
placeholder="[Enter Language]"
|
||||||
|
:options="{ de: 'de', en: 'en' }"
|
||||||
|
>
|
||||||
|
<div class="text-red-400 text-sm" v-if="errors[`subjects.${index}.language`]">
|
||||||
|
{{ errors[`subjects.${index}.language`].join(', ') }}
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
</td>
|
||||||
|
<td class="before:hidden lg:w-1 whitespace-nowrap">
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
</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>
|
||||||
|
<BaseButton
|
||||||
|
v-for="page in pagesList"
|
||||||
|
:key="page"
|
||||||
|
:active="page === currentPage"
|
||||||
|
:label="page + 1"
|
||||||
|
small
|
||||||
|
:outline="styleService.darkMode"
|
||||||
|
@click="currentPage = page"
|
||||||
|
/>
|
||||||
|
</BaseButtons>
|
||||||
|
<small>Page {{ currentPageHuman }} of {{ numPages }}</small>
|
||||||
|
</BaseLevel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-red-400 text-sm" v-if="errors.subjects && Array.isArray(errors.subjects)">
|
||||||
|
{{ errors.subjects.join(', ') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -16,6 +16,15 @@ export interface Dataset {
|
||||||
coverage: Coverage,
|
coverage: Coverage,
|
||||||
errors?: IErrorMessage;
|
errors?: IErrorMessage;
|
||||||
// async (user): Promise<void>;
|
// async (user): Promise<void>;
|
||||||
|
subjects: Array<Subject>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Subject {
|
||||||
|
// id: number;
|
||||||
|
language: string
|
||||||
|
type: string;
|
||||||
|
value: string;
|
||||||
|
external_key?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Title {
|
export interface Title {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Head, useForm } from '@inertiajs/vue3';
|
import { Head, useForm } from '@inertiajs/vue3';
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { Dataset, Description, Title } from '@/Dataset';
|
|
||||||
|
import { Dataset, Description, Title, Subject } from '@/Dataset';
|
||||||
import {
|
import {
|
||||||
mdiDatabasePlus,
|
mdiDatabasePlus,
|
||||||
mdiMinusCircle,
|
mdiMinusCircle,
|
||||||
|
@ -40,6 +41,7 @@ import MapComponent from '@/Components/Map/map.component.vue';
|
||||||
import { MapOptions } from '@/Components/Map/MapOptions';
|
import { MapOptions } from '@/Components/Map/MapOptions';
|
||||||
import { LatLngBoundsExpression } from 'leaflet/src/geo/LatLngBounds';
|
import { LatLngBoundsExpression } from 'leaflet/src/geo/LatLngBounds';
|
||||||
import { LayerOptions } from '@/Components/Map/LayerOptions';
|
import { LayerOptions } from '@/Components/Map/LayerOptions';
|
||||||
|
import TableKeywords from '@/Components/TableKeywords.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
licenses: {
|
licenses: {
|
||||||
|
@ -109,6 +111,11 @@ if (Object.keys(mainService.dataset).length == 0) {
|
||||||
time_absolut: undefined,
|
time_absolut: undefined,
|
||||||
},
|
},
|
||||||
// errors: undefined,
|
// errors: undefined,
|
||||||
|
subjects: [
|
||||||
|
{ value: '', type: 'uncontrolled', language: language.value },
|
||||||
|
{ value: '', type: 'uncontrolled', language: language.value },
|
||||||
|
{ value: '', type: 'uncontrolled', language: language.value },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
// mainService.setDataset(dataset, language);
|
// mainService.setDataset(dataset, language);
|
||||||
} else {
|
} else {
|
||||||
|
@ -129,6 +136,7 @@ if (Object.keys(mainService.dataset).length == 0) {
|
||||||
project_id: mainService.dataset.project_id,
|
project_id: mainService.dataset.project_id,
|
||||||
embargo_date: mainService.dataset.embargo_date,
|
embargo_date: mainService.dataset.embargo_date,
|
||||||
coverage: mainService.dataset.coverage,
|
coverage: mainService.dataset.coverage,
|
||||||
|
subjects: mainService.dataset.subjects,
|
||||||
};
|
};
|
||||||
for (let index in mainService.dataset.titles) {
|
for (let index in mainService.dataset.titles) {
|
||||||
let title: Title = mainService.dataset.titles[index];
|
let title: Title = mainService.dataset.titles[index];
|
||||||
|
@ -289,6 +297,21 @@ const onMapInitialized = (newItem) => {
|
||||||
// notify({ type: 'info', text: message });
|
// notify({ type: 'info', text: message });
|
||||||
console.log(newItem);
|
console.log(newItem);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
adds a new Keyword
|
||||||
|
*/
|
||||||
|
const addKeyword = () => {
|
||||||
|
let newSubject: Subject = { value: 'test', language: '', type: 'uncontrolled' };
|
||||||
|
//this.dataset.files.push(uploadedFiles[i]);
|
||||||
|
form.subjects.push(newSubject);
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
Removes a selected keyword
|
||||||
|
*/
|
||||||
|
// const removeKeyword = (key) => {
|
||||||
|
// form.subjects.splice(key, 1);
|
||||||
|
// };
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -947,12 +970,7 @@ const onMapInitialized = (newItem) => {
|
||||||
:class="{ 'text-red-400': form.errors['coverage.depth_min'] }"
|
:class="{ 'text-red-400': form.errors['coverage.depth_min'] }"
|
||||||
class="w-full mx-2 flex-1"
|
class="w-full mx-2 flex-1"
|
||||||
>
|
>
|
||||||
<FormControl
|
<FormControl required v-model="form.coverage.depth_min" type="text" placeholder="[enter depth_min]">
|
||||||
required
|
|
||||||
v-model="form.coverage.depth_min"
|
|
||||||
type="text"
|
|
||||||
placeholder="[enter depth_min]"
|
|
||||||
>
|
|
||||||
<div class="text-red-400 text-sm" v-if="Array.isArray(form.errors['coverage.depth_min'])">
|
<div class="text-red-400 text-sm" v-if="Array.isArray(form.errors['coverage.depth_min'])">
|
||||||
{{ form.errors['coverage.depth_min'].join(', ') }}
|
{{ form.errors['coverage.depth_min'].join(', ') }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -964,12 +982,7 @@ const onMapInitialized = (newItem) => {
|
||||||
:class="{ 'text-red-400': form.errors['coverage.depth_max'] }"
|
:class="{ 'text-red-400': form.errors['coverage.depth_max'] }"
|
||||||
class="w-full mx-2 flex-1"
|
class="w-full mx-2 flex-1"
|
||||||
>
|
>
|
||||||
<FormControl
|
<FormControl required v-model="form.coverage.depth_max" type="text" placeholder="[enter depth_max]">
|
||||||
required
|
|
||||||
v-model="form.coverage.depth_max"
|
|
||||||
type="text"
|
|
||||||
placeholder="[enter depth_max]"
|
|
||||||
>
|
|
||||||
<div class="text-red-400 text-sm" v-if="Array.isArray(form.errors['coverage.depth_max'])">
|
<div class="text-red-400 text-sm" v-if="Array.isArray(form.errors['coverage.depth_max'])">
|
||||||
{{ form.errors['coverage.depth_max'].join(', ') }}
|
{{ form.errors['coverage.depth_max'].join(', ') }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -977,6 +990,23 @@ const onMapInitialized = (newItem) => {
|
||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
</div>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
|
|
||||||
|
<CardBox
|
||||||
|
class="mb-6 shadow"
|
||||||
|
has-table
|
||||||
|
title="Dataset Keywords"
|
||||||
|
:icon="mdiEarthPlus"
|
||||||
|
:header-icon="mdiPlusCircle"
|
||||||
|
v-on:header-icon-click="addKeyword"
|
||||||
|
>
|
||||||
|
<!-- <ul>
|
||||||
|
<li v-for="(subject, index) in form.subjects" :key="index">
|
||||||
|
{{ subject.value }} <BaseButton color="danger" :icon="mdiTrashCan" small @click.prevent="removeKeyword(index)" />
|
||||||
|
</li>
|
||||||
|
</ul> -->
|
||||||
|
<TableKeywords :keywords="form.subjects" :errors="form.errors" v-if="form.subjects.length > 0" />
|
||||||
|
|
||||||
|
</CardBox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="formStep == 4">
|
<div v-if="formStep == 4">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user