diff --git a/frontend/src/pages/JobApplications.vue b/frontend/src/pages/JobApplications.vue index a54f6865..25f00d80 100644 --- a/frontend/src/pages/JobApplications.vue +++ b/frontend/src/pages/JobApplications.vue @@ -8,17 +8,19 @@ :items="[ { label: __('Jobs'), route: { name: 'Jobs' } }, { - label: job.data?.job_title, - route: { name: 'JobDetail', params: { job: job.data?.name } }, + label: applications.data?.[0]?.job_title, + route: { name: 'JobDetail', params: { job: props.job } }, }, { label: __('Applications') }, ]" /> -
+

- {{ __('Applications for {0}').format(job.data.job_title) }} + {{ + __('Applications for {0}').format(applications.data?.[0]?.job_title) + }}

{{ applications.data?.length || 0 }} @@ -30,75 +32,83 @@

-
-
+ -
-
- + + + + +
- + +
+ +
+ {{ item }}
-
-

- {{ application.full_name || application.user }} -

-

{{ application.email }}

-

- {{ - __('Applied on {0}').format( - dayjs(application.creation).format('MMM DD, YYYY') - ) - }} -

+
+ +
-
-
- - -
-
-
-
+
+ {{ item }} +
+ + + +
+
{{ __('Message') }}
{ - applications.submit() +const applications = createListResource({ + doctype: 'LMS Job Application', + fields: [ + 'name', + 'user.user_image as user_image', + 'user.full_name as full_name', + 'user.email as email', + 'creation', + 'resume', + 'job.job_title as job_title', + ], + filters: { + job: props.job, }, -}) - -const applications = createResource({ - url: 'lms.lms.api.get_job_applications', - params: { job: props.job }, auto: true, }) const emailResource = createResource({ - url: 'lms.lms.api.send_email_to_applicant', + url: 'frappe.core.doctype.communication.email.make', + makeParams(values) { + return { + recipients: selectedApplicant.value.email, + cc: emailForm.replyTo, + subject: emailForm.subject, + content: emailForm.message, + doctype: 'LMS Job Application', + name: selectedApplicant.value.name, + send_email: 1, + now: true, + } + }, }) const openEmailModal = (applicant) => { selectedApplicant.value = applicant - emailSubject.value = `Job Application for ${job.data?.job_title} - ${ - applicant.full_name || applicant.user - }` - emailMessage.value = '' + emailForm.subject = `Job Application for ${applications.data?.[0]?.job_title} - ${applicant.full_name}` + emailForm.replyTo = '' + emailForm.message = '' showEmailModal.value = true } const sendEmail = (close) => { - if (!emailSubject.value || !emailMessage.value) { - toast.error(__('Please fill in all fields')) - return - } - emailResource.submit( + {}, { - applicant_email: selectedApplicant.value.email, - subject: emailSubject.value, - message: emailMessage.value, - job: props.job, - }, - { + validate() { + if (!emailForm.subject) { + return __('Subject is required') + } + if (!emailForm.message) { + return __('Message is required') + } + }, onSuccess: () => { toast.success(__('Email sent successfully')) close() @@ -223,11 +258,43 @@ const downloadResume = (resumeUrl) => { window.open(resumeUrl, '_blank') } +const applicationColumns = computed(() => { + return [ + { + label: __('Name'), + key: 'display_name', + width: '15rem', + }, + { + label: __('Email'), + key: 'email', + width: '15rem', + }, + { + label: __('Applied On'), + key: 'applied_date', + width: '10rem', + }, + { + label: __('Actions'), + key: 'actions', + width: '10rem', + }, + ] +}) + +const applicantRows = computed(() => { + if (!applications.data) return [] + return applications.data.map((application) => ({ + ...application, + display_name: application.full_name, + applied_date: dayjs(application.creation).format('MMM DD, YYYY'), + })) +}) + usePageMeta(() => { return { - title: job.data - ? `Applications - ${job.data.job_title}` - : 'Job Applications', + title: `Applications - ${applications.data?.[0]?.job_title}`, icon: brand.favicon, } }) diff --git a/frontend/src/pages/JobDetail.vue b/frontend/src/pages/JobDetail.vue index 4d337ac4..9df6ffc5 100644 --- a/frontend/src/pages/JobDetail.vue +++ b/frontend/src/pages/JobDetail.vue @@ -21,20 +21,14 @@ class="flex items-center space-x-2" > - { const canManageJob = computed(() => { if (!user.data?.name || !job.data) return false return ( - user.data.name === job.data.owner || - user.data.roles?.includes('System Manager') + user.data.name === job.data.owner || user.data.roles?.includes('Moderator') ) }) diff --git a/lms/lms/api.py b/lms/lms/api.py index c5f51daf..11f025a9 100644 --- a/lms/lms/api.py +++ b/lms/lms/api.py @@ -1651,60 +1651,6 @@ def get_progress_distribution(progressList): return distribution -@frappe.whitelist() -def get_job_applications(job): - """Get job applications for a specific job. Only job owner or system manager can access.""" - job_doc = frappe.get_doc("Job Opportunity", job) - - if job_doc.owner != frappe.session.user and "System Manager" not in frappe.get_roles(): - frappe.throw(_("You don't have permission to view applications for this job")) - - applications = frappe.get_all( - "LMS Job Application", - filters={"job": job}, - fields=[ - "name", - "user", - "resume", - "creation", - "user.full_name as full_name", - "user.email as email", - "user.user_image as user_image", - ], - order_by="creation desc", - ) - - for app in applications: - if app.resume: - file_url = frappe.db.get_value("File", {"file_name": app.resume}, "file_url") - if file_url: - app.resume_url = file_url - - return applications - - -@frappe.whitelist() -def send_email_to_applicant(applicant_email, subject, message, job): - """Send email to job applicant. Only job owners or system managers can send emails.""" - job_owner = frappe.db.get_value("Job Opportunity", job, "owner") - - if "System Manager" not in frappe.get_roles(): - if job_owner != frappe.session.user: - frappe.throw(_("You don't have permission to send emails for this job")) - - job_owner_email = frappe.db.get_value("User", job_owner, "email") - - frappe.sendmail( - recipients=[applicant_email], - subject=subject, - message=message, - reply_to=job_owner_email, - now=True, - ) - - return {"success": True, "message": "Email sent successfully"} - - @frappe.whitelist(allow_guest=True) def get_pwa_manifest(): title = frappe.db.get_single_value("Website Settings", "app_name") or "Frappe Learning"