wip
This commit is contained in:
1 parent
b80e88b121
commit
51debefee5
13 files changed
+139
-70
No files matched your search
+1
-1
@@ -53,7 +53,7 @@ onMounted(async () => {
|
|||||||
<template>
|
<template>
|
||||||
<component :is="layout">
|
<component :is="layout">
|
||||||
<router-view v-slot="{ Component, route }">
|
<router-view v-slot="{ Component, route }">
|
||||||
<div :key="route.name">
|
<div :key="route.name" class="h-100">
|
||||||
<component :is="Component" />
|
<component :is="Component" />
|
||||||
</div>
|
</div>
|
||||||
</router-view>
|
</router-view>
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ function onEnter(e: KeyboardEvent) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chat__bubble">
|
<v-sheet class="chat__bubble px-4 py-2">
|
||||||
<div v-if="item.showHeader && !item.own" class="font-weight-medium text-body-small text-primary">
|
<div v-if="item.showHeader && !item.own" class="font-weight-medium text-body-small text-primary">
|
||||||
{{ authorName(item.comment) }}
|
{{ authorName(item.comment) }}
|
||||||
</div>
|
</div>
|
||||||
@@ -171,13 +171,13 @@ function onEnter(e: KeyboardEvent) {
|
|||||||
<div class="chat__meta text-medium-emphasis">
|
<div class="chat__meta text-medium-emphasis">
|
||||||
<span v-if="item.comment.editedAt && !item.comment.deletedAt">edited · </span>{{ item.time }}
|
<span v-if="item.comment.editedAt && !item.comment.deletedAt">edited · </span>{{ item.time }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</v-sheet>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<v-fade-transition>
|
<v-fade-transition>
|
||||||
<v-btn v-if="!atBottom" class="chat__jump" icon size="small" elevation="2" @click="scrollToBottom('smooth')">
|
<v-btn v-if="!atBottom" density="compact" class="chat__jump" icon @click="scrollToBottom('smooth')">
|
||||||
<v-badge :model-value="unread > 0" :content="unread" color="primary" floating>
|
<v-badge :model-value="unread > 0" :content="unread" color="primary" floating>
|
||||||
<v-icon icon="mdi-chevron-down" />
|
<v-icon icon="mdi-chevron-down" />
|
||||||
</v-badge>
|
</v-badge>
|
||||||
@@ -220,7 +220,8 @@ function onEnter(e: KeyboardEvent) {
|
|||||||
.chat {
|
.chat {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: min(30vh, 560px);
|
height: 100%;
|
||||||
|
min-height: 20rem;
|
||||||
|
|
||||||
&__viewport {
|
&__viewport {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -271,12 +272,10 @@ function onEnter(e: KeyboardEvent) {
|
|||||||
&__bubble {
|
&__bubble {
|
||||||
max-width: min(75%, 640px);
|
max-width: min(75%, 640px);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 6px 10px;
|
|
||||||
border-radius: 14px;
|
|
||||||
background: rgba(var(--v-theme-on-surface), 0.06);
|
background: rgba(var(--v-theme-on-surface), 0.06);
|
||||||
|
|
||||||
.chat__row--own & {
|
.chat__row--own & {
|
||||||
background: rgba(var(--v-theme-primary), 0.14);
|
background: rgba(var(--v-theme-primary), 0.35);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,10 +46,18 @@ const open = () => {
|
|||||||
<v-btn density="compact" size="small" :icon="icon" @click="open" />
|
<v-btn density="compact" size="small" :icon="icon" @click="open" />
|
||||||
<div @click="open" class="font-weight-bold text-capitalize text-title-medium cursor-pointer">{{ title }}</div>
|
<div @click="open" class="font-weight-bold text-capitalize text-title-medium cursor-pointer">{{ title }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isOpen" class="ml-6 mt-2">
|
<div v-if="isOpen" class="panel__body ml-6 mt-2">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss">
|
||||||
|
// Lets the body fill the panel when the panel itself is stretched by its parent
|
||||||
|
.panel__body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -33,7 +33,7 @@ const cancel = () => {
|
|||||||
<v-hover #default="{ isHovering, props }">
|
<v-hover #default="{ isHovering, props }">
|
||||||
<v-sheet
|
<v-sheet
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
:class="isHovering ? 'px-4' : 'px-0'"
|
:class="isHovering ? 'px-0' : 'px-0'"
|
||||||
class="py-1 cursor-pointer content-sheet"
|
class="py-1 cursor-pointer content-sheet"
|
||||||
@click="startEdit"
|
@click="startEdit"
|
||||||
>
|
>
|
||||||
@@ -59,9 +59,10 @@ const cancel = () => {
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-sheet:hover {
|
//.content-sheet:hover {
|
||||||
background-color: rgba(var(--v-theme-on-surface), 0.06);
|
//background-color: rgba(var(--v-theme-on-surface), 0.06);
|
||||||
}
|
//}
|
||||||
|
|
||||||
.v-sheet {
|
.v-sheet {
|
||||||
transition: padding 0.2s ease-in-out;
|
transition: padding 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ const cancel = () => {
|
|||||||
<v-date-input
|
<v-date-input
|
||||||
input-format="dd-mm-yyyy"
|
input-format="dd-mm-yyyy"
|
||||||
autofocus
|
autofocus
|
||||||
class="mb-1"
|
|
||||||
hide-details
|
hide-details
|
||||||
prepend-icon=""
|
prepend-icon=""
|
||||||
v-model="draft"
|
v-model="draft"
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ const { ckey } = defineProps<Props>()
|
|||||||
|
|
||||||
const createdAtFormatted = computed(() => {
|
const createdAtFormatted = computed(() => {
|
||||||
if (!issue.value?.createdAt) return ''
|
if (!issue.value?.createdAt) return ''
|
||||||
return new Intl.DateTimeFormat('ru-RU', { dateStyle: 'medium', timeStyle: 'short' }).format(
|
return new Date(issue.value.createdAt).toLocaleString('ru-RU')
|
||||||
new Date(issue.value.createdAt),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
@@ -101,62 +99,97 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="issue" class="d-flex flex-column ga-4 pa-4">
|
<div v-if="issue" class="pa-4 details">
|
||||||
<div>{{ ckey }}</div>
|
<div class="d-flex flex-column ga-4">
|
||||||
|
<div>{{ ckey }}</div>
|
||||||
|
|
||||||
<issue-title :text="issue.title" @changed="onTitleChanged" />
|
<issue-title :text="issue.title" @changed="onTitleChanged" />
|
||||||
|
|
||||||
<v-card elevation="0" border>
|
<v-card elevation="0" border>
|
||||||
<v-card-text class="u-grid">
|
<v-card-text class="u-grid">
|
||||||
<div class="text-medium-emphasis">Status:</div>
|
<div class="text-medium-emphasis">Status:</div>
|
||||||
<status-menu :value="issue.status" @changed="onStatusChanged" />
|
<status-menu :value="issue.status" @changed="onStatusChanged" />
|
||||||
|
|
||||||
<div class="text-medium-emphasis">Reporter:</div>
|
<div class="text-medium-emphasis">Reporter:</div>
|
||||||
<reporter-info :user="issue.reporter" />
|
<reporter-info :user="issue.reporter" />
|
||||||
|
|
||||||
<div class="text-medium-emphasis">Assignee:</div>
|
<div class="text-medium-emphasis">Assignee:</div>
|
||||||
<assignee-menu :value="issue.assignee" :compact="false" @changed="onAssigneeChanged" />
|
<assignee-menu :value="issue.assignee" :compact="false" @changed="onAssigneeChanged" />
|
||||||
|
|
||||||
<div class="text-medium-emphasis">Created At:</div>
|
<div class="text-medium-emphasis">Created At:</div>
|
||||||
<div>{{ createdAtFormatted }}</div>
|
<div>{{ createdAtFormatted }}</div>
|
||||||
|
|
||||||
<div class="text-medium-emphasis">Due Date:</div>
|
<div class="text-medium-emphasis">Due Date:</div>
|
||||||
<issue-date :date="issue.dueDate" @changed="onDueDateChanged" />
|
<issue-date :date="issue.dueDate" @changed="onDueDateChanged" />
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<expansion-panel title="Details">
|
<expansion-panel title="Details">
|
||||||
<issue-content :text="issue.content" @changed="onContentChanged" />
|
<issue-content :text="issue.content" @changed="onContentChanged" />
|
||||||
</expansion-panel>
|
</expansion-panel>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<expansion-panel title="Activity" class="activity">
|
||||||
|
<v-sheet class="activity__sheet">
|
||||||
|
<v-btn-toggle class="align-self-start" border divided v-model="activity" mandatory="force" density="compact">
|
||||||
|
<v-btn rounded="0" prepend-icon="mdi-chat" value="comment">Comments</v-btn>
|
||||||
|
<v-btn rounded="0" prepend-icon="mdi-history" value="history">History</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
|
||||||
<expansion-panel title="Activity">
|
<v-tabs-window v-model="activity" class="activity__window">
|
||||||
<v-sheet>
|
<v-tabs-window-item value="comment" class="h-100">
|
||||||
<v-tabs v-model="activity" density="compact">
|
<comments-list :issue-id="issue.id" />
|
||||||
<v-tab prepend-icon="mdi-star" value="comment">Comments</v-tab>
|
</v-tabs-window-item>
|
||||||
<v-tab prepend-icon="mdi-star" value="history">History</v-tab>
|
<v-tabs-window-item value="history">
|
||||||
</v-tabs>
|
<history-list />
|
||||||
|
</v-tabs-window-item>
|
||||||
<v-divider />
|
</v-tabs-window>
|
||||||
|
</v-sheet>
|
||||||
<v-tabs-window v-model="activity">
|
</expansion-panel>
|
||||||
<v-tabs-window-item value="comment">
|
</div>
|
||||||
<comments-list :issue-id="issue.id" />
|
|
||||||
</v-tabs-window-item>
|
|
||||||
<v-tabs-window-item value="history">
|
|
||||||
<history-list />
|
|
||||||
</v-tabs-window-item>
|
|
||||||
</v-tabs-window>
|
|
||||||
</v-sheet>
|
|
||||||
</expansion-panel>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.details {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details > * {
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
&__sheet {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__window {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
:deep(.v-window__container) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.u-grid {
|
.u-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
column-gap: 1rem;
|
column-gap: 1rem;
|
||||||
row-gap: 0.75rem;
|
row-gap: 1.5rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -33,7 +33,7 @@ const cancel = () => {
|
|||||||
<v-hover #default="{ isHovering, props }">
|
<v-hover #default="{ isHovering, props }">
|
||||||
<v-sheet
|
<v-sheet
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
:class="isHovering ? 'px-3' : 'px-0'"
|
:class="isHovering ? 'px-0' : 'px-0'"
|
||||||
class="py-1 cursor-pointer title-sheet"
|
class="py-1 cursor-pointer title-sheet"
|
||||||
@click="startEdit"
|
@click="startEdit"
|
||||||
>
|
>
|
||||||
@@ -56,9 +56,9 @@ const cancel = () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.title-sheet:hover {
|
//.title-sheet:hover {
|
||||||
background-color: rgba(var(--v-theme-on-surface), 0.06);
|
// background-color: rgba(var(--v-theme-on-surface), 0.06);
|
||||||
}
|
//}
|
||||||
|
|
||||||
.v-sheet {
|
.v-sheet {
|
||||||
transition: padding 0.2s ease-in-out;
|
transition: padding 0.2s ease-in-out;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { authClient } from '@/lib/auth-client.ts'
|
import { authClient } from '@/lib/auth-client.ts'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import ProjectMenu from '@/components/ProjectMenu.vue'
|
import ProjectMenu from '@/components/ProjectMenu.vue'
|
||||||
@@ -9,6 +9,7 @@ import { useThemeMode } from '@/composables/useThemeMode.ts'
|
|||||||
import { THEME_ICONS } from '@/lib/theme.ts'
|
import { THEME_ICONS } from '@/lib/theme.ts'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
const session = authClient.useSession()
|
const session = authClient.useSession()
|
||||||
const { mode: themeMode, cycleThemeMode } = useThemeMode()
|
const { mode: themeMode, cycleThemeMode } = useThemeMode()
|
||||||
|
|
||||||
@@ -59,6 +60,10 @@ async function goToSignOut() {
|
|||||||
const goToSettings = () => {
|
const goToSettings = () => {
|
||||||
router.push({ name: 'settings' })
|
router.push({ name: 'settings' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fluid = computed(() => {
|
||||||
|
return route.meta.fluid !== undefined ? !!route.meta.fluid : true
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -141,12 +146,14 @@ const goToSettings = () => {
|
|||||||
</div>
|
</div>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
<v-main>
|
<v-main>
|
||||||
<v-container :fluid="false">
|
<v-container :fluid="fluid" class="layout-container">
|
||||||
<div class="nav-row d-flex align-center ga-2 mb-2">
|
<div class="nav-row d-flex align-center ga-2 mb-2">
|
||||||
<back-button />
|
<back-button />
|
||||||
<app-breadcrumbs />
|
<app-breadcrumbs />
|
||||||
</div>
|
</div>
|
||||||
<slot></slot>
|
<div class="layout-content">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-main>
|
</v-main>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
@@ -156,4 +163,15 @@ const goToSettings = () => {
|
|||||||
.nav-row:empty {
|
.nav-row:empty {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-container {
|
||||||
|
height: calc(100dvh - var(--v-layout-top) - var(--v-layout-bottom));
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-content {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -75,6 +75,9 @@ const vuetify = createVuetify({
|
|||||||
density: 'compact',
|
density: 'compact',
|
||||||
hideDetails: true,
|
hideDetails: true,
|
||||||
},
|
},
|
||||||
|
VCard: {
|
||||||
|
elevation: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -16,6 +16,7 @@ declare module 'vue-router' {
|
|||||||
breadcrumb?: string | ((route: RouteLocationNormalizedLoaded) => string)
|
breadcrumb?: string | ((route: RouteLocationNormalizedLoaded) => string)
|
||||||
/** Named route the back button should navigate to, when deep-linking means history may be empty. */
|
/** Named route the back button should navigate to, when deep-linking means history may be empty. */
|
||||||
backTo?: string
|
backTo?: string
|
||||||
|
fluid?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: HomeView,
|
component: HomeView,
|
||||||
meta: { layout: 'Default' },
|
meta: { layout: 'Default', fluid: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'sign-in',
|
name: 'sign-in',
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const issuesRoutes: RouteRecordRaw = {
|
|||||||
meta: {
|
meta: {
|
||||||
breadcrumb: (route) => route.params.ckey as string,
|
breadcrumb: (route) => route.params.ckey as string,
|
||||||
backTo: 'issues',
|
backTo: 'issues',
|
||||||
|
fluid: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ watchEffect(async () => fetch())
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-sheet border>
|
<v-sheet border class="h-100 d-flex flex-column">
|
||||||
<issue-details :ckey="ckey" />
|
<issue-details :ckey="ckey" />
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ async function onDeleteBatch() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="d-flex flex-column ga-2">
|
<div class="d-flex flex-column ga-2 h-100">
|
||||||
<div class="u-grid ga-1">
|
<div class="u-grid ga-1">
|
||||||
<div class="u-height">
|
<div class="u-height">
|
||||||
<issue-filter
|
<issue-filter
|
||||||
@@ -274,7 +274,7 @@ async function onDeleteBatch() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<v-sheet border v-if="selected.length" class="overflow-y-auto">
|
<v-sheet border v-if="selected.length" class="d-flex flex-column">
|
||||||
<div class="d-flex align-center ga-2 px-4 py-2">
|
<div class="d-flex align-center ga-2 px-4 py-2">
|
||||||
<div>Work item</div>
|
<div>Work item</div>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
@@ -297,9 +297,15 @@ async function onDeleteBatch() {
|
|||||||
.u-grid {
|
.u-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: v-bind(gridTemplate);
|
grid-template-columns: v-bind(gridTemplate);
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-grid > * {
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.u-height {
|
.u-height {
|
||||||
height: calc(100vh - 10rem);
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in new issue
Block a user