mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 12:08:01 +00:00
274 lines
7.4 KiB
Vue
274 lines
7.4 KiB
Vue
<script setup>
|
|
import { reactive, computed, onMounted, ref } from 'vue';
|
|
import { useVuelidate } from '@vuelidate/core';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useTrack } from 'dashboard/composables';
|
|
import { useAlert } from 'dashboard/composables';
|
|
import LinearAPI from 'dashboard/api/integrations/linear';
|
|
import validations from './validations';
|
|
import { parseLinearAPIErrorResponse } from 'dashboard/store/utils/api';
|
|
import SearchableDropdown from './SearchableDropdown.vue';
|
|
import { LINEAR_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
|
import Button from 'dashboard/components-next/button/Button.vue';
|
|
|
|
const props = defineProps({
|
|
conversationId: {
|
|
type: [Number, String],
|
|
required: true,
|
|
},
|
|
title: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits(['close']);
|
|
const { t } = useI18n();
|
|
|
|
const teams = ref([]);
|
|
const assignees = ref([]);
|
|
const projects = ref([]);
|
|
const labels = ref([]);
|
|
const statuses = ref([]);
|
|
|
|
const priorities = [
|
|
{ id: 0, name: 'No priority' },
|
|
{ id: 1, name: 'Urgent' },
|
|
{ id: 2, name: 'High' },
|
|
{ id: 3, name: 'Normal' },
|
|
{ id: 4, name: 'Low' },
|
|
];
|
|
|
|
const statusDesiredOrder = [
|
|
'Backlog',
|
|
'Todo',
|
|
'In Progress',
|
|
'Done',
|
|
'Canceled',
|
|
];
|
|
|
|
const isCreating = ref(false);
|
|
const inputStyles = { borderRadius: '0.75rem', fontSize: '0.875rem' };
|
|
|
|
const formState = reactive({
|
|
title: '',
|
|
description: '',
|
|
teamId: '',
|
|
assigneeId: '',
|
|
labelId: '',
|
|
stateId: '',
|
|
priority: '',
|
|
projectId: '',
|
|
});
|
|
const v$ = useVuelidate(validations, formState);
|
|
|
|
const isSubmitDisabled = computed(
|
|
() => v$.value.title.$invalid || isCreating.value
|
|
);
|
|
const nameError = computed(() =>
|
|
v$.value.title.$error
|
|
? t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.TITLE.REQUIRED_ERROR')
|
|
: ''
|
|
);
|
|
const teamError = computed(() =>
|
|
v$.value.teamId.$error
|
|
? t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.TEAM.REQUIRED_ERROR')
|
|
: ''
|
|
);
|
|
|
|
const dropdowns = computed(() => {
|
|
return [
|
|
{
|
|
type: 'teamId',
|
|
label: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.TEAM.LABEL',
|
|
items: teams.value,
|
|
placeholder: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.TEAM.SEARCH',
|
|
error: teamError.value,
|
|
},
|
|
{
|
|
type: 'assigneeId',
|
|
label: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.ASSIGNEE.LABEL',
|
|
items: assignees.value,
|
|
placeholder:
|
|
'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.ASSIGNEE.SEARCH',
|
|
error: '',
|
|
},
|
|
{
|
|
type: 'labelId',
|
|
label: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.LABEL.LABEL',
|
|
items: labels.value,
|
|
placeholder: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.LABEL.SEARCH',
|
|
error: '',
|
|
},
|
|
{
|
|
type: 'priority',
|
|
label: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.PRIORITY.LABEL',
|
|
items: priorities,
|
|
placeholder:
|
|
'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.PRIORITY.SEARCH',
|
|
error: '',
|
|
},
|
|
{
|
|
type: 'projectId',
|
|
label: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.PROJECT.LABEL',
|
|
items: projects.value,
|
|
placeholder:
|
|
'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.PROJECT.SEARCH',
|
|
error: '',
|
|
},
|
|
{
|
|
type: 'stateId',
|
|
label: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.STATUS.LABEL',
|
|
items: statuses.value,
|
|
placeholder: 'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.STATUS.SEARCH',
|
|
error: '',
|
|
},
|
|
];
|
|
});
|
|
|
|
const onClose = () => emit('close');
|
|
|
|
const getTeams = async () => {
|
|
try {
|
|
const response = await LinearAPI.getTeams();
|
|
teams.value = response.data;
|
|
} catch (error) {
|
|
const errorMessage = parseLinearAPIErrorResponse(
|
|
error,
|
|
t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.LOADING_TEAM_ERROR')
|
|
);
|
|
useAlert(errorMessage);
|
|
}
|
|
};
|
|
|
|
const getTeamEntities = async () => {
|
|
try {
|
|
const response = await LinearAPI.getTeamEntities(formState.teamId);
|
|
assignees.value = response.data.users;
|
|
labels.value = response.data.labels;
|
|
projects.value = response.data.projects;
|
|
statuses.value = statusDesiredOrder
|
|
.map(name => response.data.states.find(status => status.name === name))
|
|
.filter(Boolean);
|
|
} catch (error) {
|
|
const errorMessage = parseLinearAPIErrorResponse(
|
|
error,
|
|
t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.LOADING_TEAM_ENTITIES_ERROR')
|
|
);
|
|
useAlert(errorMessage);
|
|
}
|
|
};
|
|
|
|
const onChange = (item, type) => {
|
|
formState[type] = item.id;
|
|
if (type === 'teamId') {
|
|
formState.assigneeId = '';
|
|
formState.stateId = '';
|
|
formState.labelId = '';
|
|
formState.projectId = '';
|
|
getTeamEntities();
|
|
}
|
|
};
|
|
|
|
const createIssue = async () => {
|
|
v$.value.$touch();
|
|
if (v$.value.$invalid) return;
|
|
|
|
const payload = {
|
|
team_id: formState.teamId,
|
|
title: formState.title,
|
|
description: formState.description || undefined,
|
|
assignee_id: formState.assigneeId || undefined,
|
|
project_id: formState.projectId || undefined,
|
|
state_id: formState.stateId || undefined,
|
|
priority: formState.priority || undefined,
|
|
label_ids: formState.labelId ? [formState.labelId] : undefined,
|
|
conversation_id: props.conversationId,
|
|
};
|
|
|
|
try {
|
|
isCreating.value = true;
|
|
const response = await LinearAPI.createIssue(payload);
|
|
const { identifier: issueIdentifier } = response.data;
|
|
await LinearAPI.link_issue(
|
|
props.conversationId,
|
|
issueIdentifier,
|
|
props.title
|
|
);
|
|
useAlert(t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.CREATE_SUCCESS'));
|
|
useTrack(LINEAR_EVENTS.CREATE_ISSUE);
|
|
onClose();
|
|
} catch (error) {
|
|
const errorMessage = parseLinearAPIErrorResponse(
|
|
error,
|
|
t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.CREATE_ERROR')
|
|
);
|
|
useAlert(errorMessage);
|
|
} finally {
|
|
isCreating.value = false;
|
|
}
|
|
};
|
|
|
|
onMounted(getTeams);
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<woot-input
|
|
v-model="formState.title"
|
|
:class="{ error: v$.title.$error }"
|
|
class="w-full"
|
|
:styles="{ ...inputStyles, padding: '0.375rem 0.75rem' }"
|
|
:label="$t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.TITLE.LABEL')"
|
|
:placeholder="
|
|
$t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.TITLE.PLACEHOLDER')
|
|
"
|
|
:error="nameError"
|
|
@input="v$.title.$touch"
|
|
/>
|
|
<label>
|
|
{{ $t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.DESCRIPTION.LABEL') }}
|
|
<textarea
|
|
v-model="formState.description"
|
|
:style="{ ...inputStyles, padding: '0.5rem 0.75rem' }"
|
|
rows="3"
|
|
class="text-sm"
|
|
:placeholder="
|
|
$t(
|
|
'INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.FORM.DESCRIPTION.PLACEHOLDER'
|
|
)
|
|
"
|
|
/>
|
|
</label>
|
|
<div class="flex flex-col gap-4">
|
|
<SearchableDropdown
|
|
v-for="dropdown in dropdowns"
|
|
:key="dropdown.type"
|
|
:type="dropdown.type"
|
|
:value="formState[dropdown.type]"
|
|
:label="$t(dropdown.label)"
|
|
:items="dropdown.items"
|
|
:placeholder="$t(dropdown.placeholder)"
|
|
:error="dropdown.error"
|
|
@change="onChange"
|
|
/>
|
|
</div>
|
|
<div class="flex items-center justify-end w-full gap-2 mt-8">
|
|
<Button
|
|
faded
|
|
slate
|
|
type="reset"
|
|
:label="$t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.CANCEL')"
|
|
@click.prevent="onClose"
|
|
/>
|
|
<Button
|
|
type="submit"
|
|
:label="$t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.CREATE')"
|
|
:disabled="isSubmitDisabled"
|
|
:is-loading="isCreating"
|
|
@click.prevent="createIssue"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|