WC
Worklance Connect
Recruitment CRM

Welcome Back

Login to continue to your CRM dashboard.

Β© 2026 Worklance Connect CRM
Dashboard

Recruiter Performance

Monthly Closure Graph

Present Today
0
Active Recruiters Now
0
Offline Recruiters
0
Last Active Recruiter
-

Recruiter Attendance

Daily login, logout and active working hours. Auto logout after 10 minutes of inactivity.

Monthly Attendance Admin Tracking

Admin tracking view: First Login, Last Logout, Current Session and Current Status are shown recruiter-wise. Calls / Interviews / Selected / Joined columns have been removed from Attendance.
RecruiterPresent DaysTotal HoursAvg HoursFirst Login TodayLast Logout TodayCurrent SessionCurrent Status
Loading admin attendance summary...
DateRecruiterRoleLogin TimeLogout TimeWorking HoursStatusLast Active
Loading attendance...
Loading CRM Data...
Please wait while we prepare your workspace
'; const blob = new Blob(['\ufeff' + html], {type:'application/vnd.ms-excel;charset=utf-8'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = (name || 'export') + '.xls'; a.click(); } async function resetDefaultExportFormat(){ if(!confirm('Reset interview export formats to default Nivas Housing Finance tabular format?')) return; const r = await api('saveExportFormats',{formats:DEFAULT_INTERVIEW_EXPORT_FORMATS_FINAL}); alert(r.message || 'Reset complete'); if(r.success){ window.INTERVIEW_EXPORT_FORMATS = DEFAULT_INTERVIEW_EXPORT_FORMATS_FINAL; if(typeof renderSettings === 'function') renderSettings(); } } function interviewFormatOptions(){ const fmts = window.INTERVIEW_EXPORT_FORMATS || DEFAULT_INTERVIEW_EXPORT_FORMATS_FINAL; return fmts.map((f,i)=>``).join(''); } // ===== FINAL INTERVIEW EXPORT UI FIX: no email send, job filter, admin format manager ===== function interviewJobOptionsFinal(){ const jobs=[...new Set((DATA.interviews||[]).map(x=>String(x['Job Name']||'').trim()).filter(Boolean))]; return ''+jobs.map(j=>``).join(''); } function currentInterviewRowsFiltered(){ const job=document.getElementById('interviewJobFilter') ? document.getElementById('interviewJobFilter').value : 'ALL'; return (DATA.interviews||[]).filter(r=>job==='ALL'||String(r['Job Name']||'')===String(job)); } function renderSimple(id){ const rows = id==='interviews' ? currentInterviewRowsFiltered() : (DATA[id]||[]); const add = isAdmin()?['jobs','clients','interviews','followups','recruiters'].includes(id):['interviews','followups'].includes(id); let exportBtn=''; if(id==='interviews'){ exportBtn = `${isAdmin()?``:''}`; } else if(isAdmin()){ exportBtn = ``; } document.getElementById(id).innerHTML=`
${add?``:''}${exportBtn}
${tableHTMLActions(id,rows)}${id==='jobs'?'

Candidate Match Suggestions

':''}`; if(id==='interviews' && document.getElementById('interviewJobFilter')){ const old = document.getElementById('interviewJobFilter').dataset.selected; } } function exportFormatAdminPanel(){ if(!isAdmin()) return ''; const fmts = window.INTERVIEW_EXPORT_FORMATS || DEFAULT_INTERVIEW_EXPORT_FORMATS_FINAL || DEFAULT_INTERVIEW_EXPORT_FORMATS; return `

Client-wise Interview Export Formats

Admin can add separate Excel column formats as per each client requirement. This controls the Interviews Export Excel button.

${tableHTML(fmts.map((f,i)=>({'#':i+1,'Format Name':f.name,'Client Name':f.clientName||f.name||'', 'Columns':(f.columns||[]).map(c=>c.header).join(' | ')})))}
`; } function openExportFormatModal(){ if(!isAdmin()) return alert('Only Admin can manage export formats'); const fmts = window.INTERVIEW_EXPORT_FORMATS || DEFAULT_INTERVIEW_EXPORT_FORMATS_FINAL || DEFAULT_INTERVIEW_EXPORT_FORMATS; modalTitle.textContent='Add / Update Client Export Format'; modalBody.innerHTML=`

Sources: sr, today, blank, constant:Text, candidate.Name, candidate.Mobile, candidate.Position, candidate.City, candidate.State, candidate.Experience, candidate.Company, candidate.Current CTC, candidate.Expected CTC, candidate.Notice Period, candidate.Remark, interview.Interview Date, interview.Status

`; modal.style.display='flex';document.body.classList.add('modal-open'); } function loadFormatIntoEditor(){ const idx=fmtIndex.value; if(idx==='new'){ fmtName.value='';fmtClientName.value='';fmtColumns.value='';return; } const f=(window.INTERVIEW_EXPORT_FORMATS||DEFAULT_INTERVIEW_EXPORT_FORMATS_FINAL||DEFAULT_INTERVIEW_EXPORT_FORMATS)[Number(idx)]; fmtName.value=f.name||''; fmtClientName.value=f.clientName||f.name||''; fmtColumns.value=(f.columns||[]).map(c=>`${c.header} = ${c.source}`).join('\n'); } async function saveExportFormatFromEditor(){ const name=fmtName.value.trim(); if(!name) return alert('Format name required'); const columns=fmtColumns.value.split(/\r?\n/).map(x=>x.trim()).filter(Boolean).map(line=>{ const parts=line.split('='); return {header:(parts[0]||'').trim(), source:(parts.slice(1).join('=')||'blank').trim()}; }).filter(c=>c.header); if(!columns.length) return alert('At least one column required'); let fmts=[...(window.INTERVIEW_EXPORT_FORMATS||DEFAULT_INTERVIEW_EXPORT_FORMATS_FINAL||DEFAULT_INTERVIEW_EXPORT_FORMATS)]; const item={name, clientName:fmtClientName.value.trim()||name, columns}; const idx=fmtIndex.value; if(idx==='new') fmts.push(item); else fmts[Number(idx)]=item; const r=await api('saveExportFormats',{formats:fmts}); alert(r.message||'Saved'); if(r.success){window.INTERVIEW_EXPORT_FORMATS=fmts;closeModal(); if(typeof renderSettings==='function')renderSettings(); if(document.getElementById('interviews')?.classList.contains('active'))renderSimple('interviews');} } function exportInterviewsClientFormat(){ const rows = currentInterviewRowsFiltered(); if(!rows.length) return alert('No interview data to export'); const idx = Number(document.getElementById('interviewExportFormat')?.value || 0); let fmt = (window.INTERVIEW_EXPORT_FORMATS || DEFAULT_INTERVIEW_EXPORT_FORMATS_FINAL || DEFAULT_INTERVIEW_EXPORT_FORMATS)[idx] || (DEFAULT_INTERVIEW_EXPORT_FORMATS_FINAL||DEFAULT_INTERVIEW_EXPORT_FORMATS)[0]; if(typeof isValidExportFormatFinal==='function' && !isValidExportFormatFinal(fmt)) fmt = DEFAULT_INTERVIEW_EXPORT_FORMATS_FINAL[0]; const out = rows.map((interview,i)=>{ const candidate = candidateForInterview(interview); const obj = {}; (fmt.columns || NIVAS_TABULAR_COLUMNS_FINAL).forEach(col=>{ obj[col.header] = getSourceValue(col.source, interview, candidate, i); }); return obj; }); exportExcelRows((fmt.name || 'client-interview-format').toLowerCase().replace(/[^a-z0-9]+/g,'-'), out); } /* ===== Professional Browse Jobs + Job Details Upgrade ===== */ (function(){ const jobCss = ` `; document.head.insertAdjacentHTML('beforeend', jobCss); })(); let JOB_TAB='active'; function jobVal(j, keys, fallback=''){ for(const k of keys){ if(j && j[k]!==undefined && j[k]!==null && String(j[k]).trim()!=='') return String(j[k]); } return fallback; } function clientReplacementDaysForJob(clientName){ const c=(DATA.clients||[]).find(x=>String(x['Company Name']||x.Company||'').trim()===String(clientName||'').trim()); return (c && (c['Replacement Days'] || c.Replacement || c.replacementDays)) || '90 Days'; } function syncJobReplacementDays(){ const c=document.getElementById('g_client'); const r=document.getElementById('g_replacementDays'); if(c && r) r.value=clientReplacementDaysForJob(c.value); } function shortText(txt,n=210){txt=String(txt||'').replace(/<[^>]+>/g,' ').replace(/\s+/g,' ').trim();return txt.length>n?txt.slice(0,n)+'...':txt;} function jobStatus(j){return jobVal(j,['Status'],'Active').toLowerCase();} function renderJobsPage(){ const all=DATA.jobs||[]; const active=all.filter(j=>!['closed','inactive'].includes(jobStatus(j))); const closed=all.filter(j=>['closed','inactive'].includes(jobStatus(j))); const rows=(JOB_TAB==='closed'?closed:active); const types=[...new Set(all.map(j=>jobVal(j,['Employment Type','Type'],'Full-time')).filter(Boolean))]; document.getElementById('jobs').innerHTML=`
${isAdmin()?`
`:''}
${rows.length?rows.map((j,i)=>jobCardHTML(j, all.indexOf(j))).join(''):'
No jobs found.
'}
`; } function jobCardHTML(j,i){ const title=jobVal(j,['Position Name','Job Title','Title'],'Untitled Job'); const company=jobVal(j,['Client Name','Company Name','Company'],''); const loc=jobVal(j,['Location','City'],''); const type=jobVal(j,['Employment Type','Type'],'Full-time'); const mode=jobVal(j,['Work Mode','Mode'],''); const salary=jobVal(j,['Salary','Compensation'],''); const openings=jobVal(j,['Openings','Hiring Count'],''); const desc=jobVal(j,['Short Description','Description','Full JD','Job Description','Skills'],''); const priority=jobVal(j,['Priority'],''); const reward=jobVal(j,['Reward','Placement Reward'],''); const assigned=jobVal(j,['Assigned Recruiter','Recruiter'],'Not Assigned'); const replacement=jobVal(j,['Replacement Days','Replacement','replacementDays'], clientReplacementDaysForJob(company)); return `

${esc(title)}${priority.toLowerCase()==='hot'||priority.toLowerCase()==='high'?'πŸ”₯ HOT':''}

${esc(company)}
${loc?`πŸ“ ${esc(loc)}`:''}${type?`πŸ’Ό ${esc(type)}`:''}${mode?`🏒 ${esc(mode)}`:''}${salary?`πŸ’² ${esc(salary)}`:''}${openings?`πŸ‘₯ ${esc(openings)} Openings`:''}πŸ‘€ Assigned: ${esc(assigned)}${replacement?`πŸ” Replacement: ${esc(replacement)}`:''}${reward?`Reward: ${esc(reward)}`:''}
${esc(shortText(desc))}
${isAdmin()?`
`:''}
`; } function filterJobCards(){ const q=(jobSearch?.value||'').toLowerCase(),t=(jobTypeFilter?.value||''),l=(jobLocationFilter?.value||'').toLowerCase(); document.querySelectorAll('#jobsList .job-card').forEach(c=>{const ok=(!q||(c.dataset.title+c.dataset.company).includes(q))&&(!t||c.dataset.type===t)&&(!l||c.dataset.location.includes(l));c.style.display=ok?'block':'none';}); } function showJobDetails(i){ const j=DATA.jobs[i]||{}; const title=jobVal(j,['Position Name','Job Title','Title'],'Untitled Job'), company=jobVal(j,['Client Name','Company Name','Company'],''); const loc=jobVal(j,['Location','City'],''), type=jobVal(j,['Employment Type','Type'],'Full-time'), mode=jobVal(j,['Work Mode','Mode'],''), salary=jobVal(j,['Salary','Compensation'],''), openings=jobVal(j,['Openings','Hiring Count'],''), replacement=jobVal(j,['Replacement Days','Replacement','replacementDays'], clientReplacementDaysForJob(company)); const about=jobVal(j,['Full JD','Job Description','Description','Short Description'],''); const resp=jobVal(j,['Responsibilities'],''); const req=jobVal(j,['Requirements','Skills','Experience'],''); const benefits=jobVal(j,['Benefits'],''); document.getElementById('jobs').innerHTML=`

${esc(title)}${jobVal(j,['Priority'],'').toLowerCase().match(/hot|high/)?'πŸ”₯ HOT':''}

${esc(company)}

Job Description

${esc(title)}

${type?`

Employment Type: ${esc(type)}

`:''}${mode?`

Work Mode: ${esc(mode)}

`:''}${loc?`

Location: ${esc(loc)}

`:''}${salary?`

Compensation: ${esc(salary)}

`:''}${openings?`

Hiring Count: ${esc(openings)} Openings

`:''}${replacement?`

Replacement Days: ${esc(replacement)}

`:''}
${about?`

About this Role

${esc(about)}
`:''} ${resp?`

What you'll do

${esc(resp)}
`:''} ${req?`

Requirements

${esc(req)}
`:''} ${benefits?`

Benefits

${esc(benefits)}
`:''}
`; } const oldGenericLabels=genericLabels; genericLabels=function(id){ if(id==='jobs')return [['client','Client Name','clientSelect'],['replacementDays','Replacement Days','replacementDaysAuto'],['position','Job Title / Position Name'],['location','Location'],['type','Employment Type','employmentType'],['mode','Work Mode','workMode'],['salary','Compensation / Salary'],['openings','Hiring Count / Openings','number'],['priority','Priority','priority'],['recruiter','Assigned Recruiter','recruiterSelect'],['status','Status','jobStatus'],['jobDescription','Job Description','textareaLarge']]; return oldGenericLabels(id); }; const oldRowToForm=rowToForm; rowToForm=function(id,r={}){ if(id==='jobs')return {id:r['Job ID']||'',client:r['Client Name']||r['Company Name']||'',replacementDays:r['Replacement Days']||r.Replacement||clientReplacementDaysForJob(r['Client Name']||r['Company Name']||''),position:r['Position Name']||r['Job Title']||r.Title||'',location:r.Location||'',type:r['Employment Type']||r.Type||'',mode:r['Work Mode']||r.Mode||'',salary:r.Salary||r.Compensation||'',openings:r.Openings||r['Hiring Count']||'',priority:r.Priority||'',recruiter:r['Assigned Recruiter']||'',status:r.Status||'Active',jobDescription:r['Job Description']||r['Full JD']||r.Description||r['Short Description']||r.Skills||''}; return oldRowToForm(id,r); }; const oldFieldHTML=fieldHTML; fieldHTML=function(f,val=''){ let id='g_'+f[0],label=f[1],type=f[2]||'text'; if(type==='clientSelect')return `
`; if(type==='replacementDaysAuto')return `
`; if(type==='textarea'||type==='textareaLarge')return `
`; if(type==='employmentType')return sel(id,label,['Full-time','Part-time','Contract','Freelance','Internship','Temporary','Permanent','Consultant','Commission Based'],val); if(type==='workMode')return sel(id,label,['On-site','Remote','Hybrid','Work From Home','Field Work','Office Based'],val); return oldFieldHTML(f,val); }; const oldSaveGeneric=saveGeneric; saveGeneric=async function(id){ if(id!=='jobs')return oldSaveGeneric(id); if(!isAdmin()){alert('Only Admin can save this module');return;} const inputs=[...modalBody.querySelectorAll('input,select,textarea')];let data={};inputs.forEach(x=>data[x.id.replace('g_','')]=x.value); data.replacementDays=data.replacementDays || clientReplacementDaysForJob(data.client);data['Position Name']=data.position;data['Job Title']=data.position;data['Client Name']=data.client;data['Replacement Days']=data.replacementDays;data['Employment Type']=data.type;data['Work Mode']=data.mode;data['Salary']=data.salary;data['Compensation']=data.salary;data['Openings']=data.openings;data['Hiring Count']=data.openings;data['Job Description']=data.jobDescription;data['Full JD']=data.jobDescription;data['Description']=data.jobDescription;data['Skills']=data.jobDescription;data['Priority']=data.priority;data['Status']=data.status;data['Assigned Recruiter']=data.recruiter; const r=await api(data.id?'updateJob':'addJob',{data});alert(r.message||'Saved');if(r.success){closeModal();await refreshAll(true);renderJobsPage();} }; const oldRenderSimple=renderSimple; renderSimple=function(id){ if(id==='jobs')return renderJobsPage(); return oldRenderSimple(id); };