Files
LinkDesk/frontend/src/components/layout/AppSidebar.vue
T
indigo cd2efe3587 Truth-in-UI cleanup: wire real dashboard data, fix delete/dialog gaps
Phase 1 of frontend_tasks.md - stop showing fabricated/broken UI:

- Dashboard now fetches real stats (projects, tasks, users, pending
  approvals, API keys, developer stats, pending reviews, admin
  activity) instead of hardcoded numbers. Added services/developer.ts
  and services/review.ts wrappers for previously-unused backend
  endpoints.
- Wired ActivityFeed into every project's Overview page in place of
  the "coming soon" placeholder.
- Registered the missing /projects/:id/technical-specs route (view
  and service already existed, just unreachable).
- Fixed AssetDeleteConfirmDialog's raw styled divs to use the shared
  Alert component and wired it into AssetBrowser, matching
  ShotBrowser's impact-summary + type-to-confirm safety pattern
  (asset deletion was previously less safe than shot deletion).
- Fixed a shared bug in both delete dialogs where the impact-summary
  section never rendered (watch on the open prop needed
  { immediate: true }).
- Replaced native confirm()/alert() with styled AlertDialog/Dialog in
  NoteItem, TaskAttachments, and UserMenu's keyboard-shortcuts item.
- Removed dead-end UI: Google OAuth stub buttons, UserMenu items
  pointing at non-existent routes, the /developer/docs dead link, and
  the no-op action button on the API Keys placeholder page.
- Removed leftover debug console logging across 8 files.

Added frontend_report.md (full audit) and frontend_tasks.md (phased
checklist) as the reference for this and future phases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-17 04:47:26 +08:00

194 lines
6.7 KiB
Vue

<template>
<Sidebar variant="inset" v-bind="props">
<SidebarHeader>
<ProjectSwitcher v-if="userRole !== 'developer'" />
<!-- Developer header (no project switching) -->
<SidebarMenu v-else>
<SidebarMenuItem>
<SidebarMenuButton size="lg" as-child>
<router-link to="/" class="flex items-center gap-2">
<div class="flex aspect-square size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
<Clapperboard class="size-4" />
</div>
<div class="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
<span class="truncate font-semibold">VFX Studio</span>
<span class="truncate text-xs">Developer Tools</span>
</div>
</router-link>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<!-- Main Navigation -->
<SidebarGroup>
<SidebarGroupLabel>Navigation</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem v-for="item in navigationItems" :key="item.title">
<SidebarMenuButton as-child :tooltip="isCollapsed ? item.title : undefined">
<router-link :to="item.url" class="flex items-center gap-2">
<component :is="item.icon" class="size-4" />
<span class="group-data-[collapsible=icon]:hidden">{{ item.title }}</span>
</router-link>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
<!-- View Settings Section - Only show on shot/project pages -->
<SidebarColumnSwitch v-if="userRole !== 'developer' && isOnShotPage" />
<!-- Projects Section -->
<!-- <SidebarGroup v-if="userRole !== 'developer'">
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem v-for="project in recentProjects" :key="project.id">
<SidebarMenuButton as-child :tooltip="isCollapsed ? project.name : undefined">
<router-link :to="`/projects/${project.id}`" class="flex items-center gap-2">
<Folder class="size-4" />
<span class="truncate group-data-[collapsible=icon]:hidden">{{ project.name }}</span>
</router-link>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup> -->
<!-- Admin Tools (only for admin users) -->
<SidebarGroup v-if="authStore.isAdmin">
<SidebarGroupLabel>Administration</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem v-for="item in adminItems" :key="item.title">
<SidebarMenuButton as-child :tooltip="isCollapsed ? item.title : undefined">
<router-link :to="item.url" class="flex items-center gap-2">
<component :is="item.icon" class="size-4" />
<span class="group-data-[collapsible=icon]:hidden">{{ item.title }}</span>
</router-link>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
<!-- Developer Tools (only for developer role) -->
<SidebarGroup v-if="userRole === 'developer'">
<SidebarGroupLabel>Developer Tools</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem v-for="item in developerItems" :key="item.title">
<SidebarMenuButton as-child :tooltip="isCollapsed ? item.title : undefined">
<router-link :to="item.url" class="flex items-center gap-2">
<component :is="item.icon" class="size-4" />
<span class="group-data-[collapsible=icon]:hidden">{{ item.title }}</span>
</router-link>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<UserMenu />
</SidebarFooter>
</Sidebar>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
SidebarProps
} from '@/components/ui/sidebar'
import {
Clapperboard,
Home,
CheckSquare,
FolderOpen,
Users,
Settings,
Folder,
Key,
Database,
BarChart3,
RotateCcw,
} from 'lucide-vue-next'
import { useAuthStore } from '@/stores/auth'
import ProjectSwitcher from './ProjectSwitcher.vue'
import UserMenu from './UserMenu.vue'
import SidebarColumnSwitch from '@/components/ui/sidebar/SidebarColumnSwitch.vue'
const authStore = useAuthStore()
const { state } = useSidebar()
const route = useRoute()
const user = computed(() => authStore.user)
const userRole = computed(() => authStore.user?.role || 'artist')
// Check if sidebar is collapsed
const isCollapsed = computed(() => state.value === 'collapsed')
// Check if on shot page - show column switch only on shot pages
const isOnShotPage = computed(() => {
return route.path.endsWith('/shots') || route.path.includes('/project/')
})
const props = withDefaults(defineProps<SidebarProps>(), {
collapsible: "icon",
})
// Navigation items based on user role
const navigationItems = computed(() => {
const baseItems = [
{ title: 'Dashboard', url: '/', icon: Home },
{ title: 'My Tasks', url: '/tasks', icon: CheckSquare },
]
if (userRole.value === 'coordinator' || authStore.isAdmin) {
baseItems.push(
{ title: 'Projects', url: '/projects', icon: FolderOpen },
{ title: 'Team', url: '/users', icon: Users }
)
}
if (userRole.value === 'director' || authStore.isAdmin) {
baseItems.push(
{ title: 'Reviews', url: '/reviews', icon: CheckSquare }
)
}
if (authStore.isAdmin) {
baseItems.push(
{ title: 'Settings', url: '/settings', icon: Settings }
)
}
return baseItems
})
// Admin-specific navigation items
const adminItems = computed(() => [
{ title: 'Recovery Management', url: '/admin/deleted-items', icon: RotateCcw },
])
// Developer-specific navigation items
const developerItems = computed(() => [
{ title: 'API Keys', url: '/developer/api-keys', icon: Key },
{ title: 'All Projects', url: '/developer/projects', icon: Database },
{ title: 'All Tasks', url: '/developer/tasks', icon: CheckSquare },
{ title: 'Usage Analytics', url: '/developer/analytics', icon: BarChart3 }
])
// Mock recent projects - this would come from a store in real implementation
const recentProjects = computed(() => [
{ id: 1, name: 'Project Alpha' },
{ id: 2, name: 'Project Beta' },
{ id: 3, name: 'Project Gamma' }
])
</script>