Skip to content

Commit

Permalink
feat: Case specific data functionality (#93) (#175)
Browse files Browse the repository at this point in the history
  • Loading branch information
gromdimon authored Oct 30, 2023
1 parent 3bef5e9 commit 247d9b1
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 12 deletions.
111 changes: 111 additions & 0 deletions frontend/src/components/CaseInformationCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useCaseStore } from '@/stores/case'
import { StoreState } from '@/stores/misc'
const caseStore = useCaseStore()
const form = ref(null)
const loadDataToStore = async () => {
await caseStore.loadCase()
}
const saveChanges = () => {
if (!form.value) {
return
}
console.log('form', form.value)
if ((form.value as any).validate()) {
caseStore.updateCase(caseStore.caseInfo)
}
}
onMounted(async () => {
loadDataToStore()
})
</script>

<template>
<div v-if="caseStore.storeState === StoreState.Active">
<v-card class="case-card">
<v-card-title>Case Information</v-card-title>
<v-card-text>
<v-form ref="form">
<v-text-field label="Pseudonym" v-model="caseStore.caseInfo.pseudonym"></v-text-field>

<v-text-field
v-for="(disease, index) in caseStore.caseInfo.diseases"
:key="index"
label="Disease"
v-model="caseStore.caseInfo.diseases[index]"
></v-text-field>

<v-text-field
v-for="(term, index) in caseStore.caseInfo.hpoTerms"
:key="index"
label="HPO Term"
v-model="caseStore.caseInfo.hpoTerms[index]"
></v-text-field>

<v-text-field label="Inheritance" v-model="caseStore.caseInfo.inheritance"></v-text-field>

<v-text-field
v-for="(member, index) in caseStore.caseInfo.affectedFamilyMembers"
:key="index"
label="Affected Family Member"
v-model="caseStore.caseInfo.affectedFamilyMembers[index]"
></v-text-field>

<v-select
label="Sex"
:items="['Male', 'Female']"
v-model="caseStore.caseInfo.sex"
></v-select>

<v-text-field label="Age of Onset" v-model="caseStore.caseInfo.ageOfOnset"></v-text-field>

<v-text-field label="Ethnicity" v-model="caseStore.caseInfo.ethnicity"></v-text-field>

<v-select
label="Zygosity"
:items="['Heterozygous', 'Homozygous', 'Compound Heterozygous']"
v-model="caseStore.caseInfo.zygosity"
></v-select>

<v-text-field
label="Family Segregation"
v-model="caseStore.caseInfo.familySegregation"
></v-text-field>

<v-btn class="ml-2" @click="saveChanges">Save Changes</v-btn>
<v-btn class="ml-2" @click="caseStore.clearData">Clear Data</v-btn>
</v-form>
</v-card-text>
</v-card>
</div>
<div v-else-if="caseStore.storeState === StoreState.Loading">
<v-card>
<v-progress-circular indeterminate></v-progress-circular>
</v-card>
</div>
<div v-else-if="caseStore.storeState === StoreState.Error">
<v-card>
<v-alert type="error">Error loading data.</v-alert>
</v-card>
</div>
<div v-else>
<v-card>
<v-card-text>
<p>Initial State</p>
</v-card-text>
</v-card>
</div>
</template>

<style scoped>
.case-card {
min-width: 600px;
}
</style>
17 changes: 17 additions & 0 deletions frontend/src/components/HeaderDefault.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
<script setup lang="ts">
import UserProfileButton from '@/components/UserProfileButton.vue'
export interface Props {
caseInformation?: boolean
}
const props = withDefaults(defineProps<Props>(), {
caseInformation: false
})
</script>

<template>
Expand All @@ -18,6 +26,15 @@ import UserProfileButton from '@/components/UserProfileButton.vue'
</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items class="topbar-links">
<v-btn
class="mr-4"
prepend-icon="mdi-information-outline"
:model-value="props.caseInformation"
@click="$emit('update:caseInformation', !props.caseInformation)"
>
{{ props.caseInformation ? 'Hide' : 'Show' }} Case Information
</v-btn>

<UserProfileButton />
<v-menu id="menu">
<template v-slot:activator="{ props }">
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/components/__tests__/CaseInformationCard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest'

import { setupMountedComponents } from '@/lib/test-utils'
import { StoreState } from '@/stores/misc'

import CaseInformationCard from '../CaseInformationCard.vue'

describe.concurrent('CaseInformationCard.vue', () => {
it('renders information', () => {
const { wrapper } = setupMountedComponents(
{ component: CaseInformationCard, template: false },
{
initialStoreState: {
case: {
storeState: StoreState.Active
}
}
}
)
expect(wrapper.text()).toContain('Case Information')
})
})
6 changes: 6 additions & 0 deletions frontend/src/components/__tests__/HeaderDefault.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ describe.concurrent('HeaderDefault.vue', () => {
user: {
currentUser: null
}
},
props: {
caseInformation: true
}
}
)
Expand All @@ -33,6 +36,9 @@ describe.concurrent('HeaderDefault.vue', () => {
user: {
currentUser: null
}
},
props: {
caseInformation: true
}
}
)
Expand Down
50 changes: 50 additions & 0 deletions frontend/src/stores/__tests__/case.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it } from 'vitest'

import { useCaseStore } from '../case'
import { StoreState } from '../misc'

