From 52b76bb1cfa74ddc2b0ceb040d7f295a981c14b1 Mon Sep 17 00:00:00 2001 From: "ahmed.mujtaba" Date: Fri, 7 Aug 2026 19:03:23 +0500 Subject: [PATCH] added all aplicants --- backend/inbox/app.py | 17 +++++++++ backend/inbox/models.py | 12 ++++++ backend/inbox/views.py | 10 +++++ frontend/src/screens/Inbox.jsx | 70 ++++++++++++++++++++++++++-------- 4 files changed, 93 insertions(+), 16 deletions(-) diff --git a/backend/inbox/app.py b/backend/inbox/app.py index a4d8d89..db68767 100644 --- a/backend/inbox/app.py +++ b/backend/inbox/app.py @@ -1,9 +1,11 @@ +from typing import Any from fastapi import APIRouter,Depends, Query from fastapi.responses import JSONResponse from fastapi import HTTPException from db_setup import get_session from sqlalchemy.ext.asyncio import AsyncSession from inbox.views import Email +import uuid from users.permissions import PermissionTag, require_permission from dotenv import load_dotenv load_dotenv() @@ -111,3 +113,18 @@ async def get_inbox_read_status( raise except Exception as e: raise HTTPException(status_code=500,detail=str(e)) + +@router.get("/inbox/all-applications") +async def get_all_applications( + app_id:uuid.UUID|int=Query(None), + current_user:dict=Depends(require_permission(PermissionTag.INBOX_VIEW)), + session:AsyncSession=Depends(get_session), +): + try: + service=Email(session=session) + data=await service.get_all_applications(app_id) + return JSONResponse(content={"data":data,"total":1,"status_code":200}) + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500,detail=str(e)) \ No newline at end of file diff --git a/backend/inbox/models.py b/backend/inbox/models.py index a23c4e0..820e077 100644 --- a/backend/inbox/models.py +++ b/backend/inbox/models.py @@ -1,6 +1,7 @@ import uuid from datetime import datetime, timezone from typing import Any, Optional +from fastapi import HTTPException from sqlalchemy import Column, DateTime, func, or_, update from sqlalchemy.dialects.postgresql import JSONB @@ -82,6 +83,17 @@ class Inbox_Messages(SQLModel, table=True): return email_data.get("bodyPreview") or "" @classmethod + async def get_all_applications(cls,session:AsyncSession,message_id:uuid.UUID|int|None=None): + try: + qry=select(cls.message_id,cls.full_email_response,cls.message_subject,cls.message_from,cls.message_to,cls.message_sent_time,cls.message_read,cls.attachment) + if message_id: + qry=qry.where(cls.message_id==message_id) + result=await session.execute(qry) + return result.scalars().all() + + except Exception as e: + raise HTTPException(status_code=500,detail=str(e)) + @classmethod async def set_match_result( cls, session: AsyncSession, diff --git a/backend/inbox/views.py b/backend/inbox/views.py index e786a70..1fba17d 100644 --- a/backend/inbox/views.py +++ b/backend/inbox/views.py @@ -24,6 +24,16 @@ class Email: self.token=token or EMAIL_API_TOKEN self.pending_match_ids:list[str]=[] + async def get_all_applications(self,app_id=None): + try: + if app_id: + application_lst=await Inbox_Messages.get_all_applications(self.session,message_id=app_id) + else: + application_lst=await Inbox_Messages.get_all_applications(self.session) + return application_lst + except Exception as e: + raise HTTPException(status_code=500,detail=str(e)) + async def service_email(self,top,skip): async with httpx.AsyncClient() as client: try: diff --git a/frontend/src/screens/Inbox.jsx b/frontend/src/screens/Inbox.jsx index dcf2003..308d481 100644 --- a/frontend/src/screens/Inbox.jsx +++ b/frontend/src/screens/Inbox.jsx @@ -31,6 +31,26 @@ const TABS = ['All Applications', 'Unread', 'Imported', 'Processed', 'Rejected', /** The prototype computed "time ago" against a fixed 2026-07-09T20:00. */ const NOW = new Date('2026-07-09T20:00') +/** + * The seed candidate record importEmail() writes needs a number. The agent + * returns a verdict, not a score, so there is nothing on the wire to use — + * named here so the fabricated value is visible at its point of use instead of + * arriving disguised as a server field on every message. + */ +const SEED_ATS_SCORE = 70 + +/** + * message_received_time / message_sent_time are plain string columns + * (backend/inbox/models.py:54-56), not timestamps. An unparseable value yields + * an Invalid Date that every fmt* helper renders as the literal "Invalid Date", + * so return null instead and let the call sites decide what to show. + */ +function parseDate(value) { + if (!value) return null + const d = new Date(value) + return Number.isNaN(d.getTime()) ? null : d +} + function resumeText(i) { return `${i.name.toUpperCase()} ${i.email} · ${i.phone} @@ -87,11 +107,18 @@ export default function Inbox() { fromEmail: row.fromEmail || '', subject: row.subject || '', body: row.body || '', - when: row.when ? new Date(row.when) : new Date(), + when: parseDate(row.when) ?? parseDate(row.message_sent_time), unread: Boolean(row.unread), attachment: row.attachment_name || 'Resume.pdf', attachmentSize: '—', - atsScore: 70, + // The agent's verdict, straight off backend/inbox/serializers.py:44-48. + // suggested_job_post_ids is deliberately NOT carried: job posts stay + // dark to the inbox. + matchStatus: row.match_status || null, + matchSummary: row.match_summary || '', + matchReasoning: row.match_reasoning || '', + matchError: row.match_error || '', + matchedAt: parseDate(row.matched_at), imported: false, })) }, @@ -444,13 +471,18 @@ function EmailTab({ query, jobs, updateCandidates, toast }) { onError: (err) => toast(friendlyAuthError(err, 'Could not mark message read.'), 'error'), }) - async function sync() { - toast('Fetching from Outlook…', 'info') - const res = await qc.refetchQueries({ queryKey: qk.mailbox.messages() }) - if (query.isError) toast('Sync failed', 'error') - else toast('Mailbox synced', 'success') - return res - } + // Refetching the list alone only re-reads rows already in our DB. GET + // /email/fetch is the Graph proxy pull that inserts new mail and enqueues the + // matching agent, so it has to run FIRST — then the list is invalidated to + // pick up whatever it wrote. + const sync = useMutation({ + mutationFn: () => inboxApi.syncMailbox(), + onSuccess: async () => { + await qc.invalidateQueries({ queryKey: qk.mailbox.all() }) + toast('Mailbox synced', 'success') + }, + onError: (err) => toast(friendlyAuthError(err, 'Sync failed'), 'error'), + }) function importEmail(e) { const job = jobs[0] @@ -463,12 +495,12 @@ function EmailTab({ query, jobs, updateCandidates, toast }) { jobId: job.id, jobTitle: job.title, department: job.department, experience: int(2, 10), currentCompany: pick(companies), currentTitle: job.title, location: pick(locations), stage: 'Applied', status: 'Applied', - aiScore: e.atsScore, source: 'Microsoft Outlook', recruiter: job.recruiter, recruiterId: '', + aiScore: SEED_ATS_SCORE, source: 'Microsoft Outlook', recruiter: job.recruiter, recruiterId: '', applied: new Date(TODAY), education: "Bachelor's Degree", skills: job.skills.slice(0, 4), rating: '4.0', salary: 130000, matchedSkills: job.skills.slice(0, 3), missingSkills: job.skills.slice(3), recommendation: 'Potential Match', - subScores: { skills: e.atsScore, experience: 80, education: 80, keywords: e.atsScore, location: 100, salary: 90 }, + subScores: { skills: SEED_ATS_SCORE, experience: 80, education: 80, keywords: SEED_ATS_SCORE, location: 100, salary: 90 }, noticePeriod: '1 month', availability: '2 weeks', certifications: [], favorite: false, interviewStatus: 'Not Scheduled', }, @@ -492,8 +524,13 @@ function EmailTab({ query, jobs, updateCandidates, toast }) { {query.isPending ? 'Loading…' : query.isError ? 'Sync failed' : `${emails.length} messages · ${unread} unread`} - @@ -525,7 +562,7 @@ function EmailTab({ query, jobs, updateCandidates, toast }) { {isImported(e) && Imported} -
{fmtShort(e.when)}
+
{e.when ? fmtShort(e.when) : '—'}
))} @@ -547,7 +584,9 @@ function EmailTab({ query, jobs, updateCandidates, toast }) {
{selected.from}
-
{selected.fromEmail} · {fmtDate(selected.when)}
+
+ {selected.fromEmail} · {selected.when ? fmtDate(selected.when) : 'Date unavailable'} +
@@ -564,7 +603,6 @@ function EmailTab({ query, jobs, updateCandidates, toast }) {
{selected.attachmentSize} · PDF
-