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
|
@ -1,162 +1,143 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, ComputedRef } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
// import { Link } from '@inertiajs/inertia-vue3';
|
||||
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import { mdiMinus, mdiPlus } from '@mdi/js';
|
||||
import { getButtonColor } from '@/colors';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
// import AsideMenuList from '@/Components/AsideMenuList.vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import type { User } from '@/Dataset';
|
||||
import { MenuItem } from '@headlessui/vue';
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
parentItem: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
// isDropdownList: Boolean,
|
||||
});
|
||||
interface MenuItem {
|
||||
href?: string;
|
||||
route?: string;
|
||||
icon?: string;
|
||||
label: string;
|
||||
target?: string;
|
||||
color?: string;
|
||||
children?: MenuItem[];
|
||||
isOpen?: boolean;
|
||||
roles?: string[];
|
||||
}
|
||||
|
||||
const user: ComputedRef<User> = computed(() => {
|
||||
return usePage().props.authUser as User;
|
||||
const props = defineProps<{
|
||||
item: MenuItem;
|
||||
parentItem?: MenuItem;
|
||||
// isDropdownList?: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'menu-click', event: Event, item: MenuItem): void;
|
||||
}>();
|
||||
|
||||
// Retrieve authenticated user from page props
|
||||
const user = computed<User>(() => usePage().props.authUser as User);
|
||||
|
||||
// Check if the menu item has children
|
||||
const hasChildren = computed(() => {
|
||||
return Array.isArray(props.item?.children) && props.item.children.length > 0;
|
||||
});
|
||||
|
||||
const itemRoute = computed(() => (props.item && props.item.route ? stardust.route(props.item.route) : ''));
|
||||
// const isCurrentRoute = computed(() => (props.item && props.item.route ? stardust.isCurrent(props.item.route): false));
|
||||
// const itemHref = computed(() => (props.item && props.item.href ? props.item.href : ''));
|
||||
|
||||
const emit = defineEmits(['menu-click']);
|
||||
// Determine which element to render based on 'href' or 'route'
|
||||
const isComponent = computed(() => {
|
||||
if (props.item.href) {
|
||||
return 'a';
|
||||
}
|
||||
if (props.item.route) {
|
||||
return Link;
|
||||
}
|
||||
return 'div';
|
||||
});
|
||||
|
||||
// Check if any child route is active
|
||||
const isChildActive = computed(() => {
|
||||
if (props.item.children && props.item.children.length > 0) {
|
||||
return props.item.children.some(child => child.route && stardust.isCurrent(child.route));
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Automatically use prop item.isOpen if set from the parent,
|
||||
// or if one of its children is active then force open state.
|
||||
const isOpen = computed(() => {
|
||||
return props.item.isOpen || isChildActive.value;
|
||||
});
|
||||
|
||||
|
||||
const styleService = StyleService();
|
||||
|
||||
const hasColor = computed(() => props.item && props.item.color);
|
||||
|
||||
// const isDropdownOpen = ref(false);
|
||||
|
||||
// const isChildSelected = computed(() => {
|
||||
// if (props.item.children && props.item.children.length > 0) {
|
||||
// return children.value.some(childItem => stardust.isCurrent(childItem.route));
|
||||
// }
|
||||
// return false;
|
||||
|
||||
// const children = computed(() => {
|
||||
// return props.item.children || [];
|
||||
// });
|
||||
|
||||
|
||||
const hasChildren = computed(() => {
|
||||
// props.item.children?.length > 0
|
||||
if (props.item.children && props.item.children.length > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const children = computed(() => {
|
||||
return props.item.children || [];
|
||||
});
|
||||
|
||||
const componentClass = computed(() => [
|
||||
hasChildren ? 'py-3 px-6 text-sm font-semibold' : 'py-3 px-6',
|
||||
hasColor.value ? getButtonColor(props.item.color, false, true) : styleService.asideMenuItemStyle,
|
||||
]);
|
||||
|
||||
|
||||
|
||||
// const toggleDropdown = () => {
|
||||
// // emit('menu-click', event, props.item);
|
||||
// // console.log(props.item);
|
||||
// if (hasChildren.value) {
|
||||
// isDropdownOpen.value = !isDropdownOpen.value;
|
||||
// }
|
||||
// // if (props.parentItem?.hasDropdown.value) {
|
||||
// // props.parentItem.isDropdownActive.value = true;
|
||||
// // }
|
||||
// };
|
||||
|
||||
const menuClick = (event) => {
|
||||
const menuClick = (event: Event) => {
|
||||
emit('menu-click', event, props.item);
|
||||
|
||||
if (hasChildren.value) {
|
||||
// if (isChildSelected.value == false) {
|
||||
// isDropdownOpen.value = !isDropdownOpen.value;
|
||||
props.item.isOpen = !props.item.isOpen;
|
||||
// }
|
||||
// Toggle open state if the menu has children
|
||||
props.item.isOpen = !props.item.isOpen;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// const handleChildSelected = () => {
|
||||
// isChildSelected.value = true;
|
||||
// };
|
||||
|
||||
|
||||
const activeInactiveStyle = computed(() => {
|
||||
const activeStyle = computed(() => {
|
||||
if (props.item.route && stardust.isCurrent(props.item.route)) {
|
||||
// console.log(props.item.route);
|
||||
return styleService.asideMenuItemActiveStyle;
|
||||
return 'text-sky-600 font-bold';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const is = computed(() => {
|
||||
if (props.item.href) {
|
||||
return 'a';
|
||||
}
|
||||
if (props.item.route) {
|
||||
return Link;
|
||||
}
|
||||
|
||||
return 'div';
|
||||
});
|
||||
|
||||
const hasRoles = computed(() => {
|
||||
if (props.item.roles) {
|
||||
return user.value.roles.some(role => props.item.roles.includes(role.name));
|
||||
return user.value.roles.some(role => props.item.roles?.includes(role.name));
|
||||
// return test;
|
||||
}
|
||||
return true
|
||||
});
|
||||
|
||||
// props.routeName && stardust.isCurrent(props.routeName) ? props.activeColor : null
|
||||
</script>
|
||||
|
||||
<!-- :target="props.item.target ?? null" -->
|
||||
<template>
|
||||
<li v-if="hasRoles">
|
||||
<!-- <component :is="itemHref ? 'div' : Link" :href="itemHref ? itemHref : itemRoute" -->
|
||||
<component :is="is" :href="itemRoute ? stardust.route(props.item.route) : props.item.href"
|
||||
class="flex cursor-pointer dark:text-slate-300 dark:hover:text-white menu-item-wrapper" :class="componentClass"
|
||||
@click="menuClick" v-bind:target="props.item.target ?? null">
|
||||
<BaseIcon v-if="item.icon" :path="item.icon" class="flex-none menu-item-icon" :class="activeInactiveStyle"
|
||||
w="w-16" :size="18" />
|
||||
<component :is="isComponent" :href="props.item.href ? props.item.href : itemRoute"
|
||||
class="flex cursor-pointer dark:text-slate-300 dark:hover:text-white menu-item-wrapper"
|
||||
:class="componentClass" @click="menuClick" :target="props.item.target || null">
|
||||
<BaseIcon v-if="props.item.icon" :path="props.item.icon" class="flex-none menu-item-icon"
|
||||
:class="activeStyle" w="w-16" :size="18" />
|
||||
<div class="menu-item-label">
|
||||
<span class="grow text-ellipsis line-clamp-1" :class="activeInactiveStyle">
|
||||
{{ item.label }}
|
||||
<span class="grow text-ellipsis line-clamp-1" :class="[activeStyle]">
|
||||
{{ props.item.label }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- plus icon for expanding sub menu -->
|
||||
<BaseIcon v-if="hasChildren" :path="props.item.isOpen ? mdiMinus : mdiPlus" class="flex-none"
|
||||
:class="[activeInactiveStyle]" w="w-12" />
|
||||
<!-- Display plus or minus icon if there are child items -->
|
||||
<BaseIcon v-if="hasChildren" :path="isOpen ? mdiMinus : mdiPlus" class="flex-none"
|
||||
:class="[activeStyle]" w="w-12" />
|
||||
</component>
|
||||
<!-- Render dropdown -->
|
||||
<div class="menu-item-dropdown"
|
||||
:class="[styleService.asideMenuDropdownStyle, props.item.isOpen ? 'block dark:bg-slate-800/50' : 'hidden']"
|
||||
v-if="hasChildren">
|
||||
:class="[styleService.asideMenuDropdownStyle, isOpen ? 'block dark:bg-slate-800/50' : 'hidden']"
|
||||
v-if="props.item.children && props.item.children.length > 0">
|
||||
<ul>
|
||||
<!-- <li v-for="( child, index ) in children " :key="index">
|
||||
<AsideMenuItem :item="child" :key="index"> </AsideMenuItem>
|
||||
</li> -->
|
||||
<AsideMenuItem v-for="(childItem, index) in children" :key="index" :item="childItem" />
|
||||
|
||||
<AsideMenuItem v-for="(childItem, index) in (props.item.children as any[])" :key="index" :item="childItem"
|
||||
@menu-click="$emit('menu-click', $event, childItem)" />
|
||||
</ul>
|
||||
</div>
|
||||
<!-- <AsideMenuList v-if="hasChildren" :items="item.children"
|
||||
:class="[styleService.asideMenuDropdownStyle, isDropdownOpen ? 'block dark:bg-slate-800/50' : 'hidden']" /> -->
|
||||
|
||||
|
||||
</li>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
@ -167,17 +148,12 @@ const hasRoles = computed(() => {
|
|||
}
|
||||
|
||||
.menu-item-icon {
|
||||
font-size: 2.5rem;
|
||||
/* margin-right: 10px; */
|
||||
font-size: 2.5rem;
|
||||
/* margin-right: 10px; */
|
||||
}
|
||||
|
||||
/* .menu-item-label {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
} */
|
||||
|
||||
.menu-item-dropdown {
|
||||
/* margin-left: 10px; */
|
||||
padding-left: 0.75rem;
|
||||
/* margin-left: 10px; */
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { mdiTrendingDown, mdiTrendingUp, mdiTrendingNeutral } from '@mdi/js';
|
||||
// import { mdiTrendingDown, mdiTrendingUp, mdiTrendingNeutral } from '@mdi/js';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import BaseLevel from '@/Components/BaseLevel.vue';
|
||||
import PillTag from '@/Components/PillTag.vue';
|
||||
|
@ -27,6 +27,10 @@ const props = defineProps({
|
|||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: null,
|
||||
|
@ -42,11 +46,11 @@ const pillType = computed(() => {
|
|||
return props.type;
|
||||
}
|
||||
|
||||
if (props.progress) {
|
||||
if (props.progress >= 60) {
|
||||
if (props.count) {
|
||||
if (props.count >= 20) {
|
||||
return 'success';
|
||||
}
|
||||
if (props.progress >= 40) {
|
||||
if (props.count >= 5) {
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
|
@ -56,17 +60,17 @@ const pillType = computed(() => {
|
|||
return 'info';
|
||||
});
|
||||
|
||||
const pillIcon = computed(() => {
|
||||
return {
|
||||
success: mdiTrendingUp,
|
||||
warning: mdiTrendingNeutral,
|
||||
danger: mdiTrendingDown,
|
||||
info: mdiTrendingNeutral,
|
||||
}[pillType.value];
|
||||
});
|
||||
// const pillIcon = computed(() => {
|
||||
// return {
|
||||
// success: mdiTrendingUp,
|
||||
// warning: mdiTrendingNeutral,
|
||||
// danger: mdiTrendingDown,
|
||||
// info: mdiTrendingNeutral,
|
||||
// }[pillType.value];
|
||||
// });
|
||||
|
||||
const pillText = computed(() => props.text ?? `${props.progress}%`);
|
||||
</script>
|
||||
// const pillText = computed(() => props.text ?? `${props.progress}%`);
|
||||
// </script>
|
||||
|
||||
<template>
|
||||
<CardBox class="mb-6 last:mb-0" hoverable>
|
||||
|
@ -83,7 +87,17 @@ const pillText = computed(() => props.text ?? `${props.progress}%`);
|
|||
</p>
|
||||
</div>
|
||||
</BaseLevel>
|
||||
<PillTag :type="pillType" :text="pillText" small :icon="pillIcon" />
|
||||
<!-- <PillTag :type="pillType" :text="text" small :icon="pillIcon" /> -->
|
||||
|
||||
<div class="text-center md:text-right space-y-2">
|
||||
<p class="text-sm text-gray-500">
|
||||
Count
|
||||
</p>
|
||||
<div>
|
||||
<PillTag :type="pillType" :text="String(count)" small />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</BaseLevel>
|
||||
</CardBox>
|
||||
</template>
|
||||
|
|
107
resources/js/Components/CardBoxDataset.vue
Normal file
107
resources/js/Components/CardBoxDataset.vue
Normal file
|
@ -0,0 +1,107 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, PropType } from 'vue';
|
||||
import { mdiChartTimelineVariant, mdiFileDocumentOutline, mdiFileOutline, mdiDatabase } from '@mdi/js';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import PillTag from '@/Components/PillTag.vue';
|
||||
import IconRounded from '@/Components/IconRounded.vue';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
|
||||
// Extend dayjs to support relative times
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
interface Dataset {
|
||||
account_id: number;
|
||||
created_at: string;
|
||||
creating_corporation: string;
|
||||
editor_id: number;
|
||||
embargo_date: string | null;
|
||||
id: number;
|
||||
language: string;
|
||||
main_abstract: string | null;
|
||||
main_title: string | null;
|
||||
preferred_reviewer: string | null;
|
||||
preferred_reviewer_email: string | null;
|
||||
project_id: number | null;
|
||||
publish_id: number;
|
||||
publisher_name: string;
|
||||
reject_editor_note: string | null;
|
||||
reject_reviewer_note: string | null;
|
||||
remaining_time: number;
|
||||
reviewer_id: number;
|
||||
server_date_modified: string;
|
||||
server_date_published: string;
|
||||
server_state: string;
|
||||
type: string;
|
||||
doi_identifier: string;
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
dataset: {
|
||||
type: Object as PropType<Dataset>,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const icon = computed(() => {
|
||||
switch (props.dataset.type) {
|
||||
case 'analysisdata':
|
||||
return { icon: mdiChartTimelineVariant, type: 'success' };
|
||||
case 'measurementdata':
|
||||
return { icon: mdiFileDocumentOutline, type: 'warning' };
|
||||
case 'monitoring':
|
||||
return { icon: mdiFileOutline, type: 'info' };
|
||||
case 'remotesensing':
|
||||
return { icon: mdiDatabase, type: 'primary' };
|
||||
case 'gis':
|
||||
return { icon: mdiDatabase, type: 'info' };
|
||||
case 'models':
|
||||
return { icon: mdiChartTimelineVariant, type: 'success' };
|
||||
case 'mixedtype':
|
||||
return { icon: mdiFileDocumentOutline, type: 'warning' };
|
||||
case 'vocabulary':
|
||||
return { icon: mdiFileOutline, type: 'info' };
|
||||
default:
|
||||
return { icon: mdiDatabase, type: 'secondary' };
|
||||
}
|
||||
});
|
||||
|
||||
const displayTitle = computed(() => props.dataset.main_title || 'Untitled Dataset');
|
||||
|
||||
const doiLink = computed(() => {
|
||||
return `https://doi.tethys.at/10.24341/tethys.${props.dataset.publish_id}`;
|
||||
});
|
||||
|
||||
const relativeDate = computed(() => {
|
||||
const publishedDate = dayjs(props.dataset.server_date_published);
|
||||
if (publishedDate.isValid()) {
|
||||
return publishedDate.fromNow();
|
||||
}
|
||||
return props.dataset.server_date_published;
|
||||
});
|
||||
|
||||
// const displayBusiness = computed(() => props.dataset.publisher_name);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardBox class="mb-6 last:mb-0" hoverable>
|
||||
<div class="flex items-start justify-between">
|
||||
<IconRounded :icon="icon.icon" :type="icon.type" class="mr-6" />
|
||||
<div class="flex-grow space-y-1 text-left" style="width: 70%;">
|
||||
<h4 class="text-lg truncate" >
|
||||
{{ displayTitle }}
|
||||
</h4>
|
||||
<p class="text-gray-500 dark:text-slate-400">
|
||||
<b>
|
||||
<a :href="doiLink" target="_blank">View Publication</a>
|
||||
</b>
|
||||
• {{ relativeDate }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-end gap-2">
|
||||
<p class="text-sm text-gray-500">{{ props.dataset.type }}</p>
|
||||
<PillTag :type="icon.type" :text="props.dataset.type" small class="inline-flex" />
|
||||
</div>
|
||||
</div>
|
||||
</CardBox>
|
||||
</template>
|
|
@ -1,17 +1,19 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, Ref } from 'vue';
|
||||
import { MainService } from '@/Stores/main';
|
||||
import { StyleService } from '@/Stores/style.service';
|
||||
import { mdiEye, mdiTrashCan } from '@mdi/js';
|
||||
import { mdiEye } 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 UserAvatar from '@/Components/UserAvatar.vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { User } from '@/Stores/main';
|
||||
|
||||
defineProps({
|
||||
checkable: Boolean,
|
||||
checkable: Boolean,
|
||||
});
|
||||
|
||||
const styleService = StyleService();
|
||||
|
@ -19,128 +21,124 @@ const mainService = MainService();
|
|||
const items = computed(() => mainService.clients);
|
||||
|
||||
const isModalActive = ref(false);
|
||||
const isModalDangerActive = ref(false);
|
||||
// const isModalDangerActive = ref(false);
|
||||
const perPage = ref(5);
|
||||
const currentPage = ref(0);
|
||||
const checkedRows = ref([]);
|
||||
const currentClient: Ref<User | null> = ref(null);
|
||||
|
||||
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 = [];
|
||||
|
||||
for (let i = 0; i < numPages.value; i++) {
|
||||
pagesList.push(i);
|
||||
}
|
||||
|
||||
return pagesList;
|
||||
const pagesList = [];
|
||||
for (let i = 0; i < numPages.value; i++) {
|
||||
pagesList.push(i);
|
||||
}
|
||||
return pagesList;
|
||||
});
|
||||
|
||||
const remove = (arr, cb) => {
|
||||
const newArr = [];
|
||||
|
||||
arr.forEach((item) => {
|
||||
if (!cb(item)) {
|
||||
newArr.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return newArr;
|
||||
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);
|
||||
}
|
||||
if (isChecked) {
|
||||
checkedRows.value.push(client);
|
||||
} else {
|
||||
checkedRows.value = remove(checkedRows.value, (row) => row.id === client.id);
|
||||
}
|
||||
};
|
||||
|
||||
const showModal = (client: User) => {
|
||||
currentClient.value = client;
|
||||
isModalActive.value = true;
|
||||
};
|
||||
</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>
|
||||
<CardBoxModal v-model="isModalActive" :title="currentClient ? currentClient.login : ''">
|
||||
<div v-if="currentClient">
|
||||
<p>Login: {{ currentClient.login }}</p>
|
||||
<p>Email: {{ currentClient.email }}</p>
|
||||
<p>Created: {{ currentClient?.created_at ? dayjs(currentClient.created_at).format('MMM D, YYYY h:mm A') : 'N/A' }}
|
||||
</p>
|
||||
</div>
|
||||
</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> -->
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="checkable" />
|
||||
<th />
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>City</th>
|
||||
<th>Progress</th>
|
||||
<th>Created</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="client in itemsPaginated" :key="client.id">
|
||||
<TableCheckboxCell v-if="checkable" @checked="checked($event, client)" />
|
||||
<td class="border-b-0 lg:w-6 before:hidden">
|
||||
<UserAvatar :username="client.name" class="w-24 h-24 mx-auto lg:w-6 lg:h-6" />
|
||||
</td>
|
||||
<td data-label="Name">
|
||||
{{ client.name }}
|
||||
</td>
|
||||
<td data-label="Email">
|
||||
{{ client.email }}
|
||||
</td>
|
||||
<td data-label="City">
|
||||
{{ client.city }}
|
||||
</td>
|
||||
<td data-label="Progress" class="lg:w-32">
|
||||
<progress class="flex w-2/5 self-center lg:w-full" max="100" v-bind:value="client.progress">
|
||||
{{ client.progress }}
|
||||
</progress>
|
||||
</td>
|
||||
<td data-label="Created" class="lg:w-1 whitespace-nowrap">
|
||||
<small class="text-gray-500 dark:text-slate-400" :title="client.created">{{ client.created }}</small>
|
||||
</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="isModalDangerActive = true" />
|
||||
</BaseButtons>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<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>
|
||||
</template>
|
||||
<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.login }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="checkable" />
|
||||
<th />
|
||||
<th>Login</th>
|
||||
<th>Email</th>
|
||||
<th>Created</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="client in itemsPaginated" :key="client.id">
|
||||
<TableCheckboxCell v-if="checkable" @checked="checked($event, client)" />
|
||||
<td class="border-b-0 lg:w-6 before:hidden">
|
||||
<!-- <UserAvatar :username="client.login" :avatar="client.avatar" class="w-24 h-24 mx-auto lg:w-6 lg:h-6" /> -->
|
||||
<div v-if="client.avatar">
|
||||
<UserAvatar :default-url="client.avatar ? '/public' + client.avatar : ''"
|
||||
:username="client.first_name + ' ' + client.last_name" class="w-24 h-24 mx-auto lg:w-6 lg:h-6" />
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else>
|
||||
<UserAvatar :username="client.first_name + ' ' + client.last_name" class="w-24 h-24 mx-auto lg:w-6 lg:h-6" />
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="Login">
|
||||
{{ client.login }}
|
||||
</td>
|
||||
<td data-label="Email">
|
||||
{{ client.email }}
|
||||
</td>
|
||||
<td data-label="Created">
|
||||
<small class="text-gray-500 dark:text-slate-400"
|
||||
:title="client.created_at ? dayjs(client.created_at).format('MMM D, YYYY h:mm A') : 'N/A'">
|
||||
{{ client.created_at ? dayjs(client.created_at).format('MMM D, YYYY h:mm A') : 'N/A' }}
|
||||
</small>
|
||||
</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="showModal(client)" />
|
||||
<!-- <BaseButton color="danger" :icon="mdiTrashCan" small @click="isModalDangerActive = true" /> -->
|
||||
</BaseButtons>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<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>
|
||||
</template>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<script setup>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -22,55 +22,55 @@ const avatar = computed(() => {
|
|||
|
||||
const username = computed(() => props.username);
|
||||
|
||||
const darkenColor = (color) => {
|
||||
const r = parseInt(color.slice(0, 2), 16);
|
||||
const g = parseInt(color.slice(2, 4), 16);
|
||||
const b = parseInt(color.slice(4, 6), 16);
|
||||
// const darkenColor = (color: string) => {
|
||||
// const r = parseInt(color.slice(0, 2), 16);
|
||||
// const g = parseInt(color.slice(2, 4), 16);
|
||||
// const b = parseInt(color.slice(4, 6), 16);
|
||||
|
||||
const darkerR = Math.round(r * 0.6);
|
||||
const darkerG = Math.round(g * 0.6);
|
||||
const darkerB = Math.round(b * 0.6);
|
||||
// const darkerR = Math.round(r * 0.6);
|
||||
// const darkerG = Math.round(g * 0.6);
|
||||
// const darkerB = Math.round(b * 0.6);
|
||||
|
||||
const darkerColor = ((darkerR << 16) + (darkerG << 8) + darkerB).toString(16);
|
||||
// const darkerColor = ((darkerR << 16) + (darkerG << 8) + darkerB).toString(16);
|
||||
|
||||
return darkerColor.padStart(6, '0');
|
||||
};
|
||||
// return darkerColor.padStart(6, '0');
|
||||
// };
|
||||
|
||||
const getColorFromName = (name) => {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
let color = '#';
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const value = (hash >> (i * 8)) & 0xff;
|
||||
color += ('00' + value.toString(16)).substr(-2);
|
||||
}
|
||||
return color.replace('#', '');
|
||||
};
|
||||
// const getColorFromName = (name: string): string => {
|
||||
// let hash = 0;
|
||||
// for (let i = 0; i < name.length; i++) {
|
||||
// hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
||||
// }
|
||||
// let color = '#';
|
||||
// for (let i = 0; i < 3; i++) {
|
||||
// const value = (hash >> (i * 8)) & 0xff;
|
||||
// color += ('00' + value.toString(16)).substr(-2);
|
||||
// }
|
||||
// return color.replace('#', '');
|
||||
// };
|
||||
|
||||
const lightenColor = (hexColor, percent) => {
|
||||
let r = parseInt(hexColor.substring(0, 2), 16);
|
||||
let g = parseInt(hexColor.substring(2, 4), 16);
|
||||
let b = parseInt(hexColor.substring(4, 6), 16);
|
||||
// const lightenColor = (hexColor: string, percent: number): string => {
|
||||
// let r = parseInt(hexColor.substring(0, 2), 16);
|
||||
// let g = parseInt(hexColor.substring(2, 4), 16);
|
||||
// let b = parseInt(hexColor.substring(4, 6), 16);
|
||||
|
||||
r = Math.floor(r * (100 + percent) / 100);
|
||||
g = Math.floor(g * (100 + percent) / 100);
|
||||
b = Math.floor(b * (100 + percent) / 100);
|
||||
// r = Math.floor(r * (100 + percent) / 100);
|
||||
// g = Math.floor(g * (100 + percent) / 100);
|
||||
// b = Math.floor(b * (100 + percent) / 100);
|
||||
|
||||
r = (r < 255) ? r : 255;
|
||||
g = (g < 255) ? g : 255;
|
||||
b = (b < 255) ? b : 255;
|
||||
// r = (r < 255) ? r : 255;
|
||||
// g = (g < 255) ? g : 255;
|
||||
// b = (b < 255) ? b : 255;
|
||||
|
||||
const lighterHex = ((r << 16) | (g << 8) | b).toString(16);
|
||||
// const lighterHex = ((r << 16) | (g << 8) | b).toString(16);
|
||||
|
||||
return lighterHex.padStart(6, '0');
|
||||
};
|
||||
// return lighterHex.padStart(6, '0');
|
||||
// };
|
||||
|
||||
const generateAvatarUrl = (name) => {
|
||||
const originalColor = getColorFromName(name);
|
||||
const backgroundColor = lightenColor(originalColor, 60);
|
||||
const textColor = darkenColor(originalColor);
|
||||
const generateAvatarUrl = (name: string): string => {
|
||||
// const originalColor = getColorFromName(name);
|
||||
// const backgroundColor = lightenColor(originalColor, 60);
|
||||
// const textColor = darkenColor(originalColor);
|
||||
|
||||
const avatarUrl = `/api/avatar?name=${name}&size=50`;
|
||||
return avatarUrl;
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -2,6 +2,56 @@ import { defineStore } from 'pinia';
|
|||
import axios from 'axios';
|
||||
import { Dataset } from '@/Dataset';
|
||||
import menu from '@/menu';
|
||||
// import type Person from '#models/person';
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
login: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
password: string;
|
||||
created_at: DateTime;
|
||||
updatedAt: DateTime;
|
||||
lastLoginAt: DateTime;
|
||||
isActive: boolean;
|
||||
isVerified: boolean;
|
||||
roles: string[];
|
||||
permissions: string[];
|
||||
settings: Record<string, any>;
|
||||
profile: {
|
||||
avatar: string;
|
||||
bio: string;
|
||||
location: string;
|
||||
website: string;
|
||||
social: {
|
||||
twitter: string;
|
||||
facebook: string;
|
||||
linkedin: string;
|
||||
github: string;
|
||||
}
|
||||
};
|
||||
metadata: Record<string, any>;
|
||||
verifyPassword: (plainPassword: string) => Promise<boolean>;
|
||||
}
|
||||
|
||||
interface DateTime {
|
||||
get: (unit: keyof DateTime) => number;
|
||||
getPossibleOffsets: () => DateTime[];
|
||||
toRelativeCalendar: (options?: ToRelativeCalendarOptions) => string | null;
|
||||
toFormat: (format: string) => string;
|
||||
toISO: () => string;
|
||||
toJSON: () => string;
|
||||
toString: () => string;
|
||||
toLocaleString: (options?: Intl.DateTimeFormatOptions) => string;
|
||||
toUTC: () => DateTime;
|
||||
toLocal: () => DateTime;
|
||||
valueOf: () => number;
|
||||
toMillis: () => number;
|
||||
toSeconds: () => number;
|
||||
toUnixInteger: () => number;
|
||||
}
|
||||
|
||||
|
||||
export interface Person {
|
||||
id: number;
|
||||
|
@ -9,10 +59,12 @@ export interface Person {
|
|||
email: string;
|
||||
name_type: string;
|
||||
identifier_orcid: string;
|
||||
datasetCount: string;
|
||||
dataset_count: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface TransactionItem {
|
||||
amount: number;
|
||||
account: string;
|
||||
|
@ -61,7 +113,7 @@ export const MainService = defineStore('main', {
|
|||
isFieldFocusRegistered: false,
|
||||
|
||||
/* Sample data for starting dashboard(commonly used) */
|
||||
clients: [],
|
||||
clients: [] as Array<User>,
|
||||
history: [] as Array<TransactionItem>,
|
||||
|
||||
// api based data
|
||||
|
@ -184,7 +236,7 @@ export const MainService = defineStore('main', {
|
|||
this.totpState = state;
|
||||
},
|
||||
|
||||
async fetchChartData(year: string) {
|
||||
fetchChartData(year: string) {
|
||||
// sampleDataKey= authors or datasets
|
||||
axios
|
||||
.get(`/api/statistic/${year}`)
|
||||
|
|
|
@ -8,6 +8,7 @@ import { createPinia } from 'pinia';
|
|||
import { StyleService } from '@/Stores/style.service';
|
||||
import { LayoutService } from '@/Stores/layout';
|
||||
import { LocaleStore } from '@/Stores/locale';
|
||||
import { MainService } from './Stores/main';
|
||||
import { darkModeKey, styleKey } from '@/config';
|
||||
import type { DefineComponent } from 'vue';
|
||||
import { resolvePageComponent } from '@adonisjs/inertia/helpers';
|
||||
|
@ -80,7 +81,7 @@ const layoutService = LayoutService(pinia);
|
|||
const localeService = LocaleStore(pinia);
|
||||
|
||||
localeService.initializeLocale();
|
||||
// const mainService = MainService(pinia);
|
||||
const mainService = MainService(pinia);
|
||||
// mainService.setUser(user);
|
||||
|
||||
/* App style */
|
||||
|
@ -90,6 +91,12 @@ styleService.setStyle(localStorage[styleKey] ?? 'basic');
|
|||
if ((!localStorage[darkModeKey] && window.matchMedia('(prefers-color-scheme: dark)').matches) || localStorage[darkModeKey] === '1') {
|
||||
styleService.setDarkMode(true);
|
||||
}
|
||||
// mainService.fetch('clients');
|
||||
// mainService.fetch('history');
|
||||
mainService.fetchApi('clients');
|
||||
mainService.fetchApi('authors');
|
||||
mainService.fetchApi('datasets');
|
||||
mainService.fetchChartData("2022");
|
||||
|
||||
/* Collapse mobile aside menu on route change */
|
||||
Inertia.on('navigate', () => {
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
mdiShieldCrownOutline,
|
||||
mdiLicense,
|
||||
mdiFileDocument,
|
||||
mdiLibraryShelves
|
||||
} from '@mdi/js';
|
||||
|
||||
export default [
|
||||
|
@ -111,6 +112,11 @@ export default [
|
|||
icon: mdiPublish,
|
||||
label: 'Create Dataset',
|
||||
},
|
||||
// {
|
||||
// route: 'dataset.categorize',
|
||||
// icon: mdiLibraryShelves,
|
||||
// label: 'Library Classification',
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue