mirror of
https://github.com/Gimanh/taskview-community.git
synced 2026-09-11 13:29:17 +00:00
@@ -12,9 +12,6 @@
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<h2 class="text-lg font-semibold mb-4">
|
||||
{{ projectName }}
|
||||
</h2>
|
||||
<UTabs
|
||||
v-model="activeTab"
|
||||
:items="tabs"
|
||||
@@ -60,16 +57,9 @@ import { useCollaborationPermissionsStore } from '@/stores/collaboration-permiss
|
||||
import MembersList from './parts/members/MembersList.vue'
|
||||
import RolesList from './parts/roles/RolesList.vue'
|
||||
import { useAppRouteInfo } from '@/composables/useAppRouteInfo'
|
||||
import { useGoalsStore } from '@/stores/goals.store'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { projectId } = useAppRouteInfo()
|
||||
const goalsStore = useGoalsStore()
|
||||
|
||||
const projectName = computed(() => {
|
||||
const goal = goalsStore.goalMap.get(projectId.value)
|
||||
return goal?.name ?? ''
|
||||
})
|
||||
const collaborationStore = useCollaborationStore()
|
||||
const rolesStore = useCollaborationRolesStore()
|
||||
const permissionsStore = useCollaborationPermissionsStore()
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
<UButton
|
||||
v-if="isValid"
|
||||
icon="i-lucide-corner-down-left"
|
||||
:label="t('collaboration.members.add')"
|
||||
color="primary"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
:aria-label="t('collaboration.members.add')"
|
||||
@click="addMember"
|
||||
/>
|
||||
<UIcon
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
:key="status.id"
|
||||
class="h-full max-w-[340px] min-w-[272px] shadow-lg gap-2 flex flex-col w-[91.666667%] rounded-lg"
|
||||
>
|
||||
<div class="bg-elevated rounded-lg p-2 px-3 flex items-center text-base h-10">
|
||||
<div class="bg-elevated rounded-lg p-2 px-3 flex items-center text-base h-10 rounded-b-none">
|
||||
<span class="grow truncate">
|
||||
{{ t(status.name) }}
|
||||
</span>
|
||||
@@ -27,6 +27,7 @@
|
||||
v-if="canAddTask"
|
||||
:goal-id="kanbanStore.goalId"
|
||||
:status-id="status.id"
|
||||
class="mx-1"
|
||||
/>
|
||||
|
||||
<draggable
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</TvGoalLikeItem>
|
||||
|
||||
<template #content>
|
||||
<UPageList class="w-full gap-2 flex flex-col">
|
||||
<UPageList class="w-full gap-2 flex flex-col p-1">
|
||||
<ProjectAddInput
|
||||
v-if="!isArchive"
|
||||
@add="handleAdd"
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<TaskDetailPanel />
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2 w-full">
|
||||
<div class="flex justify-between gap-2 w-full">
|
||||
<UButton
|
||||
:label="t('common.delete')"
|
||||
color="error"
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
class="flex-1"
|
||||
:variant="isDark ? 'subtle' : 'soft'"
|
||||
@update:model-value="handleAmountInput"
|
||||
@focus="editing = true"
|
||||
@blur="handleBlur"
|
||||
>
|
||||
<template #leading>
|
||||
<UIcon
|
||||
@@ -73,17 +75,23 @@ const tasksStore = useTasksStore()
|
||||
const { isDark } = useColor()
|
||||
const { canDeleteTask } = useGoalPermissions()
|
||||
|
||||
const localAmount = ref<string>(props.amount?.toString() ?? '')
|
||||
const localAmount = ref<string>(formatDisplay(props.amount))
|
||||
const tabResetKey = ref(0)
|
||||
// don't overwrite what user is typing with server response like "2.00"
|
||||
let editing:boolean = false
|
||||
|
||||
// Watch for external changes, skip if numeric value is the same
|
||||
function formatDisplay(value: number | string | null): string {
|
||||
if (value === null || value === '') return ''
|
||||
const num = parseFloat(value.toString())
|
||||
if (isNaN(num)) return ''
|
||||
// drop trailing zeros: 2.00 -> 2, 2.50 -> 2.5
|
||||
return String(num)
|
||||
}
|
||||
|
||||
// only sync from server when user is not editing
|
||||
watch(() => props.amount, (newAmount) => {
|
||||
const incoming = newAmount === null ? null : parseFloat(newAmount.toString())
|
||||
const current = localAmount.value === '' ? null : parseFloat(localAmount.value)
|
||||
|
||||
if (incoming !== current) {
|
||||
localAmount.value = newAmount?.toString() ?? ''
|
||||
}
|
||||
if (editing) return
|
||||
localAmount.value = formatDisplay(newAmount)
|
||||
})
|
||||
|
||||
const tabItems = computed(() => [
|
||||
@@ -135,6 +143,12 @@ function handleAmountInput(value: string | number) {
|
||||
debouncedUpdateAmount(amount)
|
||||
}
|
||||
|
||||
function handleBlur() {
|
||||
editing = false
|
||||
// clean up display: "2." -> "2", "02" -> "2"
|
||||
localAmount.value = formatDisplay(localAmount.value || null)
|
||||
}
|
||||
|
||||
async function clearAmount() {
|
||||
localAmount.value = ''
|
||||
await tasksStore.updateTaskAmount({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<UDashboardPanel id="collaboration">
|
||||
<template #header>
|
||||
<UDashboardNavbar :title="t('collaboration.title')">
|
||||
<UDashboardNavbar :title="`${projectName} - ${t('collaboration.title')}`">
|
||||
<template #leading>
|
||||
<UDashboardSidebarCollapse />
|
||||
</template>
|
||||
@@ -15,8 +15,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import CollaborationPanel from '@/components/features/collaboration/CollaborationPanel.vue'
|
||||
import { useAppRouteInfo } from '@/composables/useAppRouteInfo'
|
||||
import { useGoalsStore } from '@/stores/goals.store'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { projectId } = useAppRouteInfo()
|
||||
const goalsStore = useGoalsStore()
|
||||
const projectName = computed(() => goalsStore.goalMap.get(projectId.value)?.name ?? '')
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
id="graph"
|
||||
>
|
||||
<template #header>
|
||||
<UDashboardNavbar :title="t('graph.title')">
|
||||
<UDashboardNavbar :title="`${projectName} - ${t('graph.title')}`">
|
||||
<template #leading>
|
||||
<TvCollapseSidebarDesktop />
|
||||
</template>
|
||||
@@ -18,15 +18,19 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import ProjectGraph from '@/components/features/graph/ProjectGraph.vue'
|
||||
import TvCollapseSidebarDesktop from '@/components/features/base/TvCollapseSidebarDesktop.vue'
|
||||
import TvTaskDetailOverlay from '@/components/features/tasks/TvTaskDetailOverlay.vue'
|
||||
import { useAppRouteInfo } from '@/composables/useAppRouteInfo'
|
||||
import { useGoalsStore } from '@/stores/goals.store'
|
||||
import { useProjectDataLoader } from '@/composables/useProjectDataLoader'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { projectId } = useAppRouteInfo()
|
||||
const goalsStore = useGoalsStore()
|
||||
const projectName = computed(() => goalsStore.goalMap.get(projectId.value)?.name ?? '')
|
||||
|
||||
useProjectDataLoader(projectId)
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
:ui="{ body: 'sm:p-4 sm:pb-0' }"
|
||||
>
|
||||
<template #header>
|
||||
<UDashboardNavbar :title="t('kanban.title')">
|
||||
<UDashboardNavbar :title="`${projectName} - ${t('kanban.title')}`">
|
||||
<template #leading>
|
||||
<TvCollapseSidebarDesktop />
|
||||
</template>
|
||||
@@ -19,15 +19,19 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import KanbanBoard from '@/components/features/kanban/KanbanBoard.vue'
|
||||
import TvCollapseSidebarDesktop from '@/components/features/base/TvCollapseSidebarDesktop.vue'
|
||||
import TvTaskDetailOverlay from '@/components/features/tasks/TvTaskDetailOverlay.vue'
|
||||
import { useAppRouteInfo } from '@/composables/useAppRouteInfo'
|
||||
import { useProjectDataLoader } from '@/composables/useProjectDataLoader'
|
||||
import { useGoalsStore } from '@/stores/goals.store'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { projectId } = useAppRouteInfo()
|
||||
const goalsStore = useGoalsStore()
|
||||
const projectName = computed(() => goalsStore.goalMap.get(projectId.value)?.name ?? '')
|
||||
|
||||
useProjectDataLoader(projectId)
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user