11<script setup lang="ts">
22import {
3- LayoutDashboard , Briefcase , Users , Inbox ,
4- ChevronLeft , Eye , Kanban , FileText , LogOut , Table2 , Hand ,
5- Sun , Moon , MessageSquarePlus ,
3+ Briefcase , Plus , Bell ,
4+ ChevronLeft , Kanban , FileText , LogOut , Table2 ,
5+ Sun , Moon , MessageSquarePlus , Settings ,
66} from ' lucide-vue-next'
77
88const route = useRoute ()
@@ -24,13 +24,6 @@ async function handleSignOut() {
2424 await navigateTo (localePath (' /auth/sign-in' ))
2525}
2626
27- const navItems = [
28- { label: ' Dashboard' , to: ' /dashboard' , icon: LayoutDashboard , exact: true },
29- { label: ' Jobs' , to: ' /dashboard/jobs' , icon: Briefcase , exact: false },
30- { label: ' Candidates' , to: ' /dashboard/candidates' , icon: Users , exact: false },
31- { label: ' Applications' , to: ' /dashboard/applications' , icon: Inbox , exact: false },
32- ]
33-
3427// ─────────────────────────────────────────────
3528// Dynamic job context — detect when viewing a specific job
3629// ─────────────────────────────────────────────
@@ -58,6 +51,23 @@ const {
5851
5952const sidebarJobs = computed (() => sidebarJobsData .value ?.data ?? [])
6053
54+ // Active jobs sorted by urgency (most new applications first)
55+ const activeJobsSorted = computed (() => {
56+ return [... sidebarJobs .value ].sort ((a , b ) => {
57+ const aNew = a .pipeline ?.new ?? 0
58+ const bNew = b .pipeline ?.new ?? 0
59+ if (aNew !== bNew ) return bNew - aNew
60+ return new Date (b .createdAt ).getTime () - new Date (a .createdAt ).getTime ()
61+ })
62+ })
63+
64+ // Currently-viewed job title (for context header)
65+ const activeJobTitle = computed (() => {
66+ if (! activeJobId .value ) return null
67+ const found = sidebarJobs .value .find ((j : any ) => j .id === activeJobId .value )
68+ return found ?.title ?? ' Job'
69+ })
70+
6171const { data : feedbackConfig } = useFetch (' /api/feedback/config' , {
6272 key: ' feedback-config' ,
6373 headers: useRequestHeaders ([' cookie' ]),
@@ -69,10 +79,8 @@ const jobTabs = computed(() => {
6979 if (! activeJobId .value ) return []
7080 const base = ` /dashboard/jobs/${activeJobId .value } `
7181 return [
72- { label: ' Overview' , to: base , icon: Eye , exact: true },
73- { label: ' Pipeline' , to: ` ${base }/pipeline ` , icon: Kanban , exact: true },
74- { label: ' Swipe' , to: ` ${base }/swipe ` , icon: Hand , exact: true },
75- { label: ' Candidates' , to: ` ${base }/candidates ` , icon: Table2 , exact: true },
82+ { label: ' Pipeline' , to: base , icon: Kanban , exact: true },
83+ { label: ' Table' , to: ` ${base }/candidates ` , icon: Table2 , exact: true },
7684 { label: ' Application Form' , to: ` ${base }/application-form ` , icon: FileText , exact: true },
7785 ]
7886})
@@ -89,7 +97,7 @@ function isActiveTab(to: string, exact: boolean) {
8997 class =" sticky top-0 self-start flex h-screen max-h-screen flex-col justify-between w-60 min-w-60 bg-white dark:bg-surface-900 border-r border-surface-200 dark:border-surface-800 py-5 px-3 overflow-y-auto"
9098 >
9199 <!-- Top -->
92- <div class =" flex flex-col gap-5 " >
100+ <div class =" flex flex-col gap-4 " >
93101 <!-- Logo -->
94102 <NuxtLink :to =" $localePath (' /' )" class="flex items-center gap-2 px-2 no-underline">
95103 <img src =" /eagle-mascot-logo.png" alt =" Reqcore mascot" class =" size-7 shrink-0 object-contain" />
@@ -105,72 +113,96 @@ function isActiveTab(to: string, exact: boolean) {
105113 <OrgSwitcher />
106114 </div >
107115
108- <!-- Main navigation -->
109- <nav class =" flex flex-col gap-0.5" >
116+ <!-- New Job button -->
117+ <NuxtLink
118+ :to =" $localePath (' /dashboard/jobs/new' )"
119+ class="flex items-center justify-center gap-2 rounded-lg bg-brand-600 px-3 py-2 text-sm font-medium text-white hover:bg-brand-700 transition-colors no-underline"
120+ >
121+ <Plus class="size-4" />
122+ New Job
123+ </NuxtLink >
124+
125+ <!-- Job context sub-nav (when viewing a specific job) -->
126+ <div v-if =" activeJobId" class =" border-t border-surface-200 dark:border-surface-800 pt-3" >
110127 <NuxtLink
111- v-for =" item in navItems "
112- :key =" item .to "
113- :to =" $localePath (item .to )"
114- class="flex items-center gap-2.5 px-3 py-2 rounded-md text-sm text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover :bg-surface-800 hover:text-surface-900 dark:hover :text-surface-100 transition-colors no-underline"
115- :class =" isActiveTab (item .to , item .exact )
116- ? ' bg-surface-100 dark:bg-surface-800 text-surface-900 dark:text-surface-100 font-medium'
117- : ' ' "
128+ :to =" $localePath (' /dashboard' )"
129+ class="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-surface-500 dark:text-surface-400 hover:text-surface-700 dark:hover :text-surface-200 transition-colors no-underline mb-2"
118130 >
119- <component :is = " item.icon " class =" size-4 shrink-0 " />
120- {{ item.label }}
131+ <ChevronLeft class="size-3. 5 " />
132+ All Jobs
121133 </NuxtLink >
122- </nav >
123134
124- <!-- Job context sub-nav (when viewing a specific job) -->
125- <div v-if =" showJobsList" class =" border-t border-surface-200 dark:border-surface-800 pt-4" >
135+ <!-- Active job title -->
136+ <div class =" px-3 pb-2" >
137+ <div class =" flex items-center gap-2" >
138+ <Briefcase class="size-3.5 text-brand-500 shrink-0" />
139+ <span class =" text-sm font-semibold text-surface-900 dark:text-surface-100 truncate" >
140+ {{ activeJobTitle }}
141+ </span >
142+ </div >
143+ </div >
144+
145+ <nav class =" flex flex-col gap-0.5" >
146+ <NuxtLink
147+ v-for =" tab in jobTabs "
148+ :key =" tab .to "
149+ :to =" $localePath (tab .to )"
150+ class="flex items-center gap-2.5 px-3 py-2 rounded-md text-sm text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover :bg-surface-800 hover:text-surface-900 dark:hover :text-surface-100 transition-colors no-underline"
151+ :class =" isActiveTab (tab .to , tab .exact )
152+ ? ' bg-surface-100 dark:bg-surface-800 text-surface-900 dark:text-surface-100 font-medium'
153+ : ' ' "
154+ >
155+ <component :is =" tab.icon" class =" size-4 shrink-0" />
156+ {{ tab.label }}
157+ </NuxtLink >
158+ </nav >
159+ </div >
160+
161+ <!-- Jobs list (when not viewing a specific job) -->
162+ <div v-if =" showJobsList" class =" border-t border-surface-200 dark:border-surface-800 pt-3" >
126163 <div class =" px-3 pb-2 text-xs font-medium uppercase tracking-wide text-surface-500 dark:text-surface-400" >
127- Jobs
164+ My Jobs
128165 </div >
129166
130167 <div v-if =" sidebarJobsStatus === 'pending'" class =" px-3 py-2 text-xs text-surface-400" >
131168 Loading jobs…
132169 </div >
133170
134- <nav v-else class =" flex max-h-56 flex-col gap-0.5 overflow-y-auto" >
171+ <nav v-else class =" flex flex-col gap-0.5 overflow-y-auto max-h-[calc(100vh-24rem)] " >
135172 <NuxtLink
136- v-for =" job in sidebarJobs "
173+ v-for =" job in activeJobsSorted "
137174 :key =" job .id "
138175 :to =" $localePath (` /dashboard/jobs/${job .id } ` )"
139- class="px-3 py-2 rounded-md text-sm text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover :bg-surface-800 hover:text-surface-900 dark:hover :text-surface-100 transition-colors no-underline truncate"
140- :title =" job .title "
176+ class="flex items-center justify-between px-3 py-2 rounded-md text-sm text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover :bg-surface-800 hover:text-surface-900 dark:hover :text-surface-100 transition-colors no-underline group"
141177 >
142- {{ job.title }}
178+ <span class =" truncate flex-1 mr-2" >{{ job.title }}</span >
179+ <span
180+ v-if =" (job.pipeline?.new ?? 0) > 0"
181+ class =" inline-flex items-center justify-center min-w-5 h-5 rounded-full bg-warning-100 dark:bg-warning-950 text-warning-700 dark:text-warning-400 text-[11px] font-semibold px-1.5 shrink-0"
182+ :title =" `${job.pipeline!.new} new application${job.pipeline!.new === 1 ? '' : 's'}`"
183+ >
184+ {{ job.pipeline!.new }}
185+ </span >
143186 </NuxtLink >
144187
145- <div v-if =" sidebarJobs.length === 0" class =" px-3 py-2 text-xs text-surface-400 " >
146- No jobs yet
188+ <div v-if =" sidebarJobs.length === 0" class =" px-3 py-4 text-center " >
189+ < p class = " text-xs text-surface-400 mb-2 " > No jobs yet</ p >
147190 </div >
148191 </nav >
149192 </div >
150193
151- <div v-if =" activeJobId" class =" border-t border-surface-200 dark:border-surface-800 pt-4" >
194+ <!-- Settings link -->
195+ <div class =" border-t border-surface-200 dark:border-surface-800 pt-3" >
152196 <NuxtLink
153- :to =" $localePath (' /dashboard/jobs' )"
154- class="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-surface-500 dark:text-surface-400 hover:text-surface-700 dark:hover :text-surface-200 transition-colors no-underline mb-2"
197+ :to =" $localePath (' /dashboard/settings' )"
198+ class="flex items-center gap-2.5 px-3 py-2 rounded-md text-sm text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover :bg-surface-800 hover:text-surface-900 dark:hover :text-surface-100 transition-colors no-underline"
199+ :class =" isActiveTab (' /dashboard/settings' , false )
200+ ? ' bg-surface-100 dark:bg-surface-800 text-surface-900 dark:text-surface-100 font-medium'
201+ : ' ' "
155202 >
156- <ChevronLeft class="size-3. 5 " />
157- All Jobs
203+ <Settings class="size-4 shrink-0 " />
204+ Settings
158205 </NuxtLink >
159-
160- <nav class =" flex flex-col gap-0.5" >
161- <NuxtLink
162- v-for =" tab in jobTabs "
163- :key =" tab .to "
164- :to =" $localePath (tab .to )"
165- class="flex items-center gap-2.5 px-3 py-2 rounded-md text-sm text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover :bg-surface-800 hover:text-surface-900 dark:hover :text-surface-100 transition-colors no-underline"
166- :class =" isActiveTab (tab .to , tab .exact )
167- ? ' bg-surface-100 dark:bg-surface-800 text-surface-900 dark:text-surface-100 font-medium'
168- : ' ' "
169- >
170- <component :is =" tab.icon" class =" size-4 shrink-0" />
171- {{ tab.label }}
172- </NuxtLink >
173- </nav >
174206 </div >
175207 </div >
176208
0 commit comments