diff options
Diffstat (limited to 'app/assets/javascripts/sidebar/components/time_tracking/create_timelog_form.vue')
-rw-r--r-- | app/assets/javascripts/sidebar/components/time_tracking/create_timelog_form.vue | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/create_timelog_form.vue b/app/assets/javascripts/sidebar/components/time_tracking/create_timelog_form.vue new file mode 100644 index 00000000000..ec8e1ee9952 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/time_tracking/create_timelog_form.vue @@ -0,0 +1,227 @@ +<script> +import { + GlFormGroup, + GlFormInput, + GlDatepicker, + GlFormTextarea, + GlModal, + GlAlert, + GlLink, + GlSprintf, +} from '@gitlab/ui'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { formatDate } from '~/lib/utils/datetime_utility'; +import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/graphql_shared/constants'; +import { joinPaths } from '~/lib/utils/url_utility'; +import { s__ } from '~/locale'; +import createTimelogMutation from '../../queries/create_timelog.mutation.graphql'; +import { CREATE_TIMELOG_MODAL_ID } from './constants'; + +export default { + components: { + GlDatepicker, + GlFormGroup, + GlFormInput, + GlFormTextarea, + GlModal, + GlAlert, + GlLink, + GlSprintf, + }, + inject: ['issuableType'], + props: { + issuableId: { + type: String, + required: true, + }, + }, + data() { + return { + timeSpent: '', + spentAt: null, + summary: '', + isLoading: false, + saveError: '', + }; + }, + computed: { + submitDisabled() { + return this.isLoading || this.timeSpent.length === 0; + }, + primaryProps() { + return { + text: s__('CreateTimelogForm|Save'), + attributes: [ + { + variant: 'confirm', + disabled: this.submitDisabled, + loading: this.isLoading, + }, + ], + }; + }, + cancelProps() { + return { + text: s__('CreateTimelogForm|Cancel'), + }; + }, + timeTrackingDocsPath() { + return joinPaths(gon.relative_url_root || '', '/help/user/project/time_tracking.md'); + }, + issuableTypeName() { + return this.isIssue() + ? s__('CreateTimelogForm|issue') + : s__('CreateTimelogForm|merge request'); + }, + }, + methods: { + resetModal() { + this.isLoading = false; + this.timeSpent = ''; + this.spentAt = null; + this.summary = ''; + this.saveError = ''; + }, + close() { + this.resetModal(); + this.$refs.modal.close(); + }, + registerTimeSpent(event) { + event.preventDefault(); + + if (this.timeSpent.length === 0) { + return; + } + + this.isLoading = true; + this.saveError = ''; + + this.$apollo + .mutate({ + mutation: createTimelogMutation, + variables: { + input: { + timeSpent: this.timeSpent, + spentAt: this.spentAt + ? formatDate(this.spentAt, 'isoDateTime') + : formatDate(Date.now(), 'isoDateTime'), + summary: this.summary, + issuableId: this.getIssuableId(), + }, + }, + }) + .then(({ data }) => { + if (data.timelogCreate?.errors.length) { + this.saveError = data.timelogCreate.errors[0].message || data.timelogCreate.errors[0]; + } else { + this.close(); + } + }) + .catch((error) => { + this.saveError = + error?.message || + s__('CreateTimelogForm|An error occurred while saving the time entry.'); + }) + .finally(() => { + this.isLoading = false; + }); + }, + isIssue() { + return this.issuableType === 'issue'; + }, + getGraphQLEntityType() { + return this.isIssue() ? TYPE_ISSUE : TYPE_MERGE_REQUEST; + }, + updateSpentAtDate(val) { + this.spentAt = val; + }, + getIssuableId() { + return convertToGraphQLId(this.getGraphQLEntityType(), this.issuableId); + }, + }, + CREATE_TIMELOG_MODAL_ID, +}; +</script> + +<template> + <gl-modal + ref="modal" + :title="s__('CreateTimelogForm|Add time entry')" + :modal-id="$options.CREATE_TIMELOG_MODAL_ID" + size="sm" + data-testid="create-timelog-modal" + :action-primary="primaryProps" + :action-cancel="cancelProps" + @primary="registerTimeSpent" + @cancel="close" + @close="close" + @hide="close" + > + <p data-testid="timetracking-docs-link"> + <gl-sprintf + :message=" + s__( + 'CreateTimelogForm|Track time spent on this %{issuableTypeNameStart}%{issuableTypeNameEnd}. %{timeTrackingDocsLinkStart}%{timeTrackingDocsLinkEnd}', + ) + " + > + <template #issuableTypeName>{{ issuableTypeName }}</template> + <template #timeTrackingDocsLink> + <gl-link :href="timeTrackingDocsPath" target="_blank">{{ + s__('CreateTimelogForm|How do I track and estimate time?') + }}</gl-link> + </template> + </gl-sprintf> + </p> + <form + class="gl-display-flex gl-flex-direction-column js-quick-submit" + @submit.prevent="registerTimeSpent" + > + <div class="gl-display-flex gl-gap-3"> + <gl-form-group + key="time-spent" + label-for="time-spent" + :label="s__(`CreateTimelogForm|Time spent`)" + :description="s__(`CreateTimelogForm|Example: 1h 30m`)" + > + <gl-form-input + id="time-spent" + ref="timeSpent" + v-model="timeSpent" + class="gl-form-input-sm" + autocomplete="off" + /> + </gl-form-group> + <gl-form-group + key="spent-at" + optional + label-for="spent-at" + :label="s__(`CreateTimelogForm|Spent at`)" + > + <gl-datepicker + :target="null" + :value="spentAt" + show-clear-button + autocomplete="off" + size="small" + @input="updateSpentAtDate" + @clear="updateSpentAtDate(null)" + /> + </gl-form-group> + </div> + <gl-form-group + :label="s__('CreateTimelogForm|Summary')" + optional + label-for="summary" + class="gl-mb-0" + > + <gl-form-textarea id="summary" v-model="summary" rows="3" :no-resize="true" /> + </gl-form-group> + <gl-alert v-if="saveError" variant="danger" class="gl-mt-5" :dismissible="false"> + {{ saveError }} + </gl-alert> + <!-- This is needed to have the quick-submit behaviour (with Ctrl + Enter or Cmd + Enter) --> + <input type="submit" hidden /> + </form> + </gl-modal> +</template> |