const CaseInfo = {
pseudonym: '',
diseases: [],
hpoTerms: [],
inheritance: '',
affectedFamilyMembers: [],
sex: undefined,
ageOfOnset: '',
ethnicity: '',
zygosity: undefined,
familySegregation: ''
}

describe.concurrent('Case Store', () => {
beforeEach(() => {
setActivePinia(createPinia())
})

it('should have initial state', () => {
const store = useCaseStore()

expect(store.storeState).toBe(StoreState.Initial)
expect(store.caseInfo).toStrictEqual(CaseInfo)
})

it('should load case information', async () => {
const store = useCaseStore()
store.loadCase()

expect(store.storeState).toBe(StoreState.Active)
expect(store.caseInfo).toEqual(CaseInfo)
})

it('should update case information', async () => {
const store = useCaseStore()
const updatedCaseInfo = { ...CaseInfo, pseudonym: 'test' }

expect(store.storeState).toBe(StoreState.Initial)
expect(store.caseInfo).toStrictEqual(CaseInfo)
store.updateCase(updatedCaseInfo)
expect(store.storeState).toBe(StoreState.Active)
expect(store.caseInfo).toEqual(updatedCaseInfo)
})
})
99 changes: 99 additions & 0 deletions frontend/src/stores/case.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* Store for case information.
*/
import { defineStore } from 'pinia'
import { ref } from 'vue'

import { StoreState } from '@/stores/misc'

export enum Sex {
Male = 'male',
Female = 'female'
}

export enum Zygosity {
Heterozygous = 'heterozygous',
Homozygous = 'homozygous',
CompoundHeterozygous = 'compound heterozygous'
}

export interface Case {
/* The case pseudonym. */
pseudonym: string
/* Orphanet / OMIM disease(s). */
diseases: string[]
/* HPO terms. */
hpoTerms: string[]
/* Inheritance. */
inheritance: string
/* Affected family members. */
affectedFamilyMembers: string[]
/* Sex. */
sex: Sex | undefined
/* Age of onset. */
ageOfOnset: string
/* Ethnicity. */
ethnicity: string
/* Zygosity. */
zygosity: Zygosity | undefined
/* Family segregation. */
familySegregation: string
}

export const useCaseStore = defineStore('case', () => {
/* The current store state. */
const storeState = ref<StoreState>(StoreState.Initial)

/* The case information. */
const caseInfo = ref<Case>({
pseudonym: '',
diseases: [],
hpoTerms: [],
inheritance: '',
affectedFamilyMembers: [],
sex: undefined,
ageOfOnset: '',
ethnicity: '',
zygosity: undefined,
familySegregation: ''
})

function clearData() {
caseInfo.value = {
pseudonym: '',
diseases: [],
hpoTerms: [],
inheritance: '',
affectedFamilyMembers: [],
sex: undefined,
ageOfOnset: '',
ethnicity: '',
zygosity: undefined,
familySegregation: ''
}
}

const loadCase = async () => {
storeState.value = StoreState.Loading
try {
// const client = new BookmarksClient()
// bookmarks.value = await client.fetchBookmarks()
storeState.value = StoreState.Active
} catch (e) {
storeState.value = StoreState.Error
}
}

const updateCase = async (caseUpdate: Case) => {
caseInfo.value = { ...caseUpdate }
storeState.value = StoreState.Active
}

return {
storeState,
caseInfo,
clearData,
loadCase,
updateCase
}
})
32 changes: 20 additions & 12 deletions frontend/src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import CaseInformationCard from '@/components/CaseInformationCard.vue'
import FooterDefault from '@/components/FooterDefault.vue'
import HeaderDefault from '@/components/HeaderDefault.vue'
import SearchBar from '@/components/SearchBar.vue'
Expand All @@ -16,6 +17,7 @@ const router = useRouter()
const searchTerm = ref('')
const genomeRelease = ref('grch37')
const showCaseInformation = ref(false)
interface Example {
query: string
Expand Down Expand Up @@ -59,7 +61,7 @@ const performSearch = async () => {
</script>

<template>
<HeaderDefault />
<HeaderDefault v-model:case-information="showCaseInformation" />
<v-container class="home-view">
<SearchBar
v-model:search-term="searchTerm"
Expand All @@ -68,17 +70,23 @@ const performSearch = async () => {
/>

<v-row>
<v-card id="examples">
<v-card-title>Example Queries:</v-card-title>
<v-card-text class="examples">
<div v-for="example in examples" :key="example.label">
<div v-if="example.label?.length" class="text-caption mt-3">{{ example.label }}</div>
<v-btn class="example mt-1" @click="performExampleSearch(example.query)">{{
example.query
}}</v-btn>
</div>
</v-card-text>
</v-card>
<v-col cols="12" md="4">
<v-card id="examples">
<v-card-title>Example Queries:</v-card-title>
<v-card-text class="examples">
<div v-for="example in examples" :key="example.label">
<div v-if="example.label?.length" class="text-caption mt-3">{{ example.label }}</div>
<v-btn class="example mt-1" @click="performExampleSearch(example.query)">{{
example.query
}}</v-btn>
</div>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="6">
<CaseInformationCard class="ml-16 mt-8" v-if="showCaseInformation" />
</v-col>
</v-row>
</v-container>
<FooterDefault />
Expand Down

0 comments on commit 247d9b1

Please sign in to comment.