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>