Skip to content
Snippets Groups Projects
Commit ada333f7 authored by Petar Hristov's avatar Petar Hristov :speech_balloon:
Browse files

Merge branch 'Issue/1962-SearchAppUI2' into 'dev'

Issue/1962 search app ui2

See merge request !25
parents d5cb907a caa534a5
No related branches found
No related tags found
2 merge requests!29Release: Sprint/2022 04 :robot:,!25Issue/1962 search app ui2
Pipeline #663505 passed
src/assets/images/Search.png

74.5 KiB

...@@ -36,9 +36,9 @@ export default defineComponent({ ...@@ -36,9 +36,9 @@ export default defineComponent({
async initialize() { async initialize() {
// do initialization stuff (e.g. API calls, element loading, etc.) // do initialization stuff (e.g. API calls, element loading, etc.)
// ... // ...
this.searchStore.retrieveSearchResults();
}, },
}, },
}); });
</script> </script>
<style></style>
import { DummyDataEntry, DummyDataType } from "../types";
export const mainViewData: Array<DummyDataType> = [
{
header: [
{
type: "Project",
value: "SomeExampleProjectName1",
},
],
body: [
{
type: "Created By",
value: "Max Mustermann",
},
{
type: "Parent Project",
value: "None",
},
{
type: "Start Date",
value: "01.01.2020",
},
{
type: "End Date",
value: "01.12.2022",
},
{
type: "PI",
value: "Prof. Müller",
},
{
type: "Grant Id",
value: "DFG 1866",
},
],
},
{
header: [
{
type: "Project",
value: "SomeExampleProjectName2",
},
],
body: [
{
type: "Created By",
value: "Manuel Rosenberger",
},
{
type: "Parent Project",
value: "None",
},
{
type: "Start Date",
value: "17.07.2020",
},
{
type: "End Date",
value: "-",
},
{
type: "PI",
value: "Prof. Müller",
},
{
type: "Grant Id",
value: "DFG 7753",
},
],
},
{
header: [
{
type: "Project",
value: "SomeExampleProjectName3",
},
],
body: [
{
type: "Created By",
value: "Hildegard Beck",
},
{
type: "Parent Project",
value: "SomeExampleProjectName1",
},
{
type: "Start Date",
value: "01.01.2020",
},
{
type: "End Date",
value: "01.12.2022",
},
{
type: "PI",
value: "Prof. Müller",
},
{
type: "Grant Id",
value: "DFG 1867",
},
],
},
{
header: [
{
type: "Resource",
value: "SomeExampleResourceName",
archived: true,
} as DummyDataEntry,
],
body: [
{
type: "Persistent ID",
value: "21.11102/0372a8a6-3f4f-448e-aa75-acb75-acb148fe66d6",
},
{
type: "License",
value: "MIT",
},
{
type: "Usage Rights",
value: "None",
},
{
type: "Application Profile",
value: "Radar",
},
],
},
{
header: [
{
type: "Resource",
value: "Test-Resource",
},
],
body: [
{
type: "Persistent ID",
value: "21.11102/cf39b3e6-e0b7-4415-bc65-acb75-fbbbc35b4b89",
},
{
type: "License",
value: "MIT",
},
{
type: "Usage Rights",
value: "None",
},
{
type: "Application Profile",
value: "Engmeta",
},
],
},
{
header: [
{
type: "Resource",
value: "Epic Project Resource",
archived: true,
} as DummyDataEntry,
],
body: [
{
type: "Persistent ID",
value: "21.11102/2a108fee-d60a-40e8-809a-acb75-8cf5aecb93f9",
},
{
type: "License",
value: "MIT",
},
{
type: "Usage Rights",
value: "None",
},
{
type: "Application Profile",
value: "Radar",
},
],
},
{
header: [
{
type: "Resource",
value: "Epic Project Resource 2",
},
],
body: [
{
type: "Persistent ID",
value: "21.11102/3f1bb053-644b-42c0-8e45-acb75-40193cde1a14",
},
{
type: "License",
value: "MIT",
},
{
type: "Usage Rights",
value: "None",
},
{
type: "Application Profile",
value: "Radar",
},
],
},
{
header: [
{
type: "File",
value: "LabNotes.txt",
},
],
body: [
{
type: "MD1",
value: "xxxxxxx",
},
{
type: "MD2",
value: "yyyyyyyy",
},
{
type: "MD3",
value: "None",
},
{
type: "MD4",
value: "wwwwwww",
},
{
type: "MD5",
value: "yyyyyyyyyyy",
},
{
type: "MD6",
value: "None",
},
{
type: "MD7",
value: "wwwwww",
},
{
type: "MD8",
value: "yyyyyyyyyyyyy",
},
{
type: "MD9",
value: "None",
},
{
type: "MD10",
value: "wwwwwwww",
},
{
type: "MD11",
value: "yyyyyyyyyyy",
},
{
type: "MD12",
value: "None",
},
{
type: "MD13",
value: "wwwwww",
},
{
type: "MD14",
value: "yyyyyyyyy",
},
{
type: "MD15",
value: "None",
},
{
type: "MD16",
value: "wwwwwwww",
},
],
},
{
header: [
{
type: "User",
value: "Max Mustermann",
},
],
body: [
{
type: "Project Member",
value:
"SomeExampleProjectName1, SomeExampleProjectName2, SomeExampleProjectName3",
},
],
},
{
header: [
{
type: "User",
value: "Manuel Rosenberger",
},
],
body: [
{
type: "Project Member",
value: "SomeExampleProjectName2",
},
],
},
{
header: [
{
type: "User",
value: "Hildegard Beck",
},
],
body: [
{
type: "Project Member",
value: "SomeExampleProjectName1, SomeExampleProjectName3",
},
],
},
];
...@@ -9,7 +9,18 @@ export default { ...@@ -9,7 +9,18 @@ export default {
page: { page: {
search: { search: {
title: "Suchseite", title: "Suchseite",
description: "Das ist die @:page.search.title des Coscine UIv2 Apps", search: "Suchen",
buttonSearch: {
Item1: "Eintrag 1",
Item2: "Eintrag 2"
},
emptySearch: "Es wurden keine Treffer gefunden",
endSearchResults: "Alle Ergebnisse werden angezeigt",
allProjects: "Alle Projekte",
allResources: "Alle Resourcen",
}, },
}, },
} as VueI18n.LocaleMessageObject; } as VueI18n.LocaleMessageObject;
...@@ -9,7 +9,18 @@ export default { ...@@ -9,7 +9,18 @@ export default {
page: { page: {
search: { search: {
title: "Search Page", title: "Search Page",
description: "This is the @:page.search.title for the Coscine UIv2 App", search: "Search",
buttonSearch: {
Item1: "Item1",
Item2: "Item2"
},
emptySearch: "No item is found for the given search criteria",
endSearchResults: "Showing all results",
allProjects: "All Projects",
allResources: "All Resources",
}, },
}, },
} as VueI18n.LocaleMessageObject; } as VueI18n.LocaleMessageObject;
\ No newline at end of file
<template> <template>
<div> <div class="search">
<section <CoscineHeadline :headline="$parent.$t('page.search.title')" />
class="container flex flex-col items-center px-5 py-12 mx-auto text-gray-600 body-font md:flex-row"
<b-row id="mainRow">
<!-- Sidebar -->
<Sidebar />
<b-col ref="rightCol" sm="10" align-self="end" style="height: 100%">
<!-- Search Bar Fields -->
<b-row id="searchBarContainer" align-content="center">
<b-col id="searchField" align-self="start" class="pl-0">
<b-form-input
v-model="searchText"
:placeholder="$parent.$t('page.search.search')"
></b-form-input>
</b-col>
<b-col sm="2" id="selectProjCol" align-self="center" class="pl-0">
<b-form-select v-model="selectProjValue">
<template #first>
<b-form-select-option :value="null" disabled
>{{ $parent.$t("page.search.allProjects") }}
</b-form-select-option>
</template>
</b-form-select>
</b-col>
<b-col sm="2" id="selectResCol" align-self="center" class="pl-0">
<b-form-select v-model="selectResValue">
<template #first>
<b-form-select-option :value="null" disabled
>{{ $parent.$t("page.search.allResources") }}
</b-form-select-option>
</template>
</b-form-select>
</b-col>
<b-col sm="0" align-self="center" class="text-right p-0">
<b-button-group>
<b-button id="searchButton" variant="primary">
{{ $parent.$t("page.search.search") }}
</b-button>
<b-dropdown id="searchDropdown" right size="sm" variant="primary">
<b-dropdown-item>{{
$parent.$t("page.search.buttonSearch.Item1")
}}</b-dropdown-item>
<b-dropdown-item>{{
$parent.$t("page.search.buttonSearch.Item2")
}}</b-dropdown-item>
</b-dropdown>
</b-button-group>
</b-col>
</b-row>
<!-- Filter Tags -->
<b-row
id="filterTagsContainer"
align-self="center"
class="mt-2 mb-2 rounded bg-light"
style="min-height: 37px"
>
<b-col align-self="center" class="pl-0">
<b-form-tags
v-model="filterTags"
no-outer-focus
class="border-0 bg-transparent"
> >
<template v-slot="{ tags, removeTag }">
<div> <div>
<CoscineHeadline :headline="$parent.$t('page.search.title')" /> <b-form-tag
<p class="mb-8 leading-relaxed dark:text-white"> v-for="tag in tags"
{{ $parent.$t("page.search.description") }} @remove="removeTag(tag)"
</p> :key="tag"
<img alt="From Coscine Old" src="@/assets/images/Search.png" /> :title="tag"
variant="primary"
pill
class="mr-1"
>{{ tag }}
</b-form-tag>
</div>
</template>
</b-form-tags>
</b-col>
<b-col align-self="center" sm="1" class="text-right">
<b-button id="filterTagsButton" size="sm" variant="light">
<b-icon icon="funnel-fill" />
</b-button>
</b-col>
</b-row>
<!-- Results View -->
<b-row id="resultsViewContainer" class="flex-grow-1">
<b-card style="width: 100%">
<b-skeleton-wrapper :loading="resultsViewLoading">
<template #loading>
<div
v-for="(entry, index) in 3"
:key="index"
class="p-2 border-top"
style="height: 105px"
>
<b-skeleton width="30%" class="m-2 mb-3"></b-skeleton>
<b-skeleton width="95%" class="m-2"></b-skeleton>
<b-skeleton width="40%" class="m-2"></b-skeleton>
</div>
</template>
<b-table
id="resultsView"
:items="resultsViewData"
:fields="resultsViewFields"
:per-page="paginationPerPage"
:current-page="paginationCurrentPage"
thead-class="d-none"
style="min-height: 100%"
small
hover
sticky-header
show-empty
>
<template #cell(header)="data">
<Result :result="data.item" />
</template>
<template #empty>
<h6 class="text-center">
{{ $parent.$t("page.search.emptySearch") }}
</h6>
</template>
<template #custom-foot="foot">
<!-- Show footer if there are results and only on the last page -->
<div
v-if="
foot.items.length > 0 &&
paginationCurrentPage ===
Math.ceil(paginationTotalRows / paginationPerPage)
"
class="p-2 text-center text-muted border-top"
>
{{ $parent.$t("page.search.endSearchResults") }}
</div> </div>
</section> </template>
</b-table>
</b-skeleton-wrapper>
</b-card>
</b-row>
</b-col>
</b-row>
<!-- Pagination -->
<b-row class="mt-1 mb-1 text-right" align-v="center">
<b-col align-self="center" class="p-0" />
<b-col align-self="center" class="p-0">
<b-pagination
id="pagination"
v-model="paginationCurrentPage"
:total-rows="paginationTotalRows"
:per-page="paginationPerPage"
aria-controls="resultsView"
align="center"
></b-pagination>
</b-col>
<b-col align-self="center" class="p-0">
<b-form-select
v-model="paginationPerPage"
:options="paginationPerPageOptions"
style="max-width: 5rem"
></b-form-select>
</b-col>
</b-row>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from "vue-demi"; import { defineComponent } from "vue-demi";
import CoscineHeadline from "@/components/CoscineHeadline.vue"; import Result from "./components/Result.vue";
import Sidebar from "./components/Sidebar.vue";
// import the store for current module // import the store for current module
import { useSearchStore } from "../store"; import { useSearchStore } from "../store";
// import the main store // import the main store
import { useMainStore } from "@/store/index"; import { useMainStore } from "@/store/index";
import type { DummyDataType } from "../types";
export default defineComponent({ export default defineComponent({
setup() { setup() {
const mainStore = useMainStore(); const mainStore = useMainStore();
...@@ -31,8 +189,84 @@ export default defineComponent({ ...@@ -31,8 +189,84 @@ export default defineComponent({
return { mainStore, searchStore }; return { mainStore, searchStore };
}, },
data() {
return {
searchText: "",
selectProjValue: null,
selectResValue: null,
filterTags: ["Filter 1", "Filter 2", "Filter 3"],
resultsViewFields: ["header"],
resultsViewLoading: true,
paginationCurrentPage: 1,
paginationPerPage: 10,
paginationPerPageOptions: [5, 10, 20, 50, 100],
paginationTotalRows: 0,
};
},
components: { components: {
CoscineHeadline, Result,
Sidebar,
},
computed: {
resultsViewData(): DummyDataType[] | null {
return this.searchStore.searchResults;
},
},
watch: {
resultsViewData() {
if (this.resultsViewData) {
this.paginationTotalRows = this.resultsViewData.length;
}
},
},
created() {
this.showResults();
},
methods: {
/* --- get search query to show results ---
getSearchQuery() {
const urlSearchParams = new URLSearchParams(window.location.search);
const query = Object.fromEntries(urlSearchParams.entries());
return query !== null ? decodeURIComponent(query.q) : "";
},
*/
showResults() {
setTimeout(() => {
this.resultsViewLoading = false;
}, 1500);
},
}, },
}); });
</script> </script>
<style scoped>
#mainRow {
/* this style stretches the page vertically to fit the screen:-moz-animation:
- container-fluid <-> top = 59px
- mainRow <-> container-fluid = 53px
- mainRow <-> bottom = 62px
*/
height: calc(100vh - 59px - 53px - 62px);
}
#pagination {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
#resultsViewContainer {
/* this style stretches the results table vertically
- resultsViewContainer <-> mainRow = 91px
*/
height: calc(100% - 91px);
}
.card-body {
padding: 0rem;
}
</style>
<template>
<div class="p-2">
<a href="#">
<div
id="resultHeader"
v-for="(header, index) in result.header"
:key="index"
class="mb-2 text-left text-primary"
>
<b>{{ header.type }}</b
>: {{ header.value }}
<b-badge v-if="header.archived" pill variant="warning" class="ml-1">{{
$t("default.archived")
}}</b-badge>
</div>
</a>
<div id="resultBody" class="text-left">
<span
v-for="(element, index) in result.body"
:key="index"
class="mr-3 d-inline-block"
>
<!-- Keep the <br/> element at the end to have
double mouse click text selection work properly and
not have a table horizontal scrollbar appear -->
<b>{{ element.type }}</b
>: {{ element.value }}<br />
</span>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue-demi";
import { DummyDataType } from "../../types";
// import the store for current module
import { useSearchStore } from "../../store";
// import the main store
import { useMainStore } from "@/store/index";
export default defineComponent({
setup() {
const mainStore = useMainStore();
const searchStore = useSearchStore();
return { mainStore, searchStore };
},
data() {
return {};
},
props: {
result: {
required: true,
type: Object as PropType<DummyDataType>,
},
},
methods: {},
});
</script>
<template>
<b-col>
<b-card
id="sidebarContainer"
class="progress-bar-striped text-center bg-light"
style="height: 100%"
>
<!-- Sidebar components come here -->
</b-card>
</b-col>
</template>
<script lang="ts">
import { defineComponent } from "vue-demi";
// import the store for current module
import { useSearchStore } from "../../store";
// import the main store
import { useMainStore } from "@/store/index";
export default defineComponent({
setup() {
const mainStore = useMainStore();
const searchStore = useSearchStore();
return { mainStore, searchStore };
},
});
</script>
<style scoped>
.card-body {
padding: 0rem;
}
</style>
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { SearchState } from "./types"; import { SearchState } from "./types";
import { mainViewData } from "./assets/dummy_data";
import type { DummyDataType } from "./types";
/* /*
Store variable name is "this.<id>Store" Store variable name is "this.<id>Store"
...@@ -13,7 +16,9 @@ export const useSearchStore = defineStore({ ...@@ -13,7 +16,9 @@ export const useSearchStore = defineStore({
STATES STATES
-------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------
*/ */
state: (): SearchState => ({}), state: (): SearchState => ({
searchResults: null,
}),
/* /*
-------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------
...@@ -24,7 +29,11 @@ export const useSearchStore = defineStore({ ...@@ -24,7 +29,11 @@ export const useSearchStore = defineStore({
In a component use as e.g.: In a component use as e.g.:
:label = "this.searchStore.<getter_name>; :label = "this.searchStore.<getter_name>;
*/ */
getters: {}, getters: {
retrieveDummyResults(): DummyDataType[] {
return mainViewData;
}
},
/* /*
-------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------
ACTIONS ACTIONS
...@@ -35,7 +44,12 @@ export const useSearchStore = defineStore({ ...@@ -35,7 +44,12 @@ export const useSearchStore = defineStore({
In a component use as e.g.: In a component use as e.g.:
@click = "this.searchStore.<action_name>(); @click = "this.searchStore.<action_name>();
*/ */
actions: {}, actions: {
retrieveSearchResults() {
// Currently using only Dummy Data
this.searchResults = this.retrieveDummyResults;
}
},
}); });
export default useSearchStore; export default useSearchStore;
import type { SearchResult } from "@coscine/api-client/dist/types/Coscine.Api.Search";
export interface SearchState { export interface SearchState {
/* /*
-------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------
STATE TYPE DEFINITION STATE TYPE DEFINITION
-------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------
*/ */
searchResults: DummyDataType[] | null, // Fix type for real API use
} }
/* DELETE AFTER DATA IS ACTUALLY FETCHED OVER THE API */
export type DummyDataType = {
header: Array<DummyDataEntry>;
body: Array<DummyDataEntry>;
};
/* DELETE AFTER DATA IS ACTUALLY FETCHED OVER THE API */
export type DummyDataEntry = {
type: string;
value: string;
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment