diff --git a/client-nuxt/pages/phenotype-categories.vue b/client-nuxt/pages/phenotype-categories.vue index b7198a8c384d31e6d6ae6b20048a4470ceab6a96..64acba1f7ab44b8dd5b352fee7f34911b10f0673 100644 --- a/client-nuxt/pages/phenotype-categories.vue +++ b/client-nuxt/pages/phenotype-categories.vue @@ -1,375 +1,9 @@ <template> - <v-card> - <error-alert v-if="error" :error-message="error"></error-alert> - <v-data-table - :headers="headers" - :items="phenotypeCategories" - :sort-by="['dataclass', 'name']" - :sort-desc="[true, false]" - :search="search" - multi-sort - class="elevation-1" - > - <template v-slot:top> - <v-toolbar flat> - <v-toolbar-title> Phenotype Categories </v-toolbar-title> - <v-divider class="mx-4" inset vertical></v-divider> - <v-text-field - v-model="search" - append-icon="mdi-magnify" - label="Search" - single-line - hide-details - ></v-text-field> - <v-spacer></v-spacer> - <v-dialog v-if="canAdd" v-model="dialog" max-width="800px"> - <template v-slot:activator="{ on, attrs }"> - <v-btn - small - color="primary" - fab - v-bind="attrs" - v-on="on" - @click="newItem = true" - > - <v-icon> mdi-plus </v-icon> - </v-btn> - </template> - <phenotype-category-form - :options="options" - :form-title="formTitle" - v-bind.sync="phenotypeCategory" - @save="save" - @cancel="close" - ></phenotype-category-form> - <!-- <v-card> - <ValidationObserver - ref="observer" - v-slot="{ invalid, validated, handleSubmit }" - > - <v-card-title> - <span class="text-h5">{{ formTitle }}</span> - </v-card-title> - - <v-card-text> - <v-form> - <v-container> - <v-row> - <v-col cols="12"> - <ValidationProvider - v-slot="{ errors, valid }" - name="Name" - rules="required|max:50" - > - <v-text-field - v-model="phenotypeCategory.name" - label="Name" - :counter="50" - :error-messages="errors" - :success="valid" - ></v-text-field> - </ValidationProvider> - </v-col> - <v-col cols="12"> - <ValidationProvider - v-slot="{ errors, valid }" - name="Phenotype category description" - rules="required|max:400" - > - <v-textarea - v-model="phenotypeCategory.description" - outlined - name="input-7-4" - label="Description" - :counter="400" - :error-messages="errors" - :success="valid" - ></v-textarea> - </ValidationProvider> - </v-col> - <v-col cols="12"> - <v-select - v-model="phenotypeCategory.dataclass" - :items="options.dataclass.choices" - label="Class" - item-text="display_name" - item-value="value" - outlined - ></v-select> - </v-col> - <v-col cols="12"> - <v-select - v-model="phenotypeCategory.datatype" - :items="options.datatype.choices" - label="Type" - item-text="display_name" - item-value="value" - outlined - ></v-select> - </v-col> - <v-col cols="12"> - <v-select - v-model="phenotypeCategory.nature" - :items="options.nature.choices" - label="Nature" - item-text="display_name" - item-value="value" - outlined - ></v-select> - </v-col> - <v-col cols="12"> - <v-select - v-model="phenotypeCategory.location" - :items="options.location.choices" - label="Location" - item-text="display_name" - item-value="value" - outlined - ></v-select> - </v-col> - </v-row> - </v-container> - </v-form> - </v-card-text> - - <v-card-actions> - <v-spacer></v-spacer> - <v-btn color="blue darken-1" text @click="close"> - Cancel - </v-btn> - <v-btn - color="blue darken-1" - text - :disabled="invalid || !validated" - @click="handleSubmit(submit)" - > - Save - </v-btn> - </v-card-actions> - </ValidationObserver> - </v-card> --> - </v-dialog> - <v-dialog v-model="dialogDelete" max-width="800px"> - <v-card> - <v-card-title class="text-h5" - >Are you sure you want to delete this phenotype - category?</v-card-title - > - <v-card-actions> - <v-spacer></v-spacer> - <v-btn color="blue darken-1" text @click="closeDelete" - >Cancel</v-btn - > - <v-btn color="blue darken-1" text @click="deleteItemConfirm" - >OK</v-btn - > - <v-spacer></v-spacer> - </v-card-actions> - </v-card> - </v-dialog> - </v-toolbar> - </template> - <template #[`item.actions`]="{ item }"> - <v-icon - v-if="permissions.has('change')" - small - class="mr-2" - @click="editItem(item)" - > - mdi-pencil - </v-icon> - <v-icon - v-if="permissions.has('delete')" - small - @click="deleteItem(item)" - > - mdi-delete - </v-icon> - </template> - <template v-slot:no-data> - <v-btn color="primary" @click="initialize"> Reset </v-btn> - </template></v-data-table - > - <v-card-text v-if="fromAddExperiment"> - <v-btn :to="fromAddExperiment">Go back to Add experiment</v-btn> - </v-card-text></v-card - > + <nuxt-child></nuxt-child> </template> <script> -import ErrorAlert from '@/components/ErrorAlert' -import PhenotypeCategoryForm from '@/components/PhenotypeCategoryForm' - -export default { - components: { - ErrorAlert, - // ValidationObserver, - // ValidationProvider, - PhenotypeCategoryForm, - }, - async fetch() { - try { - this.phenotypeCategories = await this.$axios.$get( - `/api/phenotype-categories/` - ) - } catch (error) { - return { error: error.message } - } - }, - async asyncData({ $axios }) { - const phenotypeCategories = await $axios.$get(`/api/phenotype-categories/`) - const permissions = await $axios.$get( - '/api/phenotype-categories/permissions/' - ) - const endpointOptions = await $axios.$options('/api/phenotype-categories/') - let options = null - if (endpointOptions?.actions?.POST) { - options = endpointOptions.actions.POST - } - return { - phenotypeCategories, - options, - permissions: new Set(permissions), - } - }, - data() { - return { - fromAddExperiment: null, - search: '', - dialog: false, - dialogDelete: false, - newItem: true, - error: null, - phenotypeCategory: { - name: '', - description: '', - datatype: 'Unassigned', - dataclass: 'Phenotype', - nature: 'Unassigned', - location: 'Unassigned', - }, - defaultPhenotypeCategory: { - name: '', - description: '', - datatype: 'Unassigned', - dataclass: 'Phenotype', - nature: 'Unassigned', - location: 'Unassigned', - }, - allHeaders: [ - { - text: 'Name', - align: 'start', - value: 'name', - }, - { text: 'Description', value: 'description' }, - { text: 'Data Class', value: 'dataclass' }, - { text: 'Datatype', value: 'datatype' }, - { text: 'Nature', value: 'nature' }, - { text: 'Location', value: 'location' }, - { text: 'Actions', value: 'actions', sortable: false }, - ], - } - }, - computed: { - formTitle() { - return this.newItem ? 'New Phenotype Category' : 'Edit Phenotype Category' - }, - headers() { - return this.canChangeOrDelete - ? this.allHeaders - : this.allHeaders.slice(0, -1) - }, - canChangeOrDelete() { - return this.permissions.has('change') || this.permissions.has('delete') - }, - canAdd() { - return this.permissions.has('add') - }, - }, - - methods: { - submit() { - this.$refs.observer.validate().then((success) => { - this.save() - }) - }, - editItem(item) { - this.phenotypeCategory = { ...item } - this.dialog = true - this.newItem = false - }, - deleteItem(item) { - this.phenotypeCategory = { ...item } - this.dialogDelete = true - }, - - async deleteItemConfirm() { - try { - await this.$axios.$delete( - `/api/phenotype-categories/${this.phenotypeCategory.id}` - ) - this.$fetch() - } catch (err) { - console.dir(err) - if (err?.response?.data && err.response.data.length > 0) { - this.error = err.response.data[0] - } else { - this.error = err.message - } - } finally { - this.closeDelete() - } - }, - closeDelete() { - this.dialogDelete = false - this.$nextTick(() => { - this.phenotypeCategory = { ...this.defaultPhenotypeCategory } - this.newItem = false - }) - }, - close() { - this.dialog = false - this.$nextTick(() => { - this.phenotypeCategory = { ...this.defaultPhenotypeCategory } - this.newItem = false - }) - }, - async save() { - try { - if (this.newItem) { - await this.$axios.$post( - '/api/phenotype-categories/', - this.phenotypeCategory - ) - } else { - await this.$axios.$patch( - `/api/phenotype-categories/${this.phenotypeCategory.id}/`, - this.phenotypeCategory - ) - } - this.$fetch() - } catch (err) { - this.error = err.message - } finally { - this.close() - } - }, - }, - beforeRouteEnter(to, from, next) { - next((vm) => { - console.log(to) - console.log(from) - console.log(vm) - if (from.name === 'projects-id-experiments-add') - vm.fromAddExperiment = from - }) - }, - beforeRouteLeave(to, from, next) { - this.fromAddExperiment = null - next() - }, -} +export default {} </script> <style></style> diff --git a/client-nuxt/pages/phenotype-categories/add.vue b/client-nuxt/pages/phenotype-categories/add.vue new file mode 100644 index 0000000000000000000000000000000000000000..e6ff1418f5b72da8567a90038b72fbc321b8aa7b --- /dev/null +++ b/client-nuxt/pages/phenotype-categories/add.vue @@ -0,0 +1,114 @@ +<template> + <v-card> + <v-toolbar v-if="isFromAddExperiment" flat> + <v-btn text nuxt x-small :to="fromAddExperiment"> + <v-icon left> mdi-arrow-left</v-icon> Upload experiment in project : + <span class="primary--text"> {{ projectName }}</span> + </v-btn></v-toolbar + > + <v-card-text> + <phenotype-category-form + v-if="canAdd" + :options="options" + :form-title="formTitle" + v-bind.sync="phenotypeCategory" + :has-cancel-btn="!isFromAddExperiment" + @save="goBackOrToPhenotypeCategories" + @cancel="goBackOrToPhenotypeCategories" + > + <template v-if="fromAddExperiment" #save-btn-text> + Save + <span class="text-disabled text-lowercase text-caption" + >(And go back to upload experiment in + <span class="font-weight-bold"> {{ projectName }}</span + >)</span + ></template + > + </phenotype-category-form> + <v-alert v-else type="error" prominent text> + You don't have the permissions to add new phenotype categories</v-alert + ></v-card-text + > + </v-card> +</template> +<script> +import PhenotypeCategoryForm from '@/components/PhenotypeCategoryForm' +export default { + components: { + PhenotypeCategoryForm, + }, + async asyncData({ $axios }) { + const permissions = await $axios.$get( + '/api/phenotype-categories/permissions/' + ) + const endpointOptions = await $axios.$options('/api/phenotype-categories/') + let options = null + if (endpointOptions?.actions?.POST) { + options = endpointOptions.actions.POST + } + return { + options, + permissions: new Set(permissions), + } + }, + + data() { + return { + formTitle: 'New phenotype category', + phenotypeCategory: { + name: '', + description: '', + datatype: 'Unassigned', + dataclass: 'Phenotype', + nature: 'Unassigned', + location: 'Unassigned', + }, + fromAddExperiment: null, + projectName: null, + } + }, + computed: { + canAdd() { + return this.permissions.has('add') + }, + projectId() { + return this?.fromAddExperiment?.params?.id + }, + isFromAddExperiment() { + return this.fromAddExperiment?.name + }, + }, + watch: { + async projectId(newProjectId) { + if (newProjectId !== undefined) { + try { + const { + data: { project_name: projectName }, + } = await this.$axios.get(`/api/projects/${newProjectId}/`) + this.projectName = projectName + } catch (err) {} + } + }, + }, + methods: { + goBackOrToPhenotypeCategories() { + if (this.isFromAddExperiment) { + this.$router.push(this.fromAddExperiment) + } else { + this.$router.push({ name: 'phenotype-categories' }) + } + }, + async getProject(projectId) {}, + }, + beforeRouteEnter(to, from, next) { + next((vm) => { + if (from.name === 'projects-id-experiments-add') + vm.fromAddExperiment = from + }) + }, + beforeRouteLeave(to, from, next) { + this.fromAddExperiment = null + next() + }, +} +</script> diff --git a/client-nuxt/pages/phenotype-categories/index.vue b/client-nuxt/pages/phenotype-categories/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..c9538639c7f34c6388dc88ac7bb2471df7ee87b2 --- /dev/null +++ b/client-nuxt/pages/phenotype-categories/index.vue @@ -0,0 +1,217 @@ +<template> + <v-card flat> + <error-alert v-if="error" :error-message="error"></error-alert> + <v-data-table + :headers="headers" + :items="phenotypeCategories" + :sort-by="['dataclass', 'name']" + :sort-desc="[true, false]" + :search="search" + :loading="$fetchState.pending" + multi-sort + class="elevation-1" + > + <template v-slot:top> + <v-toolbar flat> + <v-toolbar-title> Phenotype Categories </v-toolbar-title> + <v-divider class="mx-4" inset vertical></v-divider> + <v-text-field + v-model="search" + append-icon="mdi-magnify" + label="Search" + single-line + hide-details + ></v-text-field> + <v-spacer></v-spacer> + <v-btn + v-if="canAdd" + small + color="primary" + fab + nuxt + :to="addPhenotypeCategoryRoute" + > + <v-icon> mdi-plus </v-icon> + </v-btn> + <v-dialog v-model="dialogDelete" max-width="800px"> + <v-card> + <v-card-title class="text-h5" + >Are you sure you want to delete this phenotype + category?</v-card-title + > + <v-card-actions> + <v-spacer></v-spacer> + <v-btn color="blue darken-1" text @click="closeDelete" + >Cancel</v-btn + > + <v-btn color="blue darken-1" text @click="deleteItemConfirm" + >OK</v-btn + > + <v-spacer></v-spacer> + </v-card-actions> + </v-card> + </v-dialog> + </v-toolbar> + </template> + <template #[`item.actions`]="{ item }"> + <v-icon + v-if="permissions.has('change')" + small + class="mr-2" + @click="editItem(item)" + > + mdi-pencil + </v-icon> + <v-icon + v-if="permissions.has('delete')" + small + @click="deleteItem(item)" + > + mdi-delete + </v-icon> + </template> + <template v-slot:no-data> + <v-btn color="primary" @click="initialize"> Reset </v-btn> + </template></v-data-table + > + <v-dialog v-model="dialog" max-width="800px"> + <phenotype-category-form + :options="options" + form-title="Edit Phenotype Category" + :new-item="newItem" + v-bind.sync="phenotypeCategory" + @save="save" + @cancel="close" + ></phenotype-category-form + ></v-dialog> + </v-card> +</template> +<script> +import ErrorAlert from '@/components/ErrorAlert' + +import PhenotypeCategoryForm from '@/components/PhenotypeCategoryForm' +export default { + components: { + PhenotypeCategoryForm, + ErrorAlert, + }, + async fetch() { + try { + this.phenotypeCategories = await this.$axios.$get( + `/api/phenotype-categories/` + ) + } catch (error) { + return { error: error.message } + } + }, + async asyncData({ $axios }) { + const permissions = await $axios.$get( + '/api/phenotype-categories/permissions/' + ) + const endpointOptions = await $axios.$options('/api/phenotype-categories/') + let options = null + if (endpointOptions?.actions?.POST) { + options = endpointOptions.actions.POST + } + return { + options, + permissions: new Set(permissions), + } + }, + data() { + return { + fromAddExperiment: null, + search: '', + dialog: false, + dialogDelete: false, + newItem: true, + error: null, + phenotypeCategory: { + name: '', + description: '', + datatype: 'Unassigned', + dataclass: 'Phenotype', + nature: 'Unassigned', + location: 'Unassigned', + }, + allHeaders: [ + { + text: 'Name', + align: 'start', + value: 'name', + }, + { text: 'Description', value: 'description' }, + { text: 'Data Class', value: 'dataclass' }, + { text: 'Datatype', value: 'datatype' }, + { text: 'Nature', value: 'nature' }, + { text: 'Location', value: 'location' }, + { text: 'Actions', value: 'actions', sortable: false }, + ], + phenotypeCategories: [], + } + }, + computed: { + addPhenotypeCategoryRoute() { + return { name: 'phenotype-categories-add' } + }, + canChangeOrDelete() { + return this.permissions.has('change') || this.permissions.has('delete') + }, + headers() { + return this.canChangeOrDelete + ? this.allHeaders + : this.allHeaders.filter(({ value }) => value !== 'actions') + }, + canAdd() { + return this.permissions.has('add') + }, + }, + mounted() { + this.$fetch() + }, + methods: { + editItem(item) { + this.phenotypeCategory = { ...item } + this.dialog = true + this.newItem = false + }, + deleteItem(item) { + this.phenotypeCategory = { ...item } + this.dialogDelete = true + }, + async deleteItemConfirm() { + try { + await this.$axios.$delete( + `/api/phenotype-categories/${this.phenotypeCategory.id}` + ) + this.$fetch() + } catch (err) { + console.dir(err) + if (err?.response?.data && err.response.data.length > 0) { + this.error = err.response.data[0] + } else { + this.error = err.message + } + } finally { + this.closeDelete() + } + }, + closeDelete() { + this.dialogDelete = false + this.$nextTick(() => { + this.newItem = false + }) + }, + close() { + this.dialog = false + this.$nextTick(() => { + this.newItem = false + }) + }, + save() { + this.$fetch() + this.close() + }, + }, +} +</script>