6363 </div >
6464 </div >
6565
66+ <!-- Team managers -->
67+ <div v-if =" isInAppMode && !managersLoading" class =" mt-2 flex flex-wrap items-center gap-x-1.5 text-sm text-gray-600 dark:text-gray-400" >
68+ <template v-if =" teamManagers .length > 0 " >
69+ <span class =" text-gray-400 dark:text-gray-500 shrink-0" >Manager{{ teamManagers.length > 1 ? 's' : '' }}:</span >
70+ <template v-for =" (mgr , i ) in teamManagers " :key =" mgr .uid " >
71+ <template v-if =" i > 0 " >, </template >
72+ <button
73+ @click =" navigateToPerson(mgr.uid)"
74+ class =" text-primary-600 dark:text-primary-400 hover:underline"
75+ >{{ mgr.name || mgr.uid }}</button >
76+ </template >
77+ <button
78+ v-if =" canManageMembers"
79+ @click =" showManagersModal = true"
80+ class =" ml-1 p-0.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 transition-colors"
81+ title =" Edit managers"
82+ >
83+ <svg class =" h-3.5 w-3.5" xmlns =" http://www.w3.org/2000/svg" fill =" none" viewBox =" 0 0 24 24" stroke =" currentColor" stroke-width =" 2" >
84+ <path stroke-linecap =" round" stroke-linejoin =" round" d =" M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
85+ </svg >
86+ </button >
87+ </template >
88+ <template v-else >
89+ <span class =" text-gray-400 dark:text-gray-500 italic" >
90+ No manager configured.
91+ <template v-if =" teamAdminNames .length > 0 " >
92+ Contact a Team Admin ({{ teamAdminNames.join(', ') }}) to assign one.
93+ </template >
94+ <template v-else >
95+ Contact an admin to assign one.
96+ </template >
97+ </span >
98+ <button
99+ v-if =" canManageMembers"
100+ @click =" showManagersModal = true"
101+ class =" ml-1 text-primary-600 dark:text-primary-400 hover:underline text-xs"
102+ >
103+ + Add manager
104+ </button >
105+ </template >
106+ </div >
107+
108+ <!-- Team Managers Modal -->
109+ <TeamManagersModal
110+ v-if =" showManagersModal "
111+ :teamId =" team .teamId "
112+ :teamName =" team .displayName "
113+ @close =" showManagersModal = false "
114+ @updated =" handleManagersUpdated "
115+ />
116+
66117 <!-- Team description -->
67118 <div v-if =" isInAppMode && (team.description || canManageMembers)" class =" mt-3" >
68119 <template v-if =" ! editingDescription " >
@@ -337,6 +388,7 @@ import TeamDeliveryTab from '../components/TeamDeliveryTab.vue'
337388import TeamBacklogTab from ' ../components/TeamBacklogTab.vue'
338389import TeamAutofixTab from ' ../components/autofix/TeamAutofixTab.vue'
339390import TeamFieldEditor from ' ../components/TeamFieldEditor.vue'
391+ import TeamManagersModal from ' ../components/TeamManagersModal.vue'
340392import ContributionBoundary from ' @shared/client/components/ContributionBoundary.vue'
341393import { getTeamDetailTabs , runGuard } from ' ../contributions'
342394import RefreshModal from ' @shared/client/components/RefreshModal.vue'
@@ -347,6 +399,7 @@ import { usePermissions } from '@shared/client/composables/usePermissions'
347399import { useFieldDefinitions } from ' @shared/client/composables/useFieldDefinitions'
348400import { useOrgRoster } from ' ../composables/useOrgRoster'
349401import { refreshMetrics , getTeamMetrics , apiRequest } from ' @shared/client/services/api'
402+ import { useTeams } from ' @shared/client/composables/useTeams'
350403import { useManagerTutorial } from ' ../composables/useManagerTutorial'
351404import { Marked } from ' marked'
352405import DOMPurify from ' dompurify'
@@ -358,10 +411,41 @@ const { loadGitlabStats } = useGitlabStats()
358411const { isAdmin } = useAuth ()
359412const { canEditTeam , managedUids } = usePermissions ()
360413const { definitions , fetchDefinitions } = useFieldDefinitions ()
414+ const { fetchTeamManagers } = useTeams ()
361415const { resumeTourIfActive , destroyTour } = useManagerTutorial ()
362416
363417const fromSotu = computed (() => nav .params .value ? .from === ' sotu' )
364418
419+ // --- Team managers ---
420+ const teamManagers = ref ([])
421+ const managersLoading = ref (true )
422+ const showManagersModal = ref (false )
423+ const teamAdminNames = ref ([])
424+
425+ async function fetchManagersForTeam () {
426+ if (! team .value ? .teamId ) return
427+ managersLoading .value = true
428+ try {
429+ teamManagers .value = await fetchTeamManagers (team .value .teamId )
430+ } catch {
431+ teamManagers .value = []
432+ }
433+ if (teamManagers .value .length === 0 ) {
434+ try {
435+ const data = await apiRequest (' /roles/members/team-admin' )
436+ teamAdminNames .value = (data .members || []).map (m => m .name )
437+ } catch {
438+ teamAdminNames .value = []
439+ }
440+ }
441+ managersLoading .value = false
442+ }
443+
444+ async function handleManagersUpdated () {
445+ await fetchManagersForTeam ()
446+ await reloadRoster ()
447+ }
448+
365449function handleBack () {
366450 if (fromSotu .value ) {
367451 window .location .hash = ' #/'
@@ -726,6 +810,7 @@ onMounted(() => {
726810 fetchRfeConfig ()
727811 loadGitlabStats ()
728812 fetchDefinitions ()
813+ fetchManagersForTeam ()
729814 resumeTourIfActive (' team-detail' )
730815})
731816
@@ -737,13 +822,15 @@ watch(() => nav.params.value?.teamKey, () => {
737822 fetchTeamMetrics ()
738823 fetchTeamDetail ()
739824 fetchRfeConfig ()
825+ fetchManagersForTeam ()
740826})
741827
742828// Retry loading once team resolves from roster async load
743829watch (team, (newVal , oldVal ) => {
744830 if (newVal && ! oldVal) {
745831 if (! teamMetrics .value ) fetchTeamMetrics ()
746832 if (! teamDetail .value ) fetchTeamDetail ()
833+ if (managersLoading .value ) fetchManagersForTeam ()
747834 }
748835})
749836< / script>
0 commit comments