diff options
Diffstat (limited to 'app/assets/javascripts/crm')
19 files changed, 299 insertions, 501 deletions
diff --git a/app/assets/javascripts/crm/components/contact_form.vue b/app/assets/javascripts/crm/components/contact_form.vue deleted file mode 100644 index 81ae5c246be..00000000000 --- a/app/assets/javascripts/crm/components/contact_form.vue +++ /dev/null @@ -1,224 +0,0 @@ -<script> -import { GlAlert, GlButton, GlDrawer, GlFormGroup, GlFormInput } from '@gitlab/ui'; -import { produce } from 'immer'; -import { __, s__ } from '~/locale'; -import { convertToGraphQLId } from '~/graphql_shared/utils'; -import { TYPE_GROUP } from '~/graphql_shared/constants'; -import createContactMutation from './queries/create_contact.mutation.graphql'; -import updateContactMutation from './queries/update_contact.mutation.graphql'; -import getGroupContactsQuery from './queries/get_group_contacts.query.graphql'; - -export default { - components: { - GlAlert, - GlButton, - GlDrawer, - GlFormGroup, - GlFormInput, - }, - inject: ['groupFullPath', 'groupId'], - props: { - drawerOpen: { - type: Boolean, - required: true, - }, - contact: { - type: Object, - required: false, - default: () => {}, - }, - }, - data() { - return { - firstName: '', - lastName: '', - phone: '', - email: '', - description: '', - submitting: false, - errorMessages: [], - }; - }, - computed: { - invalid() { - const { firstName, lastName, email } = this; - - return firstName.trim() === '' || lastName.trim() === '' || email.trim() === ''; - }, - editMode() { - return Boolean(this.contact); - }, - title() { - return this.editMode ? this.$options.i18n.editTitle : this.$options.i18n.newTitle; - }, - buttonLabel() { - return this.editMode - ? this.$options.i18n.editButtonLabel - : this.$options.i18n.createButtonLabel; - }, - mutation() { - return this.editMode ? updateContactMutation : createContactMutation; - }, - variables() { - const { contact, firstName, lastName, phone, email, description, editMode, groupId } = this; - - const variables = { - input: { - firstName, - lastName, - phone, - email, - description, - }, - }; - - if (editMode) { - variables.input.id = contact.id; - } else { - variables.input.groupId = convertToGraphQLId(TYPE_GROUP, groupId); - } - - return variables; - }, - }, - mounted() { - if (this.editMode) { - const { contact } = this; - - this.firstName = contact.firstName || ''; - this.lastName = contact.lastName || ''; - this.phone = contact.phone || ''; - this.email = contact.email || ''; - this.description = contact.description || ''; - } - }, - methods: { - save() { - const { mutation, variables, updateCache, close } = this; - - this.submitting = true; - - return this.$apollo - .mutate({ - mutation, - variables, - update: updateCache, - }) - .then(({ data }) => { - if ( - data.customerRelationsContactCreate?.errors.length === 0 || - data.customerRelationsContactUpdate?.errors.length === 0 - ) { - close(true); - } - - this.submitting = false; - }) - .catch(() => { - this.errorMessages = [this.$options.i18n.somethingWentWrong]; - this.submitting = false; - }); - }, - close(success) { - this.$emit('close', success); - }, - updateCache(store, { data }) { - const mutationData = - data.customerRelationsContactCreate || data.customerRelationsContactUpdate; - - if (mutationData?.errors.length > 0) { - this.errorMessages = mutationData.errors; - return; - } - - const queryArgs = { - query: getGroupContactsQuery, - variables: { groupFullPath: this.groupFullPath }, - }; - - const sourceData = store.readQuery(queryArgs); - - queryArgs.data = produce(sourceData, (draftState) => { - draftState.group.contacts.nodes = [ - ...sourceData.group.contacts.nodes.filter(({ id }) => id !== this.contact?.id), - mutationData.contact, - ]; - }); - - store.writeQuery(queryArgs); - }, - getDrawerHeaderHeight() { - const wrapperEl = document.querySelector('.content-wrapper'); - - if (wrapperEl) { - return `${wrapperEl.offsetTop}px`; - } - - return ''; - }, - }, - i18n: { - createButtonLabel: s__('Crm|Create new contact'), - editButtonLabel: __('Save changes'), - cancel: __('Cancel'), - firstName: s__('Crm|First name'), - lastName: s__('Crm|Last name'), - email: s__('Crm|Email'), - phone: s__('Crm|Phone number (optional)'), - description: s__('Crm|Description (optional)'), - newTitle: s__('Crm|New contact'), - editTitle: s__('Crm|Edit contact'), - somethingWentWrong: __('Something went wrong. Please try again.'), - }, -}; -</script> - -<template> - <gl-drawer - class="gl-drawer-responsive" - :open="drawerOpen" - :header-height="getDrawerHeaderHeight()" - @close="close(false)" - > - <template #title> - <h3>{{ title }}</h3> - </template> - <gl-alert v-if="errorMessages.length" variant="danger" @dismiss="errorMessages = []"> - <ul class="gl-mb-0! gl-ml-5"> - <li v-for="error in errorMessages" :key="error"> - {{ error }} - </li> - </ul> - </gl-alert> - <form @submit.prevent="save"> - <gl-form-group :label="$options.i18n.firstName" label-for="contact-first-name"> - <gl-form-input id="contact-first-name" v-model="firstName" /> - </gl-form-group> - <gl-form-group :label="$options.i18n.lastName" label-for="contact-last-name"> - <gl-form-input id="contact-last-name" v-model="lastName" /> - </gl-form-group> - <gl-form-group :label="$options.i18n.email" label-for="contact-email"> - <gl-form-input id="contact-email" v-model="email" /> - </gl-form-group> - <gl-form-group :label="$options.i18n.phone" label-for="contact-phone"> - <gl-form-input id="contact-phone" v-model="phone" /> - </gl-form-group> - <gl-form-group :label="$options.i18n.description" label-for="contact-description"> - <gl-form-input id="contact-description" v-model="description" /> - </gl-form-group> - <span class="gl-float-right"> - <gl-button data-testid="cancel-button" @click="close(false)"> - {{ $options.i18n.cancel }} - </gl-button> - <gl-button - variant="confirm" - :disabled="invalid" - :loading="submitting" - data-testid="save-contact-button" - type="submit" - >{{ buttonLabel }}</gl-button - > - </span> - </form> - </gl-drawer> -</template> diff --git a/app/assets/javascripts/crm/components/form.vue b/app/assets/javascripts/crm/components/form.vue index b24de1e95e8..4f94898ff63 100644 --- a/app/assets/javascripts/crm/components/form.vue +++ b/app/assets/javascripts/crm/components/form.vue @@ -61,11 +61,6 @@ export default { required: false, default: null, }, - existingModel: { - type: Object, - required: false, - default: () => ({}), - }, additionalCreateParams: { type: Object, required: false, @@ -76,25 +71,42 @@ export default { required: false, default: () => MSG_SAVE_CHANGES, }, + existingId: { + type: String, + required: false, + default: null, + }, }, data() { - const initialModel = this.fields.reduce( - (map, field) => - Object.assign(map, { - [field.name]: this.existingModel ? this.existingModel[field.name] : null, - }), - {}, - ); - return { - model: initialModel, + model: null, submitting: false, errorMessages: [], + records: [], + loading: true, }; }, + apollo: { + records: { + query() { + return this.getQuery.query; + }, + variables() { + return this.getQuery.variables; + }, + update(data) { + this.records = getPropValueByPath(data, this.getQueryNodePath).nodes || []; + this.setInitialModel(); + this.loading = false; + }, + error() { + this.errorMessages = [MSG_ERROR]; + }, + }, + }, computed: { isEditMode() { - return this.existingModel?.id; + return this.existingId; }, isInvalid() { const { fields, model } = this; @@ -115,13 +127,24 @@ export default { ); if (isEditMode) { - return { input: { id: this.existingModel.id, ...variables } }; + return { input: { id: this.existingId, ...variables } }; } return { input: { ...additionalCreateParams, ...variables } }; }, }, methods: { + setInitialModel() { + const existingModel = this.records.find(({ id }) => id === this.existingId); + + this.model = this.fields.reduce( + (map, field) => + Object.assign(map, { + [field.name]: !this.isEditMode || !existingModel ? null : existingModel[field.name], + }), + {}, + ); + }, formatValue(model, field) { if (!isEmpty(model[field.name]) && field.input?.type === 'number') { return parseFloat(model[field.name]); @@ -173,7 +196,7 @@ export default { const sourceData = store.readQuery(getQuery); const newData = produce(sourceData, (draftState) => { - getPropValueByPath(draftState, getQueryNodePath).nodes.push(getFirstPropertyValue(result)); + getPropValueByPath(draftState, getQueryNodePath).nodes.push(this.getPayload(result)); }); store.writeQuery({ @@ -185,6 +208,14 @@ export default { const optionalSuffix = field.required ? '' : ` ${MSG_OPTIONAL}`; return field.label + optionalSuffix; }, + getPayload(data) { + if (!data) return null; + + const keys = Object.keys(data); + if (keys[0] === '__typename') return data[keys[1]]; + + return data[keys[0]]; + }, }, MSG_CANCEL, INDEX_ROUTE_NAME, @@ -192,7 +223,7 @@ export default { </script> <template> - <mounting-portal mount-to="#js-crm-form-portal" append> + <mounting-portal v-if="!loading" mount-to="#js-crm-form-portal" append> <gl-drawer class="gl-drawer-responsive gl-absolute" :open="drawerOpen" @close="close(false)"> <template #title> <h3>{{ title }}</h3> diff --git a/app/assets/javascripts/crm/components/new_organization_form.vue b/app/assets/javascripts/crm/components/new_organization_form.vue deleted file mode 100644 index 3b11edc6935..00000000000 --- a/app/assets/javascripts/crm/components/new_organization_form.vue +++ /dev/null @@ -1,164 +0,0 @@ -<script> -import { GlAlert, GlButton, GlDrawer, GlFormGroup, GlFormInput } from '@gitlab/ui'; -import { produce } from 'immer'; -import { __, s__ } from '~/locale'; -import { convertToGraphQLId } from '~/graphql_shared/utils'; -import { TYPE_GROUP } from '~/graphql_shared/constants'; -import createOrganization from './queries/create_organization.mutation.graphql'; -import getGroupOrganizationsQuery from './queries/get_group_organizations.query.graphql'; - -export default { - components: { - GlAlert, - GlButton, - GlDrawer, - GlFormGroup, - GlFormInput, - }, - inject: ['groupFullPath', 'groupId'], - props: { - drawerOpen: { - type: Boolean, - required: true, - }, - }, - data() { - return { - name: '', - defaultRate: null, - description: '', - submitting: false, - errorMessages: [], - }; - }, - computed: { - invalid() { - return this.name.trim() === ''; - }, - }, - methods: { - save() { - this.submitting = true; - return this.$apollo - .mutate({ - mutation: createOrganization, - variables: { - input: { - groupId: convertToGraphQLId(TYPE_GROUP, this.groupId), - name: this.name, - defaultRate: this.defaultRate ? parseFloat(this.defaultRate) : null, - description: this.description, - }, - }, - update: this.updateCache, - }) - .then(({ data }) => { - if (data.customerRelationsOrganizationCreate.errors.length === 0) this.close(true); - - this.submitting = false; - }) - .catch(() => { - this.errorMessages = [this.$options.i18n.somethingWentWrong]; - this.submitting = false; - }); - }, - close(success) { - this.$emit('close', success); - }, - updateCache(store, { data: { customerRelationsOrganizationCreate } }) { - if (customerRelationsOrganizationCreate.errors.length > 0) { - this.errorMessages = customerRelationsOrganizationCreate.errors; - return; - } - - const variables = { - groupFullPath: this.groupFullPath, - }; - const sourceData = store.readQuery({ - query: getGroupOrganizationsQuery, - variables, - }); - - const data = produce(sourceData, (draftState) => { - draftState.group.organizations.nodes = [ - ...sourceData.group.organizations.nodes, - customerRelationsOrganizationCreate.organization, - ]; - }); - - store.writeQuery({ - query: getGroupOrganizationsQuery, - variables, - data, - }); - }, - getDrawerHeaderHeight() { - const wrapperEl = document.querySelector('.content-wrapper'); - - if (wrapperEl) { - return `${wrapperEl.offsetTop}px`; - } - - return ''; - }, - }, - i18n: { - buttonLabel: s__('Crm|Create organization'), - cancel: __('Cancel'), - name: __('Name'), - defaultRate: s__('Crm|Default rate (optional)'), - description: __('Description (optional)'), - title: s__('Crm|New Organization'), - somethingWentWrong: __('Something went wrong. Please try again.'), - }, -}; -</script> - -<template> - <gl-drawer - class="gl-drawer-responsive" - :open="drawerOpen" - :header-height="getDrawerHeaderHeight()" - @close="close(false)" - > - <template #title> - <h4>{{ $options.i18n.title }}</h4> - </template> - <gl-alert v-if="errorMessages.length" variant="danger" @dismiss="errorMessages = []"> - <ul class="gl-mb-0! gl-ml-5"> - <li v-for="error in errorMessages" :key="error"> - {{ error }} - </li> - </ul> - </gl-alert> - <form @submit.prevent="save"> - <gl-form-group :label="$options.i18n.name" label-for="organization-name"> - <gl-form-input id="organization-name" v-model="name" /> - </gl-form-group> - <gl-form-group :label="$options.i18n.defaultRate" label-for="organization-default-rate"> - <gl-form-input - id="organization-default-rate" - v-model="defaultRate" - type="number" - step="0.01" - /> - </gl-form-group> - <gl-form-group :label="$options.i18n.description" label-for="organization-description"> - <gl-form-input id="organization-description" v-model="description" /> - </gl-form-group> - <span class="gl-float-right"> - <gl-button data-testid="cancel-button" @click="close(false)"> - {{ $options.i18n.cancel }} - </gl-button> - <gl-button - variant="confirm" - :disabled="invalid" - :loading="submitting" - data-testid="create-new-organization-button" - type="submit" - >{{ $options.i18n.buttonLabel }}</gl-button - > - </span> - </form> - </gl-drawer> -</template> diff --git a/app/assets/javascripts/crm/contacts_bundle.js b/app/assets/javascripts/crm/contacts/bundle.js index f49ec64210f..f49ec64210f 100644 --- a/app/assets/javascripts/crm/contacts_bundle.js +++ b/app/assets/javascripts/crm/contacts/bundle.js diff --git a/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue b/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue new file mode 100644 index 00000000000..58eaabfbb7f --- /dev/null +++ b/app/assets/javascripts/crm/contacts/components/contact_form_wrapper.vue @@ -0,0 +1,78 @@ +<script> +import { s__, __ } from '~/locale'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { TYPE_CRM_CONTACT, TYPE_GROUP } from '~/graphql_shared/constants'; +import ContactForm from '../../components/form.vue'; +import getGroupContactsQuery from './graphql/get_group_contacts.query.graphql'; +import createContactMutation from './graphql/create_contact.mutation.graphql'; +import updateContactMutation from './graphql/update_contact.mutation.graphql'; + +export default { + components: { + ContactForm, + }, + inject: ['groupFullPath', 'groupId'], + props: { + isEditMode: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + contactGraphQLId() { + if (!this.isEditMode) return null; + + return convertToGraphQLId(TYPE_CRM_CONTACT, this.$route.params.id); + }, + groupGraphQLId() { + return convertToGraphQLId(TYPE_GROUP, this.groupId); + }, + mutation() { + if (this.isEditMode) return updateContactMutation; + + return createContactMutation; + }, + getQuery() { + return { + query: getGroupContactsQuery, + variables: { groupFullPath: this.groupFullPath }, + }; + }, + title() { + if (this.isEditMode) return s__('Crm|Edit contact'); + + return s__('Crm|New contact'); + }, + successMessage() { + if (this.isEditMode) return s__('Crm|Contact has been updated.'); + + return s__('Crm|Contact has been added.'); + }, + additionalCreateParams() { + return { groupId: this.groupGraphQLId }; + }, + }, + fields: [ + { name: 'firstName', label: __('First name'), required: true }, + { name: 'lastName', label: __('Last name'), required: true }, + { name: 'email', label: __('Email'), required: true }, + { name: 'phone', label: __('Phone') }, + { name: 'description', label: __('Description') }, + ], +}; +</script> + +<template> + <contact-form + :drawer-open="true" + :get-query="getQuery" + get-query-node-path="group.contacts" + :mutation="mutation" + :additional-create-params="additionalCreateParams" + :existing-id="contactGraphQLId" + :fields="$options.fields" + :title="title" + :success-message="successMessage" + /> +</template> diff --git a/app/assets/javascripts/crm/components/contacts_root.vue b/app/assets/javascripts/crm/contacts/components/contacts_root.vue index 178ce84c64d..17be3800256 100644 --- a/app/assets/javascripts/crm/components/contacts_root.vue +++ b/app/assets/javascripts/crm/contacts/components/contacts_root.vue @@ -2,11 +2,9 @@ import { GlAlert, GlButton, GlLoadingIcon, GlTable, GlTooltipDirective } from '@gitlab/ui'; import { parseBoolean } from '~/lib/utils/common_utils'; import { s__, __ } from '~/locale'; -import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; -import { TYPE_CRM_CONTACT } from '~/graphql_shared/constants'; -import { INDEX_ROUTE_NAME, NEW_ROUTE_NAME, EDIT_ROUTE_NAME } from '../constants'; -import getGroupContactsQuery from './queries/get_group_contacts.query.graphql'; -import ContactForm from './contact_form.vue'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { EDIT_ROUTE_NAME, NEW_ROUTE_NAME } from '../../constants'; +import getGroupContactsQuery from './graphql/get_group_contacts.query.graphql'; export default { components: { @@ -14,12 +12,11 @@ export default { GlButton, GlLoadingIcon, GlTable, - ContactForm, }, directives: { GlTooltip: GlTooltipDirective, }, - inject: ['groupFullPath', 'groupIssuesPath', 'canAdminCrmContact'], + inject: ['canAdminCrmContact', 'groupFullPath', 'groupIssuesPath'], data() { return { contacts: [], @@ -48,50 +45,20 @@ export default { isLoading() { return this.$apollo.queries.contacts.loading; }, - showNewForm() { - return this.$route.name === NEW_ROUTE_NAME; - }, - showEditForm() { - return !this.isLoading && this.$route.name === EDIT_ROUTE_NAME; - }, canAdmin() { return parseBoolean(this.canAdminCrmContact); }, - editingContact() { - return this.contacts.find( - (contact) => contact.id === convertToGraphQLId(TYPE_CRM_CONTACT, this.$route.params.id), - ); - }, }, methods: { extractContacts(data) { const contacts = data?.group?.contacts?.nodes || []; return contacts.slice().sort((a, b) => a.firstName.localeCompare(b.firstName)); }, - displayNewForm() { - if (this.showNewForm) return; - - this.$router.push({ name: NEW_ROUTE_NAME }); - }, - hideNewForm(success) { - if (success) this.$toast.show(s__('Crm|Contact has been added')); - - this.$router.replace({ name: INDEX_ROUTE_NAME }); - }, - hideEditForm(success) { - if (success) this.$toast.show(s__('Crm|Contact has been updated')); - - this.editingContactId = 0; - this.$router.replace({ name: INDEX_ROUTE_NAME }); - }, getIssuesPath(path, value) { return `${path}?scope=all&state=opened&crm_contact_id=${value}`; }, - edit(value) { - if (this.showEditForm) return; - - this.editingContactId = value; - this.$router.push({ name: EDIT_ROUTE_NAME, params: { id: value } }); + getEditRoute(id) { + return { name: this.$options.EDIT_ROUTE_NAME, params: { id } }; }, }, fields: [ @@ -119,10 +86,12 @@ export default { emptyText: s__('Crm|No contacts found'), issuesButtonLabel: __('View issues'), editButtonLabel: __('Edit'), - title: s__('Crm|Customer Relations Contacts'), + title: s__('Crm|Customer relations contacts'), newContact: s__('Crm|New contact'), errorText: __('Something went wrong. Please try again.'), }, + EDIT_ROUTE_NAME, + NEW_ROUTE_NAME, }; </script> @@ -137,24 +106,15 @@ export default { <h2 class="gl-font-size-h2 gl-my-0"> {{ $options.i18n.title }} </h2> - <div class="gl-display-none gl-md-display-flex gl-align-items-center gl-justify-content-end"> - <gl-button - v-if="canAdmin" - variant="confirm" - data-testid="new-contact-button" - @click="displayNewForm" - > - {{ $options.i18n.newContact }} - </gl-button> + <div v-if="canAdmin"> + <router-link :to="{ name: $options.NEW_ROUTE_NAME }"> + <gl-button variant="confirm" data-testid="new-contact-button"> + {{ $options.i18n.newContact }} + </gl-button> + </router-link> </div> </div> - <contact-form v-if="showNewForm" :drawer-open="showNewForm" @close="hideNewForm" /> - <contact-form - v-if="showEditForm" - :contact="editingContact" - :drawer-open="showEditForm" - @close="hideEditForm" - /> + <router-view /> <gl-loading-icon v-if="isLoading" class="gl-mt-5" size="lg" /> <gl-table v-else @@ -164,23 +124,24 @@ export default { :empty-text="$options.i18n.emptyText" show-empty > - <template #cell(id)="data"> + <template #cell(id)="{ value: id }"> <gl-button v-gl-tooltip.hover.bottom="$options.i18n.issuesButtonLabel" class="gl-mr-3" data-testid="issues-link" icon="issues" :aria-label="$options.i18n.issuesButtonLabel" - :href="getIssuesPath(groupIssuesPath, data.value)" - /> - <gl-button - v-if="canAdmin" - v-gl-tooltip.hover.bottom="$options.i18n.editButtonLabel" - data-testid="edit-contact-button" - icon="pencil" - :aria-label="$options.i18n.editButtonLabel" - @click="edit(data.value)" + :href="getIssuesPath(groupIssuesPath, id)" /> + <router-link :to="getEditRoute(id)"> + <gl-button + v-if="canAdmin" + v-gl-tooltip.hover.bottom="$options.i18n.editButtonLabel" + data-testid="edit-contact-button" + icon="pencil" + :aria-label="$options.i18n.editButtonLabel" + /> + </router-link> </template> </gl-table> </div> diff --git a/app/assets/javascripts/crm/components/queries/create_contact.mutation.graphql b/app/assets/javascripts/crm/contacts/components/graphql/create_contact.mutation.graphql index e0192459609..e0192459609 100644 --- a/app/assets/javascripts/crm/components/queries/create_contact.mutation.graphql +++ b/app/assets/javascripts/crm/contacts/components/graphql/create_contact.mutation.graphql diff --git a/app/assets/javascripts/crm/components/queries/crm_contact_fields.fragment.graphql b/app/assets/javascripts/crm/contacts/components/graphql/crm_contact_fields.fragment.graphql index cef4083b446..cef4083b446 100644 --- a/app/assets/javascripts/crm/components/queries/crm_contact_fields.fragment.graphql +++ b/app/assets/javascripts/crm/contacts/components/graphql/crm_contact_fields.fragment.graphql diff --git a/app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql b/app/assets/javascripts/crm/contacts/components/graphql/get_group_contacts.query.graphql index 2a8150e42e3..2a8150e42e3 100644 --- a/app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql +++ b/app/assets/javascripts/crm/contacts/components/graphql/get_group_contacts.query.graphql diff --git a/app/assets/javascripts/crm/components/queries/update_contact.mutation.graphql b/app/assets/javascripts/crm/contacts/components/graphql/update_contact.mutation.graphql index f55f6a10e0a..f55f6a10e0a 100644 --- a/app/assets/javascripts/crm/components/queries/update_contact.mutation.graphql +++ b/app/assets/javascripts/crm/contacts/components/graphql/update_contact.mutation.graphql diff --git a/app/assets/javascripts/crm/routes.js b/app/assets/javascripts/crm/contacts/routes.js index 12aa17d73b6..18768e1c775 100644 --- a/app/assets/javascripts/crm/routes.js +++ b/app/assets/javascripts/crm/contacts/routes.js @@ -1,4 +1,5 @@ -import { INDEX_ROUTE_NAME, NEW_ROUTE_NAME, EDIT_ROUTE_NAME } from './constants'; +import { INDEX_ROUTE_NAME, NEW_ROUTE_NAME, EDIT_ROUTE_NAME } from '../constants'; +import ContactFormWrapper from './components/contact_form_wrapper.vue'; export default [ { @@ -8,9 +9,12 @@ export default [ { name: NEW_ROUTE_NAME, path: '/new', + component: ContactFormWrapper, }, { name: EDIT_ROUTE_NAME, path: '/:id/edit', + component: ContactFormWrapper, + props: { isEditMode: true }, }, ]; diff --git a/app/assets/javascripts/crm/organizations_bundle.js b/app/assets/javascripts/crm/organizations/bundle.js index 828d7cd426c..828d7cd426c 100644 --- a/app/assets/javascripts/crm/organizations_bundle.js +++ b/app/assets/javascripts/crm/organizations/bundle.js diff --git a/app/assets/javascripts/crm/components/queries/create_organization.mutation.graphql b/app/assets/javascripts/crm/organizations/components/graphql/create_organization.mutation.graphql index 2cc7e53ee9b..2cc7e53ee9b 100644 --- a/app/assets/javascripts/crm/components/queries/create_organization.mutation.graphql +++ b/app/assets/javascripts/crm/organizations/components/graphql/create_organization.mutation.graphql diff --git a/app/assets/javascripts/crm/components/queries/crm_organization_fields.fragment.graphql b/app/assets/javascripts/crm/organizations/components/graphql/crm_organization_fields.fragment.graphql index 4adc5742d3a..4adc5742d3a 100644 --- a/app/assets/javascripts/crm/components/queries/crm_organization_fields.fragment.graphql +++ b/app/assets/javascripts/crm/organizations/components/graphql/crm_organization_fields.fragment.graphql diff --git a/app/assets/javascripts/crm/components/queries/get_group_organizations.query.graphql b/app/assets/javascripts/crm/organizations/components/graphql/get_group_organizations.query.graphql index e8d8109431e..e8d8109431e 100644 --- a/app/assets/javascripts/crm/components/queries/get_group_organizations.query.graphql +++ b/app/assets/javascripts/crm/organizations/components/graphql/get_group_organizations.query.graphql diff --git a/app/assets/javascripts/crm/organizations/components/graphql/update_organization.mutation.graphql b/app/assets/javascripts/crm/organizations/components/graphql/update_organization.mutation.graphql new file mode 100644 index 00000000000..a4c46d1f0fa --- /dev/null +++ b/app/assets/javascripts/crm/organizations/components/graphql/update_organization.mutation.graphql @@ -0,0 +1,10 @@ +#import "./crm_organization_fields.fragment.graphql" + +mutation updateOrganization($input: CustomerRelationsOrganizationUpdateInput!) { + customerRelationsOrganizationUpdate(input: $input) { + organization { + ...OrganizationFragment + } + errors + } +} diff --git a/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue b/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue new file mode 100644 index 00000000000..38468e1f4e4 --- /dev/null +++ b/app/assets/javascripts/crm/organizations/components/organization_form_wrapper.vue @@ -0,0 +1,80 @@ +<script> +import { s__, __ } from '~/locale'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { TYPE_CRM_ORGANIZATION, TYPE_GROUP } from '~/graphql_shared/constants'; +import OrganizationForm from '../../components/form.vue'; +import getGroupOrganizationsQuery from './graphql/get_group_organizations.query.graphql'; +import createOrganizationMutation from './graphql/create_organization.mutation.graphql'; +import updateOrganizationMutation from './graphql/update_organization.mutation.graphql'; + +export default { + components: { + OrganizationForm, + }, + inject: ['groupFullPath', 'groupId'], + props: { + isEditMode: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + organizationGraphQLId() { + if (!this.isEditMode) return null; + + return convertToGraphQLId(TYPE_CRM_ORGANIZATION, this.$route.params.id); + }, + groupGraphQLId() { + return convertToGraphQLId(TYPE_GROUP, this.groupId); + }, + mutation() { + if (this.isEditMode) return updateOrganizationMutation; + + return createOrganizationMutation; + }, + getQuery() { + return { + query: getGroupOrganizationsQuery, + variables: { groupFullPath: this.groupFullPath }, + }; + }, + title() { + if (this.isEditMode) return s__('Crm|Edit organization'); + + return s__('Crm|New organization'); + }, + successMessage() { + if (this.isEditMode) return s__('Crm|Organization has been updated.'); + + return s__('Crm|Organization has been added.'); + }, + additionalCreateParams() { + return { groupId: this.groupGraphQLId }; + }, + }, + fields: [ + { name: 'name', label: __('Name'), required: true }, + { + name: 'defaultRate', + label: s__('Crm|Default rate'), + input: { type: 'number', step: '0.01' }, + }, + { name: 'description', label: __('Description') }, + ], +}; +</script> + +<template> + <organization-form + :drawer-open="true" + :get-query="getQuery" + get-query-node-path="group.organizations" + :mutation="mutation" + :additional-create-params="additionalCreateParams" + :existing-id="organizationGraphQLId" + :fields="$options.fields" + :title="title" + :success-message="successMessage" + /> +</template> diff --git a/app/assets/javascripts/crm/components/organizations_root.vue b/app/assets/javascripts/crm/organizations/components/organizations_root.vue index 9370c6377e9..522e29eb2af 100644 --- a/app/assets/javascripts/crm/components/organizations_root.vue +++ b/app/assets/javascripts/crm/organizations/components/organizations_root.vue @@ -3,9 +3,8 @@ import { GlAlert, GlButton, GlLoadingIcon, GlTable, GlTooltipDirective } from '@ import { parseBoolean } from '~/lib/utils/common_utils'; import { s__, __ } from '~/locale'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import { INDEX_ROUTE_NAME, NEW_ROUTE_NAME } from '../constants'; -import getGroupOrganizationsQuery from './queries/get_group_organizations.query.graphql'; -import NewOrganizationForm from './new_organization_form.vue'; +import { EDIT_ROUTE_NAME, NEW_ROUTE_NAME } from '../../constants'; +import getGroupOrganizationsQuery from './graphql/get_group_organizations.query.graphql'; export default { components: { @@ -13,7 +12,6 @@ export default { GlButton, GlLoadingIcon, GlTable, - NewOrganizationForm, }, directives: { GlTooltip: GlTooltipDirective, @@ -21,8 +19,8 @@ export default { inject: ['canAdminCrmOrganization', 'groupFullPath', 'groupIssuesPath'], data() { return { - error: false, organizations: [], + error: false, }; }, apollo: { @@ -47,10 +45,7 @@ export default { isLoading() { return this.$apollo.queries.organizations.loading; }, - showNewForm() { - return this.$route.name === NEW_ROUTE_NAME; - }, - canCreateNew() { + canAdmin() { return parseBoolean(this.canAdminCrmOrganization); }, }, @@ -62,15 +57,8 @@ export default { getIssuesPath(path, value) { return `${path}?scope=all&state=opened&crm_organization_id=${value}`; }, - displayNewForm() { - if (this.showNewForm) return; - - this.$router.push({ name: NEW_ROUTE_NAME }); - }, - hideNewForm(success) { - if (success) this.$toast.show(this.$options.i18n.organizationAdded); - - this.$router.replace({ name: INDEX_ROUTE_NAME }); + getEditRoute(id) { + return { name: this.$options.EDIT_ROUTE_NAME, params: { id } }; }, }, fields: [ @@ -79,7 +67,7 @@ export default { { key: 'description', sortable: true }, { key: 'id', - label: __('Issues'), + label: '', formatter: (id) => { return getIdFromGraphQLId(id); }, @@ -88,11 +76,13 @@ export default { i18n: { emptyText: s__('Crm|No organizations found'), issuesButtonLabel: __('View issues'), - title: s__('Crm|Customer Relations Organizations'), + editButtonLabel: __('Edit'), + title: s__('Crm|Customer relations organizations'), newOrganization: s__('Crm|New organization'), errorText: __('Something went wrong. Please try again.'), - organizationAdded: s__('Crm|Organization has been added'), }, + EDIT_ROUTE_NAME, + NEW_ROUTE_NAME, }; </script> @@ -108,15 +98,17 @@ export default { {{ $options.i18n.title }} </h2> <div - v-if="canCreateNew" + v-if="canAdmin" class="gl-display-none gl-md-display-flex gl-align-items-center gl-justify-content-end" > - <gl-button variant="confirm" data-testid="new-organization-button" @click="displayNewForm"> - {{ $options.i18n.newOrganization }} - </gl-button> + <router-link :to="{ name: $options.NEW_ROUTE_NAME }"> + <gl-button variant="confirm" data-testid="new-organization-button"> + {{ $options.i18n.newOrganization }} + </gl-button> + </router-link> </div> </div> - <new-organization-form v-if="showNewForm" :drawer-open="showNewForm" @close="hideNewForm" /> + <router-view /> <gl-loading-icon v-if="isLoading" class="gl-mt-5" size="lg" /> <gl-table v-else @@ -126,14 +118,24 @@ export default { :empty-text="$options.i18n.emptyText" show-empty > - <template #cell(id)="data"> + <template #cell(id)="{ value: id }"> <gl-button v-gl-tooltip.hover.bottom="$options.i18n.issuesButtonLabel" + class="gl-mr-3" data-testid="issues-link" icon="issues" :aria-label="$options.i18n.issuesButtonLabel" - :href="getIssuesPath(groupIssuesPath, data.value)" + :href="getIssuesPath(groupIssuesPath, id)" /> + <router-link :to="getEditRoute(id)"> + <gl-button + v-if="canAdmin" + v-gl-tooltip.hover.bottom="$options.i18n.editButtonLabel" + data-testid="edit-organization-button" + icon="pencil" + :aria-label="$options.i18n.editButtonLabel" + /> + </router-link> </template> </gl-table> </div> diff --git a/app/assets/javascripts/crm/organizations/routes.js b/app/assets/javascripts/crm/organizations/routes.js new file mode 100644 index 00000000000..85bd3b32877 --- /dev/null +++ b/app/assets/javascripts/crm/organizations/routes.js @@ -0,0 +1,20 @@ +import { INDEX_ROUTE_NAME, NEW_ROUTE_NAME, EDIT_ROUTE_NAME } from '../constants'; +import OrganizationFormWrapper from './components/organization_form_wrapper.vue'; + +export default [ + { + name: INDEX_ROUTE_NAME, + path: '/', + }, + { + name: NEW_ROUTE_NAME, + path: '/new', + component: OrganizationFormWrapper, + }, + { + name: EDIT_ROUTE_NAME, + path: '/:id/edit', + component: OrganizationFormWrapper, + props: { isEditMode: true }, + }, +]; |