feat: complete basic front end
This commit is contained in:
@@ -72,7 +72,7 @@
|
||||
>
|
||||
<SLA-card-label v-if="hasSlaPolicyId" :chat="chat" show-extended-info />
|
||||
<linear
|
||||
v-if="isLinearIntegrationEnabled"
|
||||
v-if="isLinearIntegrationEnabled && isLinearFeatureEnabled"
|
||||
:conversation-id="currentChat.id"
|
||||
/>
|
||||
<more-actions :conversation-id="currentChat.id" />
|
||||
@@ -130,6 +130,7 @@ export default {
|
||||
currentChat: 'getSelectedChat',
|
||||
accountId: 'getCurrentAccountId',
|
||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||
appIntegrations: 'integrations/getAppIntegrations',
|
||||
}),
|
||||
chatMetadata() {
|
||||
return this.chat.meta;
|
||||
@@ -188,6 +189,11 @@ export default {
|
||||
return this.chat?.sla_policy_id;
|
||||
},
|
||||
isLinearIntegrationEnabled() {
|
||||
return this.appIntegrations.find(
|
||||
integration => integration.id === 'linear' && !!integration.hooks.length
|
||||
);
|
||||
},
|
||||
isLinearFeatureEnabled() {
|
||||
return this.isFeatureEnabledonAccount(
|
||||
this.accountId,
|
||||
FEATURE_FLAGS.LINEAR
|
||||
|
||||
@@ -1,144 +1,145 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between group">
|
||||
<a
|
||||
:href="issue.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-block rounded-sm mb-0 break-all py-0.5 text-primary-600"
|
||||
>
|
||||
{{ `${issue.identifier} ${issue.title}` }}
|
||||
</a>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
class="!px-2 hover:!bg-transparent hidden dark:hover:!bg-transparent underline group-hover:block"
|
||||
@click="unlinkIssue"
|
||||
>
|
||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.UNLINK.TITLE') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
|
||||
<!-- <span class="text-sm font-normal text-slate-900 dark:text-slate-25">
|
||||
{{ issue.description }}
|
||||
</span> -->
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between w-full">
|
||||
<span
|
||||
class="text-sm sticky top-0 h-fit font-normal tracking-[-0.6%] min-w-[140px] truncate text-slate-600 dark:text-slate-200"
|
||||
>
|
||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.ISSUE.STATUS') }}
|
||||
</span>
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<span
|
||||
class="text-sm font-normal text-left text-slate-900 dark:text-slate-25 tabular-nums"
|
||||
>
|
||||
{{ issue.state.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between w-full">
|
||||
<span
|
||||
class="text-sm sticky top-0 h-fit font-normal tracking-[-0.6%] min-w-[140px] truncate text-slate-600 dark:text-slate-200"
|
||||
>
|
||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.ISSUE.PRIORITY') }}
|
||||
</span>
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<span
|
||||
class="text-sm font-normal text-left text-slate-900 dark:text-slate-25 tabular-nums"
|
||||
>
|
||||
{{ priorityLabel }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full">
|
||||
<span
|
||||
class="text-sm sticky top-0 h-fit font-normal tracking-[-0.6%] min-w-[140px] truncate text-slate-600 dark:text-slate-200"
|
||||
>
|
||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.ISSUE.ASSIGNEE') }}
|
||||
</span>
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<span
|
||||
class="text-sm font-normal text-left text-slate-900 dark:text-slate-25 tabular-nums"
|
||||
>
|
||||
{{ assigneeName }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full">
|
||||
<span
|
||||
class="text-sm sticky top-0 h-fit font-normal tracking-[-0.6%] min-w-[140px] truncate text-slate-600 dark:text-slate-200"
|
||||
>
|
||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.ISSUE.LABELS') }}
|
||||
</span>
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<span
|
||||
class="text-sm font-normal text-left text-slate-900 dark:text-slate-25 tabular-nums"
|
||||
>
|
||||
{{ labels }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between w-full">
|
||||
<span
|
||||
class="text-sm sticky top-0 h-fit font-normal tracking-[-0.6%] min-w-[140px] truncate text-slate-600 dark:text-slate-200"
|
||||
>
|
||||
{{ $t('INTEGRATION_SETTINGS.LINEAR.ISSUE.CREATED_AT') }}
|
||||
</span>
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<span
|
||||
class="text-sm font-normal text-left text-slate-900 dark:text-slate-25 tabular-nums"
|
||||
>
|
||||
{{ formatDate(issue.createdAt) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { format } from 'date-fns';
|
||||
import UserAvatarWithName from 'dashboard/components/widgets/UserAvatarWithName.vue';
|
||||
import { computed } from 'vue';
|
||||
const priorityMap = {
|
||||
0: 'Low',
|
||||
1: 'Medium',
|
||||
2: 'High',
|
||||
3: 'Urgent',
|
||||
};
|
||||
export default {
|
||||
props: {
|
||||
issue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
linkId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
const props = defineProps({
|
||||
issue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
priorityLabel() {
|
||||
return priorityMap[this.issue.priority];
|
||||
},
|
||||
assigneeName() {
|
||||
return this.issue.assignee ? this.issue.assignee.name : '---';
|
||||
},
|
||||
labels() {
|
||||
return this.issue.labels.nodes.length
|
||||
? this.issue.labels.nodes.map(label => label.name).join(', ')
|
||||
: '---';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
unlinkIssue() {
|
||||
this.$emit('unlink-issue', this.linkId);
|
||||
},
|
||||
formatDate(timestamp) {
|
||||
return format(new Date(timestamp), 'MMM dd, hh:mm a');
|
||||
},
|
||||
linkId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['unlink-issue']);
|
||||
const formattedDate = computed(() => {
|
||||
return format(new Date(props.issue.createdAt), 'hh:mm a, MMM dd');
|
||||
});
|
||||
|
||||
const assignee = computed(() => {
|
||||
if (!props.issue.assignee) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
name: props.issue.assignee.name,
|
||||
thumbnail: props.issue.assignee.avatarUrl,
|
||||
};
|
||||
});
|
||||
|
||||
const labels = computed(() => {
|
||||
if (props.issue.labels.nodes.length) {
|
||||
return props.issue.labels.nodes;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const priorityLabel = computed(() => {
|
||||
return priorityMap[props.issue.priority];
|
||||
});
|
||||
const unlinkIssue = () => {
|
||||
emit('unlink-issue', props.linkId);
|
||||
};
|
||||
|
||||
const openIssue = () => {
|
||||
window.open(props.issue.url, '_blank');
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="absolute flex flex-col items-start bg-[#fdfdfd] dark:bg-slate-800 z-50 px-4 py-3 border border-solid border-slate-75 dark:border-slate-700 w-[384px] rounded-xl gap-4 max-h-96 overflow-auto"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-row justify-between w-full">
|
||||
<div
|
||||
class="flex items-center justify-center gap-1 px-2 py-1 border rounded-lg border-ash-200"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="linear"
|
||||
size="19"
|
||||
class="text-[#5E6AD2]"
|
||||
view-box="0 0 19 19"
|
||||
/>
|
||||
<span class="text-xs font-medium text-ash-900">
|
||||
{{ issue.identifier }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<woot-button
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
@click="unlinkIssue"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="unlink"
|
||||
size="14"
|
||||
type="outline"
|
||||
icon-lib="lucide"
|
||||
/>
|
||||
</woot-button>
|
||||
<woot-button
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
@click="openIssue"
|
||||
>
|
||||
<fluent-icon icon="arrow-up-right" size="14" />
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="mt-2 text-sm font-medium text-ash-900">
|
||||
{{ issue.title }}
|
||||
</span>
|
||||
<span class="mt-1 text-sm text-ash-800">
|
||||
{{
|
||||
issue.description.length > 130
|
||||
? issue.description.slice(0, 130) + '...'
|
||||
: issue.description
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 divide-x divide-ash-200">
|
||||
<user-avatar-with-name v-if="assignee" :user="assignee" />
|
||||
<div class="flex items-center gap-1">
|
||||
<fluent-icon
|
||||
icon="status"
|
||||
size="14"
|
||||
:class="`text-[${issue.state.color}]`"
|
||||
/>
|
||||
<h6 class="text-xs text-slate-600">
|
||||
{{ issue.state.name }}
|
||||
</h6>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<fluent-icon
|
||||
:icon="`priority-${priorityLabel.toLowerCase()}`"
|
||||
size="14"
|
||||
view-box="0 0 14 14"
|
||||
/>
|
||||
<h6 class="text-xs text-slate-600">{{ priorityLabel }}</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="labels.length" class="flex flex-wrap items-center gap-1">
|
||||
<woot-label
|
||||
v-for="label in labels"
|
||||
:key="label.id"
|
||||
:title="label.name"
|
||||
:description="label.description"
|
||||
:color="label.color"
|
||||
variant="smooth"
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs text-ash-800">
|
||||
{{ `Created at ${formattedDate}` }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div class="relative" @mouseover="openSlaPopover()">
|
||||
<div class="relative" @mouseover="linkedIssue ? openIssue() : null">
|
||||
<woot-button
|
||||
v-on-clickaway="closeSlaPopover"
|
||||
v-tooltip="tooltipText"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
@click="openSlaPopover()"
|
||||
@click="openIssue()"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="linear"
|
||||
@@ -12,11 +13,16 @@
|
||||
class="text-[#5E6AD2]"
|
||||
view-box="0 0 19 19"
|
||||
/>
|
||||
<span v-if="linkedIssue" class="text-xs font-medium text-ash-800">
|
||||
{{ linkedIssue.issue.identifier }}
|
||||
</span>
|
||||
</woot-button>
|
||||
<linear-issue-details
|
||||
v-if="showSlaPopoverCard"
|
||||
<issue-item
|
||||
v-if="shouldShowIssue"
|
||||
:issue="linkedIssue.issue"
|
||||
:link-id="linkedIssue.id"
|
||||
class="absolute right-0 top-[46px]"
|
||||
@unlink-issue="unlinkIssue"
|
||||
/>
|
||||
<woot-modal
|
||||
:show.sync="showPopup"
|
||||
@@ -34,15 +40,16 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import LinearIssueDetails from './LinearIssueDetails.vue';
|
||||
import IssueItem from './IssueItem.vue';
|
||||
import LinearAPI from 'dashboard/api/integrations/linear';
|
||||
import CreateOrLinkIssue from './CreateOrLinkIssue.vue';
|
||||
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
export default {
|
||||
components: {
|
||||
LinearIssueDetails,
|
||||
IssueItem,
|
||||
CreateOrLinkIssue,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
@@ -52,7 +59,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
linkedIssue: null,
|
||||
showSlaPopover: false,
|
||||
showIssue: false,
|
||||
showPopup: false,
|
||||
};
|
||||
},
|
||||
@@ -60,8 +67,13 @@ export default {
|
||||
...mapGetters({
|
||||
currentAccountId: 'getCurrentAccountId',
|
||||
}),
|
||||
showSlaPopoverCard() {
|
||||
return this.showSlaPopover && this.linkedIssue;
|
||||
shouldShowIssue() {
|
||||
return this.showIssue && this.linkedIssue;
|
||||
},
|
||||
tooltipText() {
|
||||
return this.linkedIssue === null
|
||||
? this.$t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK_BUTTON')
|
||||
: null;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
@@ -99,12 +111,12 @@ export default {
|
||||
);
|
||||
}
|
||||
},
|
||||
openSlaPopover() {
|
||||
openIssue() {
|
||||
if (!this.linkedIssue) this.showPopup = true;
|
||||
this.showSlaPopover = true;
|
||||
this.showIssue = true;
|
||||
},
|
||||
closeSlaPopover() {
|
||||
this.showSlaPopover = false;
|
||||
this.showIssue = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user