feat: update API controllers, validations, and Vue components
All checks were successful
CI / container-job (push) Successful in 49s
All checks were successful
CI / container-job (push) Successful in 49s
- Modified Api/Authors.Controller.ts to use only personal types and sort by dataset_count. - Completely rewritten AvatarController.ts. - Added new Api/CollectionsController.ts for querying collections and collection_roles. - Modified Api/DatasetController.ts to preload titles, identifier and order by server_date_published. - Modified FileController.ts to serve files from /storage/app/data/ instead of /storage/app/public. - Added new Api/UserController for requesting submitters (getSubmitters). - Improved OaiController.ts with performant DB queries for better ResumptionToken handling. - Modified Submitter/DatasetController.ts by adding a categorize method for library classification. - Rewritten ResumptionToken.ts. - Improved TokenWorkerService.ts to utilize browser fingerprint. - Edited dataset.ts by adding the doiIdentifier property. - Enhanced person.ts to improve the fullName property. - Completely rewritten AsideMenuItem.vue component. - Updated CarBoxClient.vue to use TypeScript. - Added new CardBoxDataset.vue for displaying recent datasets on the dashboard. - Completely rewritten TableSampleClients.vue for the dashboard. - Completely rewritten UserAvatar.vue. - Made small layout changes in Dashboard.vue. - Added new Category.vue for browsing scientific collections. - Adapted the pinia store in main.ts. - Added additional routes in start/routes.ts and start/api/routes.ts. - Improved referenceValidation.ts for better ISBN existence checking. - NPM dependency updates.
This commit is contained in:
parent
36cd7a757b
commit
b540547e4c
34 changed files with 1757 additions and 1018 deletions
|
@ -2,7 +2,6 @@
|
|||
import { Head } from '@inertiajs/vue3';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { MainService } from '@/Stores/main';
|
||||
// import { Inertia } from '@inertiajs/inertia';
|
||||
import {
|
||||
mdiAccountMultiple,
|
||||
mdiDatabaseOutline,
|
||||
|
@ -13,21 +12,18 @@ import {
|
|||
mdiGithub,
|
||||
mdiChartPie,
|
||||
} from '@mdi/js';
|
||||
// import { containerMaxW } from '@/config.js'; // "xl:max-w-6xl xl:mx-auto"
|
||||
// import * as chartConfig from '@/Components/Charts/chart.config.js';
|
||||
import LineChart from '@/Components/Charts/LineChart.vue';
|
||||
import UserCard from '@/Components/unused/UserCard.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import CardBoxWidget from '@/Components/CardBoxWidget.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import TableSampleClients from '@/Components/TableSampleClients.vue';
|
||||
import NotificationBar from '@/Components/NotificationBar.vue';
|
||||
// import NotificationBar from '@/Components/NotificationBar.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import CardBoxTransaction from '@/Components/CardBoxTransaction.vue';
|
||||
import CardBoxClient from '@/Components/CardBoxClient.vue';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import SectionBannerStarOnGitHub from '@/Components/SectionBannerStarOnGitea.vue';
|
||||
import CardBoxDataset from '@/Components/CardBoxDataset.vue';
|
||||
const mainService = MainService()
|
||||
|
||||
// const chartData = ref();
|
||||
|
@ -37,36 +33,32 @@ const fillChartData = async () => {
|
|||
// chartData.value = mainService.graphData;
|
||||
};
|
||||
const chartData = computed(() => mainService.graphData);
|
||||
onMounted(async () => {
|
||||
await mainService.fetchChartData("2022");
|
||||
});
|
||||
;
|
||||
/* Fetch sample data */
|
||||
mainService.fetch('clients');
|
||||
mainService.fetch('history');
|
||||
// onMounted(async () => {
|
||||
// await mainService.fetchChartData("2022");
|
||||
// });
|
||||
|
||||
mainService.fetchApi('authors');
|
||||
mainService.fetchApi('datasets');
|
||||
// mainService.fetch('clients');
|
||||
// mainService.fetch('history');
|
||||
|
||||
// mainService.fetchApi('authors');
|
||||
// mainService.fetchApi('datasets');
|
||||
|
||||
// const clientBarItems = computed(() => mainService.clients.slice(0, 4));
|
||||
const transactionBarItems = computed(() => mainService.history);
|
||||
// const transactionBarItems = computed(() => mainService.history);
|
||||
|
||||
const authorBarItems = computed(() => mainService.authors.slice(0, 4));
|
||||
const authorBarItems = computed(() => mainService.authors.slice(0, 5));
|
||||
const authors = computed(() => mainService.authors);
|
||||
const datasets = computed(() => mainService.datasets);
|
||||
// const props = defineProps({
|
||||
// user: {
|
||||
// type: Object,
|
||||
// default: () => ({}),
|
||||
// }
|
||||
// });
|
||||
const datasetBarItems = computed(() => mainService.datasets.slice(0, 5));
|
||||
// let test = datasets.value;
|
||||
// console.log(test);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated :showAsideMenu="false">
|
||||
<Head title="Dashboard" />
|
||||
|
||||
<!-- <section class="p-6" v-bind:class="containerMaxW"> -->
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton v-bind:icon="mdiChartTimelineVariant" title="Overview" main>
|
||||
<BaseButton
|
||||
|
@ -97,16 +89,13 @@ const datasets = computed(() => mainService.datasets);
|
|||
:number="datasets.length"
|
||||
label="Publications"
|
||||
/>
|
||||
<!-- <CardBoxWidget trend="193" trend-type="info" color="text-blue-500" :icon="mdiCartOutline" :number="datasets.length"
|
||||
prefix="$" label="Publications" /> -->
|
||||
<CardBoxWidget
|
||||
trend="Overflow"
|
||||
trend-type="alert"
|
||||
color="text-red-500"
|
||||
trend="+25%"
|
||||
trend-type="up"
|
||||
color="text-purple-500"
|
||||
:icon="mdiChartTimelineVariant"
|
||||
:number="256"
|
||||
suffix="%"
|
||||
label="Performance"
|
||||
:number="52"
|
||||
label="Citations"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -118,25 +107,19 @@ const datasets = computed(() => mainService.datasets);
|
|||
:name="client.name"
|
||||
:email="client.email"
|
||||
:date="client.created_at"
|
||||
:text="client.datasetCount"
|
||||
:text="client.identifier_orcid"
|
||||
:count="client.dataset_count"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<CardBoxTransaction
|
||||
v-for="(transaction, index) in transactionBarItems"
|
||||
<CardBoxDataset
|
||||
v-for="(dataset, index) in datasetBarItems"
|
||||
:key="index"
|
||||
:amount="transaction.amount"
|
||||
:date="transaction.date"
|
||||
:business="transaction.business"
|
||||
:type="transaction.type"
|
||||
:name="transaction.name"
|
||||
:account="transaction.account"
|
||||
:dataset="dataset"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UserCard />
|
||||
|
||||
<SectionBannerStarOnGitHub />
|
||||
|
||||
<SectionTitleLineWithButton :icon="mdiChartPie" title="Trends overview: Publications per month" />
|
||||
|
@ -146,33 +129,13 @@ const datasets = computed(() => mainService.datasets);
|
|||
</div>
|
||||
</CardBox>
|
||||
|
||||
<SectionTitleLineWithButton :icon="mdiAccountMultiple" title="Submitters (to do)" />
|
||||
<SectionTitleLineWithButton :icon="mdiAccountMultiple" title="Submitters" />
|
||||
|
||||
<NotificationBar color="info" :icon="mdiMonitorCellphone"> <b>Responsive table.</b> Collapses on mobile </NotificationBar>
|
||||
<!-- <NotificationBar color="info" :icon="mdiMonitorCellphone"> <b>Responsive table.</b> Collapses on mobile </NotificationBar> -->
|
||||
|
||||
<CardBox :icon="mdiMonitorCellphone" title="Responsive table" has-table>
|
||||
<TableSampleClients />
|
||||
</CardBox>
|
||||
|
||||
<!-- <CardBox>
|
||||
<p class="mb-3 text-gray-500 dark:text-gray-400">
|
||||
Discover the power of Tethys, the cutting-edge web backend solution that revolutionizes the way you handle research
|
||||
data. At the heart of Tethys lies our meticulously developed research data repository, which leverages state-of-the-art
|
||||
CI/CD techniques to deliver a seamless and efficient experience.
|
||||
</p>
|
||||
<p class="mb-3 text-gray-500 dark:text-gray-400">
|
||||
CI/CD, or Continuous Integration and Continuous Deployment, is a modern software development approach that ensures your
|
||||
code undergoes automated testing, continuous integration, and frequent deployment. By embracing CI/CD techniques, we
|
||||
ensure that every code change in our research data repository is thoroughly validated, enhancing reliability and
|
||||
accelerating development cycles.
|
||||
</p>
|
||||
<p class="mb-3 text-gray-500 dark:text-gray-400">
|
||||
With Tethys, you can say goodbye to the complexities of manual deployments and embrace a streamlined process that
|
||||
eliminates errors and minimizes downtime. Our CI/CD pipeline automatically verifies each code commit, runs comprehensive
|
||||
tests, and deploys the repository seamlessly, ensuring that your research data is always up-to-date and accessible.
|
||||
</p>
|
||||
</CardBox> -->
|
||||
</SectionMain>
|
||||
<!-- </section> -->
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
|
|
|
@ -26,17 +26,6 @@ const errors: Ref<any> = computed(() => {
|
|||
return usePage().props.errors;
|
||||
});
|
||||
|
||||
// const form = useForm({
|
||||
// preferred_reviewer: '',
|
||||
// preferred_reviewer_email: '',
|
||||
// preferation: 'yes_preferation',
|
||||
|
||||
// // preferation: '',
|
||||
// // isPreferationRequired: false,
|
||||
// });
|
||||
|
||||
// const isPreferationRequired = computed(() => form.preferation === 'yes_preferation');
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
// Notification.showInfo(`doi implementation is in developement. Create DOI for dataset ${props.dataset.publish_id} later on`);
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { ref, Ref } from 'vue';
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import { mdiChartTimelineVariant, mdiGithub } from '@mdi/js';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import { MapOptions } from '@/Components/Map/MapOptions';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
// import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import SearchMap from '@/Components/Map/SearchMap.vue';
|
||||
import { OpensearchDocument } from '@/Dataset';
|
||||
|
||||
|
@ -48,14 +48,15 @@ const mapOptions: MapOptions = {
|
|||
|
||||
<template>
|
||||
<LayoutAuthenticated :showAsideMenu="false">
|
||||
|
||||
<Head title="Map" />
|
||||
|
||||
<!-- <section class="p-6" v-bind:class="containerMaxW"> -->
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton v-bind:icon="mdiChartTimelineVariant" title="Tethys Map" main>
|
||||
<!-- <BaseButton href="https://gitea.geologie.ac.at/geolba/tethys" target="_blank" :icon="mdiGithub"
|
||||
label="Star on Gitea" color="contrast" rounded-full small /> -->
|
||||
<BaseButton :route-name="stardust.route('app.login.show')" label="Login" color="white" rounded-full small />
|
||||
<BaseButton href="https://gitea.geosphere.at/geolba/tethys" target="_blank" :icon="mdiGithub"
|
||||
label="Star on GeoSPhere Forgejo" color="contrast" rounded-full small />
|
||||
<!-- <BaseButton :route-name="stardust.route('app.login.show')" label="Login" color="white" rounded-full small /> -->
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<!-- <SectionBannerStarOnGitea /> -->
|
||||
|
@ -80,19 +81,20 @@ const mapOptions: MapOptions = {
|
|||
<div v-for="author in dataset.author" :key="author" class="mb-1">{{ author }}</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">
|
||||
<span
|
||||
class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">
|
||||
{{ dataset.year }}
|
||||
</span>
|
||||
<span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">
|
||||
<span
|
||||
class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">
|
||||
{{ dataset.language }}
|
||||
</span>
|
||||
</div>
|
||||
<p>
|
||||
<span class="label"><i class="fas fa-file"></i> {{ dataset.doctype }}</span>
|
||||
<!-- <span>Licence: {{ document.licence }}</span> -->
|
||||
<span v-if="openAccessLicences.includes(dataset.licence)" class="label titlecase"
|
||||
><i class="fas fa-lock-open"></i> Open Access</span
|
||||
>
|
||||
<span v-if="openAccessLicences.includes(dataset.licence)" class="label titlecase"><i
|
||||
class="fas fa-lock-open"></i> Open Access</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,91 +1,343 @@
|
|||
<template>
|
||||
<div class="flex flex-col h-screen p-4 bg-gray-100">
|
||||
<header class="flex justify-between items-center mb-4">
|
||||
<h1 class="text-xl font-bold">SKOS Browser</h1>
|
||||
<div class="flex space-x-2">
|
||||
<button @click="updateApp" title="Update the application">
|
||||
<!-- <img src="/Resources/Images/refresh.png" alt="Update" class="w-4 h-4" /> -->
|
||||
</button>
|
||||
<button @click="showInfo" title="Info">
|
||||
<!-- <img src="/Resources/Images/info.png" alt="Info" class="w-4 h-4" /> -->
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<LayoutAuthenticated>
|
||||
|
||||
<div class="bg-white shadow-md rounded-lg p-4 mb-6">
|
||||
<h2 class="text-lg font-semibold">GBA-Thesaurus</h2>
|
||||
<label class="block text-sm font-medium">Aktueller Endpoint:</label>
|
||||
<!-- <TreeView :items="endpoints" @select="onEndpointSelected" /> -->
|
||||
</div>
|
||||
<Head title="Profile"></Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiLibraryShelves" title="Library Classification" main>
|
||||
<div class="bg-lime-100 shadow rounded-lg p-6 mb-6 flex items-center justify-between">
|
||||
<div>
|
||||
<label for="role-select" class="block text-lg font-medium text-gray-700 mb-1">
|
||||
Select Classification Role <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select id="role-select" v-model="selectedCollectionRole"
|
||||
class="w-full border border-gray-300 rounded-md p-2 text-gray-700 focus:ring-2 focus:ring-indigo-500"
|
||||
required>
|
||||
<!-- <option value="" disabled selected>Please select a role</option> -->
|
||||
<option v-for="collRole in collectionRoles" :key="collRole.id" :value="collRole">
|
||||
{{ collRole.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ml-4 hidden md:block">
|
||||
<span class="text-sm text-gray-600 italic">* required</span>
|
||||
</div>
|
||||
</div>
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
|
||||
<!-- Available TopLevel Collections -->
|
||||
<CardBox class="mb-4 rounded-lg p-4">
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-slate-400 mb-2">Available Toplevel-Collections
|
||||
<span v-if="selectedCollectionRole && !selectedToplevelCollection"
|
||||
class="text-sm text-red-500 italic">(click to
|
||||
select)</span>
|
||||
</h2>
|
||||
<ul class="flex flex-wrap gap-2">
|
||||
<li v-for="col in collections" :key="col.id" :class="{
|
||||
'cursor-pointer p-2 border border-gray-200 rounded hover:bg-sky-50 text-sky-700 text-sm': true,
|
||||
'bg-sky-100 border-sky-500': selectedToplevelCollection && selectedToplevelCollection.id === col.id
|
||||
}" @click="onToplevelCollectionSelected(col)">
|
||||
{{ `${col.name} (${col.number})` }}
|
||||
</li>
|
||||
<li v-if="collections.length === 0" class="text-gray-800 dark:text-slate-400">
|
||||
No collections available.
|
||||
</li>
|
||||
</ul>
|
||||
</CardBox>
|
||||
|
||||
<!-- Collections Listing -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
||||
|
||||
<!-- Broader Collection (Parent) -->
|
||||
<CardBox v-if="selectedCollection" class="rounded-lg p-4" has-form-data>
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-slate-400 mb-2">Broader Collection</h2>
|
||||
<ul class="flex flex-wrap gap-2 max-h-60 overflow-y-auto">
|
||||
<li v-for="parent in broaderCollections" :key="parent.id"
|
||||
class="cursor-pointer p-2 border border-gray-200 rounded bg-green-50 text-green-700 text-sm hover:bg-green-100 hover:underline"
|
||||
@click="onCollectionSelected(parent)" title="Click to select this collection">
|
||||
{{ `${parent.name} (${parent.number})` }}
|
||||
</li>
|
||||
<li v-if="broaderCollections.length === 0" class="text-gray-500 text-sm">
|
||||
No broader collections available.
|
||||
</li>
|
||||
</ul>
|
||||
</CardBox>
|
||||
|
||||
<!-- Selected Collection Details -->
|
||||
<CardBox v-if="selectedCollection" class="rounded-lg p-4" has-form-data>
|
||||
<h3 class="text-xl font-bold text-gray-800 dark:text-slate-400 mb-2">Selected Collection</h3>
|
||||
<p
|
||||
class="cursor-pointer p-2 border border-gray-200 rounded bg-green-50 text-green-700 text-sm hover:bg-green-100">
|
||||
{{ `${selectedCollection.name} (${selectedCollection.number})` }}
|
||||
</p>
|
||||
</CardBox>
|
||||
|
||||
<!-- Narrower Collections (Children) -->
|
||||
<CardBox v-if="selectedCollection" class="rounded-lg p-4" has-form-data>
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-slate-400 mb-2">Narrower Collections</h2>
|
||||
<!-- <ul class="flex flex-wrap gap-2 max-h-60 overflow-y-auto">
|
||||
<li v-for="child in narrowerCollections" :key="child.id"
|
||||
class="cursor-pointer p-2 border border-gray-200 rounded bg-green-50 text-green-700 text-sm hover:bg-green-100 hover:underline"
|
||||
@click="onCollectionSelected(child)">
|
||||
{{ `${child.name} (${child.number})` }}
|
||||
</li>
|
||||
<li v-if="narrowerCollections.length === 0" class="text-gray-500 text-sm">
|
||||
No sub-collections available.
|
||||
</li>
|
||||
</ul> -->
|
||||
<draggable v-if="narrowerCollections.length > 0" v-model="narrowerCollections"
|
||||
:group="{ name: 'collections' }" tag="ul" class="flex flex-wrap gap-2 max-h-60 overflow-y-auto">
|
||||
<template #item="{ element: child }">
|
||||
<li :key="child.id"
|
||||
class="cursor-pointer p-2 border border-gray-200 rounded bg-green-50 text-green-700 text-sm hover:bg-green-100 hover:underline"
|
||||
@click="onCollectionSelected(child)">
|
||||
{{ `${child.name} (${child.number})` }}
|
||||
</li>
|
||||
</template>
|
||||
</draggable>
|
||||
<ul v-else class="flex flex-wrap gap-2 max-h-60 overflow-y-auto">
|
||||
<li class="text-gray-500 text-sm">
|
||||
No sub-collections available.
|
||||
</li>
|
||||
</ul>
|
||||
</CardBox>
|
||||
|
||||
<div class="bg-white shadow-md rounded-lg p-4">
|
||||
<h2 class="text-lg font-semibold">Konzept-Suche</h2>
|
||||
<!-- <Autocomplete v-model="selectedConcept" :items="concepts" placeholder="Search for a concept" @change="onConceptSelected" /> -->
|
||||
<div class="mt-4">
|
||||
<h3 class="text-md font-medium">Ausgewähltes Konzept</h3>
|
||||
<p>{{ selectedConcept.title }}</p>
|
||||
<a :href="selectedConcept.uri" target="_blank" class="text-blue-500">URI</a>
|
||||
<textarea
|
||||
v-model="selectedConcept.description"
|
||||
class="mt-2 w-full h-24 border rounded"
|
||||
placeholder="Description"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<h3 class="text-md font-medium">Untergeordnete Konzepte</h3>
|
||||
<!-- <LinkLabelList :items="narrowerConcepts" /> -->
|
||||
|
||||
<div class="mb-4 rounded-lg">
|
||||
<div v-if="selectedCollection" class="bg-gray-100 shadow rounded-lg p-6 mb-6">
|
||||
<p class="mb-4 text-gray-700">Please drag your collections here to classify your previously created
|
||||
dataset
|
||||
according to library classification standards.</p>
|
||||
<draggable v-model="dropCollections" :group="{ name: 'collections' }"
|
||||
class="min-h-36 border-dashed border-2 border-gray-400 p-4 text-sm flex flex-wrap gap-2 max-h-60 overflow-y-auto"
|
||||
tag="ul">
|
||||
<template #item="{ element }">
|
||||
<div :key="element.id"
|
||||
class="p-2 m-1 bg-sky-200 text-sky-800 rounded flex items-center gap-2 h-7">
|
||||
<span>{{ element.name }} ({{ element.number }})</span>
|
||||
<button
|
||||
@click="dropCollections = dropCollections.filter(item => item.id !== element.id)"
|
||||
class="hover:text-sky-600 flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<h3 class="text-md font-medium">Übergeordnete Konzepte</h3>
|
||||
<!-- <LinkLabelList :items="broaderConcepts" /> -->
|
||||
|
||||
<div class="p-6 border-t border-gray-100 dark:border-slate-800">
|
||||
<BaseButtons>
|
||||
<BaseButton @click.stop="syncDatasetCollections" label="Save" color="info" small
|
||||
:disabled="isSaveDisabled" :style="{ opacity: isSaveDisabled ? 0.5 : 1 }">
|
||||
</BaseButton>
|
||||
</BaseButtons>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<h3 class="text-md font-medium">Verwandte Konzepte</h3>
|
||||
<!-- <LinkLabelList :items="relatedConcepts" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import TreeView from './TreeView.vue'; // Assuming you have a TreeView component
|
||||
// import Autocomplete from './Autocomplete.vue'; // Assuming you have an Autocomplete component
|
||||
// import LinkLabelList from './LinkLabelList.vue'; // Assuming you have a LinkLabelList component
|
||||
<script lang="ts" setup>
|
||||
import { ref, Ref, watch, computed } from 'vue';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import axios from 'axios';
|
||||
import { mdiLibraryShelves } from '@mdi/js';
|
||||
import draggable from 'vuedraggable';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
// TreeView,
|
||||
// Autocomplete,
|
||||
// LinkLabelList,
|
||||
interface CollectionRole {
|
||||
id: number;
|
||||
name: string;
|
||||
collections?: any[];
|
||||
}
|
||||
|
||||
interface Collection {
|
||||
id: number;
|
||||
name: string;
|
||||
number: string;
|
||||
parent_id?: number | null;
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
collectionRoles: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
endpoints: [], // This should be populated with your data
|
||||
concepts: [], // This should be populated with your data
|
||||
selectedConcept: {},
|
||||
narrowerConcepts: [], // Populate with data
|
||||
broaderConcepts: [], // Populate with data
|
||||
relatedConcepts: [], // Populate with data
|
||||
};
|
||||
dataset: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
methods: {
|
||||
updateApp() {
|
||||
// Handle app update logic
|
||||
},
|
||||
showInfo() {
|
||||
// Handle showing information
|
||||
},
|
||||
onEndpointSelected(endpoint) {
|
||||
// Handle endpoint selection
|
||||
},
|
||||
onConceptSelected(concept) {
|
||||
this.selectedConcept = concept;
|
||||
// Handle concept selection logic, e.g., fetching related concepts
|
||||
},
|
||||
relatedCollections: Array<Collection>
|
||||
});
|
||||
|
||||
const collectionRoles: Ref<CollectionRole[]> = ref(props.collectionRoles as CollectionRole[]);
|
||||
const collections: Ref<Collection[]> = ref<Collection[]>([]);
|
||||
const selectedCollectionRole = ref<CollectionRole | null>(null);
|
||||
const selectedToplevelCollection = ref<Collection | null>(null);
|
||||
const selectedCollection = ref<Collection | null>(null);
|
||||
const narrowerCollections = ref<Collection[]>([]);
|
||||
const broaderCollections = ref<Collection[]>([]);
|
||||
|
||||
|
||||
// const onCollectionRoleSelected = (event: Event) => {
|
||||
// const target = event.target as HTMLSelectElement;
|
||||
// const roleId = Number(target.value);
|
||||
// selectedCollectionRole.value =
|
||||
// collectionRoles.value.find((role: CollectionRole) => role.id === roleId) || null;
|
||||
|
||||
// // Clear any previously selected collection or related data
|
||||
// selectedCollection.value = null;
|
||||
// narrowerCollections.value = [];
|
||||
// broaderCollections.value = [];
|
||||
|
||||
// // fetchTopLevelCollections(roleId);
|
||||
// collections.value = selectedCollectionRole.value?.collections || []
|
||||
// };
|
||||
|
||||
// New reactive array to hold dropped collections for the dataset
|
||||
const dropCollections: Ref<Collection[]> = ref([]);
|
||||
|
||||
// If there are related collections passed in, fill dropCollections with these.
|
||||
if (props.relatedCollections && props.relatedCollections.length > 0) {
|
||||
dropCollections.value = props.relatedCollections;
|
||||
}
|
||||
|
||||
// Add a computed property for the disabled state based on dropCollections length
|
||||
const isSaveDisabled = computed(() => dropCollections.value.length === 0);
|
||||
|
||||
// If the collectionRoles prop might load asynchronously (or change), you can watch for it:
|
||||
watch(
|
||||
() => props.collectionRoles as CollectionRole[],
|
||||
(newCollectionRoles: CollectionRole[]) => {
|
||||
collectionRoles.value = newCollectionRoles;
|
||||
// Preselect the role with name "ccs" if it exists
|
||||
const found: CollectionRole | undefined = collectionRoles.value.find(
|
||||
role => role.name.toLowerCase() === 'ccs'
|
||||
);
|
||||
if (found?.name === 'ccs') {
|
||||
selectedCollectionRole.value = found;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
// Watch for changes in selectedCollectionRole and update related collections state
|
||||
watch(
|
||||
() => selectedCollectionRole.value as CollectionRole,
|
||||
(newSelectedCollectionRole: CollectionRole | null) => {
|
||||
if (newSelectedCollectionRole != null) {
|
||||
collections.value = newSelectedCollectionRole.collections || []
|
||||
} else {
|
||||
selectedToplevelCollection.value = null;
|
||||
selectedCollection.value = null;
|
||||
collections.value = []
|
||||
}
|
||||
// Reset dependent variables when the role changes
|
||||
selectedCollection.value = null
|
||||
narrowerCollections.value = []
|
||||
broaderCollections.value = []
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// Watch for changes in dropCollections
|
||||
watch(
|
||||
() => dropCollections.value,
|
||||
() => {
|
||||
if (selectedCollection.value) {
|
||||
fetchCollections(selectedCollection.value.id);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
const onToplevelCollectionSelected = (collection: Collection) => {
|
||||
selectedToplevelCollection.value = collection;
|
||||
selectedCollection.value = collection;
|
||||
// call the API endpoint to get both.
|
||||
fetchCollections(collection.id)
|
||||
};
|
||||
|
||||
const onCollectionSelected = (collection: Collection) => {
|
||||
selectedCollection.value = collection;
|
||||
// call the API endpoint to get both.
|
||||
fetchCollections(collection.id)
|
||||
};
|
||||
|
||||
// New function to load both narrower and broader concepts using the real API route.
|
||||
const fetchCollections = async (collectionId: number) => {
|
||||
try {
|
||||
const response = await axios.get(`/api/collections/${collectionId}`);
|
||||
const data = response.data;
|
||||
|
||||
// Set narrower concepts with filtered collections
|
||||
narrowerCollections.value = data.narrowerCollections.filter(
|
||||
collection => !dropCollections.value.some(dc => dc.id === collection.id)
|
||||
);
|
||||
// For broader concepts, if present, wrap it in an array (or change your template accordingly)
|
||||
broaderCollections.value = data.broaderCollection;
|
||||
} catch (error) {
|
||||
console.error('Error in fetchConcepts:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const syncDatasetCollections = async () => {
|
||||
try {
|
||||
// Extract the ids from the dropCollections list
|
||||
const collectionIds = dropCollections.value.map(item => item.id);
|
||||
await axios.post('/api/dataset/collections/sync', { collections: collectionIds });
|
||||
// Optionally show a success message or refresh dataset info
|
||||
} catch (error) {
|
||||
console.error('Error syncing dataset collections:', error);
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Add your styles here */
|
||||
.btn-primary {
|
||||
background-color: #4f46e5;
|
||||
color: white;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #4338ca;
|
||||
}
|
||||
|
||||
.btn-primary:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #4f46e5;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: white;
|
||||
color: #374151;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.btn-secondary:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #6366f1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// import { Head, Link, useForm, usePage } from '@inertiajs/inertia-vue3';
|
||||
import { Head, usePage } from '@inertiajs/vue3';
|
||||
import { ComputedRef } from 'vue';
|
||||
import { mdiSquareEditOutline, mdiTrashCan, mdiAlertBoxOutline, mdiLockOpen } from '@mdi/js';
|
||||
import { mdiSquareEditOutline, mdiTrashCan, mdiAlertBoxOutline, mdiLockOpen, mdiLibraryShelves } from '@mdi/js';
|
||||
import { computed } from 'vue';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
|
@ -139,7 +139,10 @@ const formatServerState = (state: string) => {
|
|||
:route-name="stardust.route('dataset.release', [dataset.id])" color="info"
|
||||
:icon="mdiLockOpen" :label="'Release'" small />
|
||||
<BaseButton v-if="can.edit" :route-name="stardust.route('dataset.edit', [dataset.id])"
|
||||
color="info" :icon="mdiSquareEditOutline" :label="'Edit'" small />
|
||||
color="info" :icon="mdiSquareEditOutline" :label="'Edit'" small />
|
||||
<BaseButton v-if="can.edit"
|
||||
:route-name="stardust.route('dataset.categorize', [dataset.id])" color="info"
|
||||
:icon="mdiLibraryShelves" :label="'Library'" small />
|
||||
<BaseButton v-if="can.delete" color="danger"
|
||||
:route-name="stardust.route('dataset.delete', [dataset.id])" :icon="mdiTrashCan"
|
||||
small />
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue