From 9816c566e98c0271378da0da244325264a265e9f Mon Sep 17 00:00:00 2001 From: Adam Coldrick <adam@sotk.co.uk> Date: Wed, 23 Sep 2020 23:56:33 +0100 Subject: [PATCH] Add a Project Group Detail view --- src/components/ProjectGroupTabProjects.vue | 52 ++++++++++++ src/components/ProjectGroupTabStories.vue | 52 ++++++++++++ src/router/index.js | 4 + src/views/ProjectGroupDetail.vue | 95 ++++++++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 src/components/ProjectGroupTabProjects.vue create mode 100644 src/components/ProjectGroupTabStories.vue create mode 100644 src/views/ProjectGroupDetail.vue diff --git a/src/components/ProjectGroupTabProjects.vue b/src/components/ProjectGroupTabProjects.vue new file mode 100644 index 0000000..1b1fc9f --- /dev/null +++ b/src/components/ProjectGroupTabProjects.vue @@ -0,0 +1,52 @@ +<template> + <div class="project-group-project-list"> + <Spinner v-if="loading" :fullScreen="true" /> + <ProjectFilters @filter-change="getProjects" /> + <ProjectListItem v-for="project in projects" :key="project.id" :project="project" /> + <div v-if="!projects.length"> + No matching stories found. + </div> + </div> +</template> + +<script> +import project from '@/api/project.js' + +import ProjectFilters from '@/components/ProjectFilters.vue' +import ProjectListItem from '@/components/ProjectListItem.vue' +import Spinner from '@/components/Spinner.vue' + +export default { + name: 'ProjectGroupTabStories', + props: ['projectGroup'], + components: { + ProjectFilters, + ProjectListItem, + Spinner + }, + data () { + return { + loading: false, + projects: [] + } + }, + created () { + this.getProjects(this.$route.query) + }, + methods: { + async getProjects (filters = {}) { + this.$router.push({ query: filters }) + const params = { + ...filters, + project_group_id: this.projectGroup.id, + limit: 10 + } + + this.projects = [] + this.loading = true + this.projects = await project.browse(params) + this.loading = false + } + } +} +</script> diff --git a/src/components/ProjectGroupTabStories.vue b/src/components/ProjectGroupTabStories.vue new file mode 100644 index 0000000..e277c8e --- /dev/null +++ b/src/components/ProjectGroupTabStories.vue @@ -0,0 +1,52 @@ +<template> + <div class="project-story-list"> + <StoryFilters @filter-change="getStories" /> + <Spinner v-if="loading" :fullScreen="true" /> + <StoryListItem v-for="story in stories" :key="story.id" :story="story" /> + <div v-if="!stories.length"> + No matching stories found. + </div> + </div> +</template> + +<script> +import story from '@/api/story.js' + +import Spinner from '@/components/Spinner.vue' +import StoryFilters from '@/components/StoryFilters.vue' +import StoryListItem from '@/components/StoryListItem.vue' + +export default { + name: 'ProjectGroupTabStories', + props: ['projectGroup'], + components: { + Spinner, + StoryFilters, + StoryListItem + }, + data () { + return { + loading: false, + stories: [] + } + }, + mounted () { + this.getStories(this.$route.query) + }, + methods: { + async getStories (filters = {}) { + this.$router.push({ query: filters }) + const params = { + ...filters, + project_group_id: this.projectGroup.id, + limit: 10 + } + + this.stories = [] + this.loading = true + this.stories = await story.browse(params) + this.loading = false + } + } +} +</script> diff --git a/src/router/index.js b/src/router/index.js index 9429006..7eaad89 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -51,6 +51,10 @@ const routes = [ name: 'Project Groups', component: () => import(/* webpackChunkName: "projectgroup" */ '../views/ProjectGroupList.vue') }, + { + path: '/project-group/:id', + component: () => import(/* webpackChunkName: "projectgroup-detail" */ '../views/ProjectGroupDetail.vue') + }, { path: '/user', name: 'Users', diff --git a/src/views/ProjectGroupDetail.vue b/src/views/ProjectGroupDetail.vue new file mode 100644 index 0000000..b077f84 --- /dev/null +++ b/src/views/ProjectGroupDetail.vue @@ -0,0 +1,95 @@ +<template> + <div class="project-detail"> + <div class="header-row"> + <h1>{{ projectGroup.title }}</h1> + </div> + <ul class="tabs"> + <li class="tab" :class="{ active: currentTab === tab }" v-for="tab in tabs" :key="tab.name" @click="switchToTab(tab)"> + {{ tab.name }} + </li> + </ul> + <component :is="currentTab.component" :projectGroup="projectGroup"></component> + </div> +</template> + +<script> +import projectGroup from '@/api/project_group.js' + +import ProjectGroupTabProjects from '@/components/ProjectGroupTabProjects.vue' +import ProjectGroupTabStories from '@/components/ProjectGroupTabStories.vue' + +export default { + name: 'ProjectGroupDetailView', + components: { + ProjectGroupTabProjects, + ProjectGroupTabStories + }, + data () { + return { + currentTab: {}, + projectGroup: {}, + tabs: [ + { + name: 'Projects in this Group', + component: ProjectGroupTabProjects + }, + { + name: 'Stories related to this Group', + component: ProjectGroupTabStories + } + ] + } + }, + created () { + this.getProject(this.$route.params.id) + }, + methods: { + async getProject (projectGroupId) { + this.projectGroup = await projectGroup.get(projectGroupId) + if (Object.keys(this.$route.query).length !== 0) { + this.currentTab = this.tabs[1] + } else { + this.currentTab = this.tabs[0] + } + }, + switchToTab (tab) { + if (tab !== this.tabs[1]) { + // If we're navigating away from the Stories tab, clear + // the query string to keep the URL intuitive when shared. + this.$router.replace({ query: {} }) + } + this.currentTab = tab + } + } +} +</script> + +<style lang="scss" scoped> +.project-detail { + .tabs { + list-style: none; + display: flex; + //margin: 20px 15% 10px 15%; + padding: 0; + border-bottom: solid 1px #ddd; + text-align: center; + + .tab { + flex: 0 1 auto; + padding: 20px; + font-size: 1.2em; + cursor: pointer; + transition: 100ms ease-in-out all; + + &:hover { + box-shadow: 0 -1px 0 inset #c43422; + } + + &.active { + color: #c43422; + box-shadow: 0 -2px 0 inset #c43422; + } + } + } +} +</style>