@@ -283,6 +283,7 @@ type DashboardNotification = {
283283
284284type DashboardNotificationsResponse = {
285285 stale_days : number
286+ contacted_reminder_days : number
286287 notifications : DashboardNotification [ ]
287288}
288289
@@ -927,6 +928,7 @@ function App() {
927928 const [ projectQuery , setProjectQuery ] = useState ( "" )
928929 const [ projectStatus , setProjectStatus ] = useState ( initialProjectDetailId ? "" : "Open" )
929930 const [ staleRecruitingDays , setStaleRecruitingDays ] = useState ( 7 )
931+ const [ contactedReminderDays , setContactedReminderDays ] = useState ( 5 )
930932 const [ peopleQuery , setPeopleQuery ] = useState ( "" )
931933 const [ peopleMember , setPeopleMember ] = useState ( "" )
932934 const [ peopleFilters , setPeopleFilters ] = useState < FilterState > ( { } )
@@ -1757,6 +1759,7 @@ function App() {
17571759 "/dashboard/api/notifications?limit=20" ,
17581760 )
17591761 setStaleRecruitingDays ( payload . stale_days || 7 )
1762+ setContactedReminderDays ( payload . contacted_reminder_days || 5 )
17601763 setNotifications ( payload . notifications || [ ] )
17611764 } catch ( error ) {
17621765 showError ( error , "Unable to load notifications" )
@@ -2550,16 +2553,18 @@ function App() {
25502553 )
25512554
25522555 function openNotification ( notification : DashboardNotification ) {
2553- if ( notification . type === "stale_recruiting_gig" ) {
2556+ if ( notification . type === "stale_recruiting_gig" || notification . type === "contacted_gig" ) {
2557+ const isContacted = notification . type === "contacted_gig"
2558+ const notificationPrefix = isContacted ? "contacted-gig:" : "stale-recruiting:"
25542559 const gigId =
25552560 notification . engagement_id ||
2556- ( notification . id . startsWith ( "stale-recruiting:" )
2557- ? notification . id . slice ( "stale-recruiting:" . length )
2561+ ( notification . id . startsWith ( notificationPrefix )
2562+ ? notification . id . slice ( notificationPrefix . length )
25582563 : "" )
25592564 if ( gigId ) {
25602565 openGigDetail ( gigId )
25612566 } else {
2562- setGigStatus ( "recruiting" )
2567+ setGigStatus ( isContacted ? "contacted" : "recruiting" )
25632568 navigate ( "gigs" , true )
25642569 }
25652570 }
@@ -2797,6 +2802,7 @@ function App() {
27972802 includeHistorical = { gigIncludeHistorical }
27982803 limit = { gigLimit }
27992804 staleDays = { staleRecruitingDays }
2805+ contactedReminderDays = { contactedReminderDays }
28002806 canWrite = { can ( "gigs:write" ) }
28012807 canSearchCandidates = { can ( "people:read" ) }
28022808 canManageLeads = { can ( "people:read" ) }
@@ -3302,6 +3308,7 @@ function FilterChips({
33023308const gigStatuses = [
33033309 "lead" ,
33043310 "recruiting" ,
3311+ "contacted" ,
33053312 "filled" ,
33063313 "unknown" ,
33073314 "lost" ,
@@ -3339,11 +3346,16 @@ function gigActivityTimestamp(gig: Gig) {
33393346 return activityTimes . length > 0 ? new Date ( Math . max ( ...activityTimes ) ) . toISOString ( ) : ""
33403347}
33413348
3342- function staleRecruitingAge ( gig : Gig , staleDays : number ) {
3343- if ( gig . status !== "recruiting" ) return null
3344- const latestActivity = gigActivityTimestamp ( gig )
3345- const age = daysSince ( latestActivity )
3346- if ( age === null || age < staleDays ) return null
3349+ function staleGigAge ( gig : Gig , recruitingDays : number , contactedDays : number ) {
3350+ const isContacted = gig . status === "contacted"
3351+ if ( gig . status !== "recruiting" && ! isContacted ) return null
3352+ const age = daysSince (
3353+ isContacted
3354+ ? gig . last_status_changed_at || gig . updated_at || gig . created_at
3355+ : gigActivityTimestamp ( gig ) ,
3356+ )
3357+ const reminderDays = isContacted ? contactedDays : recruitingDays
3358+ if ( age === null || age < reminderDays ) return null
33473359 return age
33483360}
33493361
@@ -5172,6 +5184,7 @@ function GigsView(props: {
51725184 includeHistorical : boolean
51735185 limit : number
51745186 staleDays : number
5187+ contactedReminderDays : number
51755188 canWrite : boolean
51765189 canSearchCandidates : boolean
51775190 canManageLeads : boolean
@@ -5207,7 +5220,9 @@ function GigsView(props: {
52075220 acc . total += 1
52085221 acc . applications += Number ( gig . application_count || 0 )
52095222 acc . interested += Number ( gig . interested_count || 0 )
5210- if ( staleRecruitingAge ( gig , props . staleDays ) !== null ) acc . stale += 1
5223+ if ( staleGigAge ( gig , props . staleDays , props . contactedReminderDays ) !== null ) {
5224+ acc . stale += 1
5225+ }
52115226 return acc
52125227 } ,
52135228 { total : 0 , applications : 0 , interested : 0 , stale : 0 } ,
@@ -5459,6 +5474,7 @@ function GigsView(props: {
54595474 crmContactUrl = { props . crmContactUrl }
54605475 crmAttachmentUrl = { props . crmAttachmentUrl }
54615476 staleDays = { props . staleDays }
5477+ contactedReminderDays = { props . contactedReminderDays }
54625478 onBack = { props . onCloseGig }
54635479 onUpdateStatus = { props . onUpdateStatus }
54645480 onAddApplication = { props . onAddApplication }
@@ -5476,7 +5492,7 @@ function GigsView(props: {
54765492 < Metric id = "gigMetricTotal" label = "Gigs" value = { counts . total } />
54775493 < Metric id = "gigMetricCandidates" label = "Candidates" value = { counts . applications } />
54785494 < Metric id = "gigMetricInterested" label = "Interested" value = { counts . interested } />
5479- < Metric id = "gigMetricStale" label = "Stale recruiting " value = { counts . stale } />
5495+ < Metric id = "gigMetricStale" label = "Needs update " value = { counts . stale } />
54805496 </ section >
54815497
54825498 < Card >
@@ -5516,6 +5532,7 @@ function GigsView(props: {
55165532 loading = { props . loading }
55175533 canWrite = { props . canWrite }
55185534 staleDays = { props . staleDays }
5535+ contactedReminderDays = { props . contactedReminderDays }
55195536 onOpenGig = { props . onOpenGig }
55205537 onUpdateStatus = { props . onUpdateStatus }
55215538 />
@@ -5879,28 +5896,30 @@ function GigListItem({
58795896 onOpenGig,
58805897 onUpdateStatus,
58815898 staleDays,
5899+ contactedReminderDays,
58825900} : {
58835901 gig : Gig
58845902 loading : Record < string , boolean >
58855903 canWrite : boolean
58865904 onOpenGig : ( gigId : string ) => void
58875905 onUpdateStatus : ( gigId : string , status : string ) => void
58885906 staleDays : number
5907+ contactedReminderDays : number
58895908} ) {
58905909 const applications = Array . isArray ( gig . applications ) ? gig . applications : [ ]
5891- const isRecruiting = gig . status === "recruiting"
5910+ const isActive = gig . status === "recruiting" || gig . status === "contacted "
58925911 const threadUrl =
58935912 gig . discord_guild_id && gig . discord_thread_id
58945913 ? `https://discord.com/channels/${ encodeURIComponent (
58955914 gig . discord_guild_id ,
58965915 ) } /${ encodeURIComponent ( gig . discord_thread_id ) } `
58975916 : ""
5898- const staleAge = staleRecruitingAge ( gig , staleDays )
5917+ const staleAge = staleGigAge ( gig , staleDays , contactedReminderDays )
58995918 return (
59005919 < article
59015920 className = { cn (
59025921 "grid gap-4 rounded-md border bg-background p-4 lg:grid-cols-[minmax(0,1fr)_220px_180px] lg:items-start" ,
5903- ! isRecruiting && "border-l-4 border-l-muted-foreground/60 bg-secondary/45" ,
5922+ ! isActive && "border-l-4 border-l-muted-foreground/60 bg-secondary/45" ,
59045923 ) }
59055924 >
59065925 < div className = "min-w-0" >
@@ -5921,14 +5940,14 @@ function GigListItem({
59215940 ? "succeeded"
59225941 : gig . status === "lost" || gig . status === "duplicate"
59235942 ? "failed"
5924- : isRecruiting
5943+ : isActive
59255944 ? "queued"
59265945 : "neutral"
59275946 }
59285947 >
59295948 { gig . status_label || titleCase ( gig . status ) }
59305949 </ Badge >
5931- { ! isRecruiting ? < Badge variant = "neutral" > Not recruiting </ Badge > : null }
5950+ { ! isActive ? < Badge variant = "neutral" > Not active </ Badge > : null }
59325951 { staleAge !== null ? < Badge variant = "running" > { staleAge } d stale</ Badge > : null }
59335952 </ div >
59345953 < div className = "mt-2 flex flex-wrap gap-1.5" >
@@ -6027,6 +6046,7 @@ function GigDetailPage({
60276046 crmContactUrl,
60286047 crmAttachmentUrl,
60296048 staleDays,
6049+ contactedReminderDays,
60306050 onBack,
60316051 onUpdateStatus,
60326052 onAddApplication,
@@ -6039,6 +6059,7 @@ function GigDetailPage({
60396059 crmContactUrl : ( contactId ?: string ) => string
60406060 crmAttachmentUrl : ( attachmentId ?: string ) => string
60416061 staleDays : number
6062+ contactedReminderDays : number
60426063 onBack : ( ) => void
60436064 onUpdateStatus : ( gigId : string , status : string ) => void
60446065 onAddApplication : ( gigId : string , crmProfile : string ) => Promise < boolean >
@@ -6050,7 +6071,7 @@ function GigDetailPage({
60506071 const [ candidateSearchError , setCandidateSearchError ] = useState ( "" )
60516072 const [ crmProfile , setCrmProfile ] = useState ( "" )
60526073 const applications = Array . isArray ( gig . applications ) ? gig . applications : [ ]
6053- const isRecruiting = gig . status === "recruiting"
6074+ const isActive = gig . status === "recruiting" || gig . status === "contacted "
60546075 const candidateQueryReady = candidateQuery . trim ( ) . length >= 2
60556076 const selectedCandidateId = selectedCandidate ?. crm_contact_id || ""
60566077 const candidateProfile = selectedCandidateId
@@ -6063,7 +6084,7 @@ function GigDetailPage({
60636084 gig . discord_guild_id ,
60646085 ) } /${ encodeURIComponent ( gig . discord_thread_id ) } `
60656086 : ""
6066- const staleAge = staleRecruitingAge ( gig , staleDays )
6087+ const staleAge = staleGigAge ( gig , staleDays , contactedReminderDays )
60676088
60686089 useEffect ( ( ) => {
60696090 if ( ! canWrite || ! canSearchCandidates ) return
@@ -6116,9 +6137,7 @@ function GigDetailPage({
61166137
61176138 return (
61186139 < div className = "grid gap-5" >
6119- < Card
6120- className = { cn ( ! isRecruiting && "border-l-4 border-l-muted-foreground/60 bg-secondary/35" ) }
6121- >
6140+ < Card className = { cn ( ! isActive && "border-l-4 border-l-muted-foreground/60 bg-secondary/35" ) } >
61226141 < CardHeader className = "items-start" >
61236142 < div className = "grid gap-2" >
61246143 < Button type = "button" variant = "ghost" size = "sm" className = "w-fit" onClick = { onBack } >
@@ -6134,14 +6153,14 @@ function GigDetailPage({
61346153 ? "succeeded"
61356154 : gig . status === "lost" || gig . status === "duplicate"
61366155 ? "failed"
6137- : isRecruiting
6156+ : isActive
61386157 ? "queued"
61396158 : "neutral"
61406159 }
61416160 >
61426161 { gig . status_label || titleCase ( gig . status ) }
61436162 </ Badge >
6144- { ! isRecruiting ? < Badge variant = "neutral" > Not recruiting </ Badge > : null }
6163+ { ! isActive ? < Badge variant = "neutral" > Not active </ Badge > : null }
61456164 { staleAge !== null ? < Badge variant = "running" > { staleAge } d stale</ Badge > : null }
61466165 { gig . posting_type ? (
61476166 < Badge variant = "neutral" > { titleCase ( gig . posting_type ) } </ Badge >
0 commit comments