@@ -12,7 +12,10 @@ import {
1212 MapPin ,
1313 DollarSign ,
1414 User ,
15- MoreHorizontal
15+ MoreHorizontal ,
16+ CheckCircle ,
17+ XCircle ,
18+ RotateCcw
1619} from "lucide-react" ;
1720import { Button } from "@/components/ui/button" ;
1821import { Badge } from "@codevs/ui/badge" ;
@@ -36,7 +39,7 @@ import EditJobModal from "./EditJobModal";
3639import { createClientClientComponent } from "@/utils/supabase/client" ;
3740import { Skeleton } from "@/components/ui/skeleton/skeleton" ;
3841import { useUserStore } from "@/store/codev-store" ;
39- import { deleteJobListing } from "../actions" ;
42+ import { deleteJobListing , updateJobListingStatus } from "../actions" ;
4043
4144import {
4245 Accordion ,
@@ -46,6 +49,7 @@ import {
4649} from "@codevs/ui/accordion"
4750interface JobWithApplicationCount extends JobListing {
4851 application_count ?: number ;
52+ status ?: "active" | "closed" | "draft" ;
4953 created_by_details ?: {
5054 id : string ;
5155 first_name : string ;
@@ -62,10 +66,17 @@ export default function JobListingsTable() {
6266 const [ loading , setLoading ] = useState ( true ) ;
6367 const [ editingJob , setEditingJob ] = useState < JobListing | null > ( null ) ;
6468 const [ isEditModalOpen , setIsEditModalOpen ] = useState ( false ) ;
69+ const [ statusFilter , setStatusFilter ] = useState < 'all' | 'active' | 'closed' | 'draft' > ( 'all' ) ;
6570
6671 // Check if user is admin (role_id 1 or 4)
6772 const isAdmin = user ?. role_id === 1 || user ?. role_id === 4 ;
6873
74+ // Filter jobs based on status
75+ const filteredJobs = jobs . filter ( job => {
76+ if ( statusFilter === 'all' ) return true ;
77+ return ( job . status || 'active' ) === statusFilter ;
78+ } ) ;
79+
6980 useEffect ( ( ) => {
7081 fetchJobListings ( ) ;
7182 } , [ ] ) ;
@@ -183,6 +194,38 @@ export default function JobListingsTable() {
183194 router . push ( `/home/hire/applications/${ jobId } ` ) ;
184195 } ;
185196
197+ const handleStatusChange = async ( jobId : string , newStatus : "active" | "closed" | "draft" ) => {
198+ try {
199+ const result = await updateJobListingStatus ( jobId , newStatus ) ;
200+
201+ if ( ! result . success ) {
202+ throw new Error ( result . error || "Failed to update job status" ) ;
203+ }
204+
205+ // Update local state
206+ setJobs ( prevJobs =>
207+ prevJobs . map ( job =>
208+ job . id === jobId
209+ ? { ...job , status : newStatus }
210+ : job
211+ )
212+ ) ;
213+
214+ const statusText = newStatus === 'closed' ? 'closed' : 'reopened' ;
215+ toast ( {
216+ title : `Job ${ statusText } ` ,
217+ description : `The job listing has been ${ statusText } successfully.` ,
218+ } ) ;
219+ } catch ( error ) {
220+ console . error ( 'Status update error:' , error ) ;
221+ toast ( {
222+ title : "Failed to update status" ,
223+ description : error instanceof Error ? error . message : "Please try again later." ,
224+ variant : "destructive" ,
225+ } ) ;
226+ }
227+ } ;
228+
186229 const getLevelColor = ( level : string ) => {
187230 switch ( level ) {
188231 case "Entry" :
@@ -213,6 +256,19 @@ export default function JobListingsTable() {
213256 }
214257 } ;
215258
259+ const getStatusColor = ( status : string ) => {
260+ switch ( status ) {
261+ case "active" :
262+ return "bg-green-500/10 text-green-400 border-green-500/20" ;
263+ case "closed" :
264+ return "bg-red-500/10 text-red-400 border-red-500/20" ;
265+ case "draft" :
266+ return "bg-gray-500/10 text-gray-400 border-gray-500/20" ;
267+ default :
268+ return "bg-gray-500/10 text-muted-foreground border-gray-500/20" ;
269+ }
270+ } ;
271+
216272 if ( loading ) {
217273 return (
218274 < div className = "rounded-lg border border-gray-800 bg-gray-900/50 overflow-hidden p-8" >
@@ -236,22 +292,58 @@ export default function JobListingsTable() {
236292 }
237293
238294 return (
239- < div className = "rounded-lg border bg-card dark:bg-blue-50/5 overflow-hidden" >
295+ < div className = "space-y-4" >
296+ { /* Filter Bar */ }
297+ < div className = "flex items-center gap-2 p-4 bg-card dark:bg-blue-50/5 rounded-lg border" >
298+ < span className = "text-sm font-medium text-foreground mr-2" > Filter by status:</ span >
299+ < div className = "flex gap-2" >
300+ { [
301+ { value : 'all' , label : 'All' , count : jobs . length } ,
302+ { value : 'active' , label : 'Active' , count : jobs . filter ( j => ( j . status || 'active' ) === 'active' ) . length } ,
303+ { value : 'closed' , label : 'Closed' , count : jobs . filter ( j => j . status === 'closed' ) . length } ,
304+ { value : 'draft' , label : 'Draft' , count : jobs . filter ( j => j . status === 'draft' ) . length }
305+ ] . map ( ( { value, label, count } ) => (
306+ < Button
307+ key = { value }
308+ variant = { statusFilter === value ? "default" : "outline" }
309+ size = "sm"
310+ onClick = { ( ) => setStatusFilter ( value as 'all' | 'active' | 'closed' | 'draft' ) }
311+ className = "text-xs"
312+ >
313+ { label } ({ count } )
314+ </ Button >
315+ ) ) }
316+ </ div >
317+ </ div >
318+
319+ { /* Jobs Table */ }
320+ { filteredJobs . length === 0 ? (
321+ < div className = "rounded-lg border bg-card dark:border-gray-800 dark:bg-gray-900/50 p-12 text-center" >
322+ < p className = "text-muted-foreground" >
323+ { statusFilter === 'all'
324+ ? "No job listings yet. Create your first job listing to get started."
325+ : `No ${ statusFilter } job listings found.`
326+ }
327+ </ p >
328+ </ div >
329+ ) : (
330+ < div className = "rounded-lg border bg-card dark:bg-blue-50/5 overflow-hidden" >
240331 < div className = "hidden xl:block" >
241332 < Table className = "border dark:border-gray-700" >
242333 < TableHeader >
243334 < TableRow className = "border-border hover:bg-muted/50 transition-colors dark:border-gray-700" >
244335 < TableHead className = "text-foreground" > Position</ TableHead >
245336 < TableHead className = "text-foreground" > Location</ TableHead >
246337 < TableHead className = "text-foreground" > Type</ TableHead >
338+ < TableHead className = "text-foreground" > Status</ TableHead >
247339 < TableHead className = "text-foreground text-center" > Applications</ TableHead >
248340 < TableHead className = "text-foreground" > Created By</ TableHead >
249341 < TableHead className = "text-foreground" > Posted</ TableHead >
250342 < TableHead className = "text-foreground text-right min-w-[200px]" > Actions</ TableHead >
251343 </ TableRow >
252344 </ TableHeader >
253345 < TableBody >
254- { jobs . map ( ( job ) => (
346+ { filteredJobs . map ( ( job ) => (
255347 < TableRow key = { job . id } className = "border-border hover:bg-muted/30 transition-colors dark:border-gray-700" >
256348 < TableCell >
257349 < div >
@@ -279,6 +371,11 @@ export default function JobListingsTable() {
279371 { job . type }
280372 </ Badge >
281373 </ TableCell >
374+ < TableCell >
375+ < Badge variant = "outline" className = { getStatusColor ( job . status || 'active' ) } >
376+ { job . status || 'active' }
377+ </ Badge >
378+ </ TableCell >
282379 < TableCell className = "text-center" >
283380 < button
284381 onClick = { ( ) => handleViewApplications ( job . id ) }
@@ -328,20 +425,46 @@ export default function JobListingsTable() {
328425 </ Button >
329426 </ DropdownMenuTrigger >
330427 < DropdownMenuContent align = "end" className = "bg-popover border-border" >
331- < DropdownMenuItem
332- onClick = { ( ) => handleEdit ( job ) }
333- className = "text-foreground hover:text-foreground hover:bg-accent"
334- >
335- < Edit className = "mr-2 h-4 w-4" />
336- Edit Listing
337- </ DropdownMenuItem >
338- < DropdownMenuItem
339- onClick = { ( ) => handleDelete ( job . id ) }
340- className = "text-destructive hover:text-destructive hover:bg-accent "
341- >
342- < Trash2 className = "mr-2 h-4 w-4" />
343- Delete
344- </ DropdownMenuItem >
428+ { ( isAdmin || job . created_by ?. id === user ?. id ) && (
429+ < >
430+ < DropdownMenuItem
431+ onClick = { ( ) => handleEdit ( job ) }
432+ className = "text-foreground hover:text-foreground hover:bg-accent"
433+ >
434+ < Edit className = "mr-2 h-4 w-4" />
435+ Edit Listing
436+ </ DropdownMenuItem >
437+ { job . status === 'active' ? (
438+ < DropdownMenuItem
439+ onClick = { ( ) => handleStatusChange ( job . id , 'closed' ) }
440+ className = "text-orange-500 hover:text-orange-400 hover:bg-accent"
441+ >
442+ < XCircle className = "mr-2 h-4 w-4" />
443+ Mark as Closed
444+ </ DropdownMenuItem >
445+ ) : (
446+ < DropdownMenuItem
447+ onClick = { ( ) => handleStatusChange ( job . id , 'active' ) }
448+ className = "text-green-500 hover:text-green-400 hover:bg-accent"
449+ >
450+ < RotateCcw className = "mr-2 h-4 w-4" />
451+ Reopen Position
452+ </ DropdownMenuItem >
453+ ) }
454+ < DropdownMenuItem
455+ onClick = { ( ) => handleDelete ( job . id ) }
456+ className = "text-destructive hover:text-destructive hover:bg-accent "
457+ >
458+ < Trash2 className = "mr-2 h-4 w-4" />
459+ Delete
460+ </ DropdownMenuItem >
461+ </ >
462+ ) }
463+ { ! isAdmin && job . created_by ?. id !== user ?. id && (
464+ < DropdownMenuItem disabled className = "text-gray-500" >
465+ No actions available
466+ </ DropdownMenuItem >
467+ ) }
345468 </ DropdownMenuContent >
346469 </ DropdownMenu >
347470 </ div >
@@ -355,7 +478,7 @@ export default function JobListingsTable() {
355478 { /* Mobile Accordion */ }
356479 < div className = "xl:hidden" >
357480 < Accordion type = "single" collapsible className = "w-full" >
358- { jobs . map ( ( job ) => (
481+ { filteredJobs . map ( ( job ) => (
359482 < AccordionItem key = { job . id } value = { job . id } >
360483 < AccordionTrigger className = "px-4 py-3 text-left [&>svg]:text-foreground" >
361484 < div className = "w-full" >
@@ -368,6 +491,9 @@ export default function JobListingsTable() {
368491 < Badge variant = "outline" className = { getTypeColor ( job . type ) } >
369492 { job . type }
370493 </ Badge >
494+ < Badge variant = "outline" className = { getStatusColor ( job . status || 'active' ) } >
495+ { job . status || 'active' }
496+ </ Badge >
371497 { job . remote && (
372498 < Badge variant = "outline" className = "bg-green-500/10 text-green-400 border-green-500/20" >
373499 Remote
@@ -496,7 +622,7 @@ export default function JobListingsTable() {
496622 </ Button >
497623 </ DropdownMenuTrigger >
498624 < DropdownMenuContent align = "end" className = "bg-popover border-border dark:bg-gray-900 dark:border-gray-800" >
499- { ( isAdmin || job . created_by === user ?. id ) && (
625+ { ( isAdmin || job . created_by ?. id === user ?. id ) && (
500626 < >
501627 < DropdownMenuItem
502628 onClick = { ( e ) => {
@@ -509,6 +635,31 @@ export default function JobListingsTable() {
509635 < Edit className = "mr-2 h-4 w-4" />
510636 Edit Listing
511637 </ DropdownMenuItem >
638+ { job . status === 'active' ? (
639+ < DropdownMenuItem
640+ onClick = { ( e ) => {
641+ e . preventDefault ( ) ;
642+ e . stopPropagation ( ) ;
643+ handleStatusChange ( job . id , 'closed' ) ;
644+ } }
645+ className = "text-orange-400 hover:text-orange-300 hover:bg-gray-800 cursor-pointer"
646+ >
647+ < XCircle className = "mr-2 h-4 w-4" />
648+ Mark as Closed
649+ </ DropdownMenuItem >
650+ ) : (
651+ < DropdownMenuItem
652+ onClick = { ( e ) => {
653+ e . preventDefault ( ) ;
654+ e . stopPropagation ( ) ;
655+ handleStatusChange ( job . id , 'active' ) ;
656+ } }
657+ className = "text-green-400 hover:text-green-300 hover:bg-gray-800 cursor-pointer"
658+ >
659+ < RotateCcw className = "mr-2 h-4 w-4" />
660+ Reopen Position
661+ </ DropdownMenuItem >
662+ ) }
512663 < DropdownMenuItem
513664 onClick = { ( e ) => {
514665 e . preventDefault ( ) ;
@@ -522,7 +673,7 @@ export default function JobListingsTable() {
522673 </ DropdownMenuItem >
523674 </ >
524675 ) }
525- { ! isAdmin && job . created_by !== user ?. id && (
676+ { ! isAdmin && job . created_by ?. id !== user ?. id && (
526677 < DropdownMenuItem disabled className = "text-gray-500" >
527678 No actions available
528679 </ DropdownMenuItem >
@@ -543,6 +694,8 @@ export default function JobListingsTable() {
543694 onClose = { handleEditModalClose }
544695 onJobUpdated = { handleJobUpdated }
545696 />
697+ </ div >
698+ ) }
546699 </ div >
547700 ) ;
548701}
0 commit comments