Remove top project tabs in favor of the sidebar's Projects sub-menu
The sidebar already provides Overview/Shots/Assets/Tasks/Settings navigation for the current project, making the top ProjectTabs bar redundant. Removed it from ProjectDetailView.vue and deleted the now-fully-unused ProjectTabs.vue component. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,160 +0,0 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div class="grid w-full grid-cols-5 h-auto bg-muted/50 p-1 rounded-md">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
@click="setActiveTab(tab.id)"
|
||||
:class="[
|
||||
'flex flex-col sm:flex-row items-center justify-center gap-1 sm:gap-2 py-2 px-1 sm:px-3 min-h-[3rem] sm:min-h-[2.5rem] transition-all duration-200 text-center rounded-sm',
|
||||
activeTab === tab.id
|
||||
? 'bg-background shadow-sm text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center gap-1 sm:gap-2">
|
||||
<component :is="tab.icon" class="h-4 w-4 flex-shrink-0" />
|
||||
<span class="hidden sm:inline text-sm font-medium">{{ tab.label }}</span>
|
||||
<span class="sm:hidden text-xs font-medium">{{ getMobileLabel(tab) }}</span>
|
||||
</div>
|
||||
<Badge
|
||||
v-if="tab.count !== undefined"
|
||||
variant="secondary"
|
||||
class="text-xs hidden sm:inline-flex min-w-[1.5rem] h-5"
|
||||
>
|
||||
{{ tab.count }}
|
||||
</Badge>
|
||||
<!-- Mobile count display -->
|
||||
<div
|
||||
v-if="tab.count !== undefined"
|
||||
class="sm:hidden text-xs text-muted-foreground font-medium"
|
||||
>
|
||||
{{ tab.count }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { LayoutDashboard, Camera, Package, ListTodo, Settings } from "lucide-vue-next";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface Tab {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: any;
|
||||
route: string;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
projectId: number;
|
||||
shotCount?: number;
|
||||
assetCount?: number;
|
||||
taskCount?: number;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// Define available tabs
|
||||
const tabs = computed<Tab[]>(() => [
|
||||
{
|
||||
id: "overview",
|
||||
label: "Overview",
|
||||
icon: LayoutDashboard,
|
||||
route: `/projects/${props.projectId}`,
|
||||
},
|
||||
{
|
||||
id: "shots",
|
||||
label: "Shots",
|
||||
icon: Camera,
|
||||
route: `/projects/${props.projectId}/shots`,
|
||||
count: props.shotCount,
|
||||
},
|
||||
{
|
||||
id: "assets",
|
||||
label: "Assets",
|
||||
icon: Package,
|
||||
route: `/projects/${props.projectId}/assets`,
|
||||
count: props.assetCount,
|
||||
},
|
||||
{
|
||||
id: "tasks",
|
||||
label: "Tasks",
|
||||
icon: ListTodo,
|
||||
route: `/projects/${props.projectId}/tasks`,
|
||||
count: props.taskCount,
|
||||
},
|
||||
{
|
||||
id: "settings",
|
||||
label: "Settings",
|
||||
icon: Settings,
|
||||
route: `/projects/${props.projectId}/settings`,
|
||||
},
|
||||
]);
|
||||
|
||||
// Determine active tab based on current route
|
||||
const activeTab = computed(() => {
|
||||
const currentPath = route.path;
|
||||
|
||||
if (currentPath === `/projects/${props.projectId}`) {
|
||||
return "overview";
|
||||
} else if (currentPath.startsWith(`/projects/${props.projectId}/shots`)) {
|
||||
return "shots";
|
||||
} else if (currentPath.startsWith(`/projects/${props.projectId}/assets`)) {
|
||||
return "assets";
|
||||
} else if (currentPath.startsWith(`/projects/${props.projectId}/tasks`)) {
|
||||
return "tasks";
|
||||
} else if (
|
||||
currentPath.startsWith(`/projects/${props.projectId}/settings`)
|
||||
) {
|
||||
return "settings";
|
||||
}
|
||||
|
||||
return "overview";
|
||||
});
|
||||
|
||||
// Set active tab and navigate
|
||||
const setActiveTab = (tabId: string) => {
|
||||
const tab = tabs.value.find((t) => t.id === tabId);
|
||||
if (tab) {
|
||||
router.push(tab.route).catch(() => {
|
||||
// Navigation aborted (e.g. duplicate route) — safe to ignore
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Get mobile label for tabs
|
||||
const getMobileLabel = (tab: Tab) => {
|
||||
switch (tab.id) {
|
||||
case "settings":
|
||||
return "Settings";
|
||||
case "overview":
|
||||
return "Info";
|
||||
case "shots":
|
||||
return "Shots";
|
||||
case "assets":
|
||||
return "Assets";
|
||||
case "tasks":
|
||||
return "Tasks";
|
||||
default:
|
||||
return tab.label.charAt(0);
|
||||
}
|
||||
};
|
||||
|
||||
// Watch for route changes to ensure tab state persistence
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
// Tab state is automatically updated via activeTab computed property
|
||||
// This ensures tab state persistence during project navigation
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -38,15 +38,6 @@
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
|
||||
<!-- Project Tabs -->
|
||||
<div class="px-0 sm:px-0 pb-0 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60" v-if="project">
|
||||
<ProjectTabs
|
||||
:project-id="project.id"
|
||||
:shot-count="project.shot_count"
|
||||
:asset-count="project.asset_count"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Tab Content Area -->
|
||||
<div class="flex-1 overflow-auto bg-muted/30">
|
||||
<router-view :key="route.fullPath" />
|
||||
@@ -87,7 +78,6 @@ import {
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { useProjectsStore } from '@/stores/projects'
|
||||
import ProjectTabs from '@/components/project/ProjectTabs.vue'
|
||||
import type { Project } from '@/stores/projects'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
Reference in New Issue
Block a user