feat: redesigned job list
This commit is contained in:
@@ -1,22 +1,35 @@
|
||||
<template>
|
||||
<div class="flex flex-col border rounded-md p-4 h-full">
|
||||
<div
|
||||
class="flex flex-col shadow-sm border rounded-md p-3 h-full hover:bg-surface-gray-2"
|
||||
>
|
||||
<div class="flex space-x-4 mb-2">
|
||||
<img :src="job.company_logo" class="size-8 rounded-full object-contain" />
|
||||
<div class="flex flex-col space-y-1 flex-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold text-ink-gray-9">
|
||||
{{ job.job_title }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-ink-gray-5">
|
||||
<div class="flex flex-col space-y-2 flex-1">
|
||||
<div class="text-lg font-semibold text-ink-gray-9">
|
||||
{{ job.company_name }}
|
||||
</div>
|
||||
<span class="font-medium text-ink-gray-7">
|
||||
{{ job.job_title }}
|
||||
</span>
|
||||
<div class="flex items-center space-x-1 text-sm text-ink-gray-7">
|
||||
<MapPin class="size-3" />
|
||||
<span>
|
||||
{{ job.location }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="job.applicants"
|
||||
class="flex items-center space-x-1 text-sm text-ink-gray-7"
|
||||
>
|
||||
<User class="size-3" />
|
||||
<span>
|
||||
{{ job.applicants }}
|
||||
{{ job.applicants > 1 ? __('applicants') : __('applicant') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <img :src="job.company_logo" alt="Company Logo" class="h-8 object-contain bg-white" /> -->
|
||||
</div>
|
||||
<div class="space-x-4 mt-auto">
|
||||
<Badge>
|
||||
{{ job.location }}
|
||||
</Badge>
|
||||
<div class="space-x-4 mt-2 mb-4">
|
||||
<Badge>
|
||||
{{ job.type }}
|
||||
</Badge>
|
||||
@@ -24,11 +37,16 @@
|
||||
{{ dayjs(job.creation).fromNow() }}
|
||||
</Badge>
|
||||
</div>
|
||||
<div
|
||||
class="description text-ink-gray-9 text-sm"
|
||||
v-html="job.description"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { inject } from 'vue'
|
||||
import { Badge } from 'frappe-ui'
|
||||
import { MapPin, User } from 'lucide-vue-next'
|
||||
|
||||
const dayjs = inject('$dayjs')
|
||||
const props = defineProps({
|
||||
@@ -38,3 +56,15 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
.description {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
margin-top: auto;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<router-link
|
||||
v-if="user.data.name == job.data?.owner"
|
||||
:to="{
|
||||
name: 'JobCreation',
|
||||
name: 'JobForm',
|
||||
params: { jobName: job.data?.name },
|
||||
}"
|
||||
>
|
||||
@@ -62,7 +62,7 @@
|
||||
<div class="flex items-center">
|
||||
<img
|
||||
:src="job.data.company_logo"
|
||||
class="w-16 h-16 rounded-lg object-contain cursor-pointer mr-4"
|
||||
class="size-15 rounded-lg object-contain cursor-pointer mr-4"
|
||||
:alt="job.data.company_name"
|
||||
@click="redirectToWebsite(job.data.company_website)"
|
||||
/>
|
||||
@@ -75,7 +75,7 @@
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-10 gap-y-5 md:gap-y-5"
|
||||
>
|
||||
<div class="flex items-center space-x-4">
|
||||
<Building2 class="h-4 w-4 text-ink-green-2" />
|
||||
<Building2 class="size-4 stroke-1.5 text-ink-gray-7" />
|
||||
<div class="flex flex-col space-y-1 text-ink-gray-7">
|
||||
<span class="text-xs text-ink-gray-5 font-medium uppercase">
|
||||
{{ __('Organisation') }}
|
||||
@@ -86,9 +86,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<MapPin class="size-4 text-ink-red-3" />
|
||||
<MapPin class="size-4 stroke-1.5 text-ink-gray-7" />
|
||||
<div class="flex flex-col space-y-1 text-ink-gray-7">
|
||||
<span class="text-xs font-medium uppercase">
|
||||
<span class="text-xs text-ink-gray-5 font-medium uppercase">
|
||||
{{ __('Location') }}
|
||||
</span>
|
||||
<span class="text-sm font-semibold">
|
||||
@@ -97,9 +97,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<ClipboardType class="h-4 w-4 text-yellow-500" />
|
||||
<ClipboardType class="size-4 stroke-1.5 text-ink-gray-7" />
|
||||
<div class="flex flex-col space-y-1 text-ink-gray-7">
|
||||
<span class="text-xs font-medium uppercase">
|
||||
<span class="text-xs text-ink-gray-5 font-medium uppercase">
|
||||
{{ __('Category') }}
|
||||
</span>
|
||||
<span class="text-sm font-semibold">
|
||||
@@ -108,9 +108,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<CalendarDays class="h-4 w-4 text-ink-blue-2" />
|
||||
<CalendarDays class="size-4 stroke-1.5 text-ink-gray-7" />
|
||||
<div class="flex flex-col space-y-1 text-ink-gray-7">
|
||||
<span class="text-xs font-medium uppercase">
|
||||
<span class="text-xs text-ink-gray-5 font-medium uppercase">
|
||||
{{ __('Posted on') }}
|
||||
</span>
|
||||
<span class="text-sm font-semibold">
|
||||
@@ -122,9 +122,9 @@
|
||||
v-if="applicationCount.data"
|
||||
class="flex items-center space-x-4"
|
||||
>
|
||||
<SquareUserRound class="h-4 w-4 text-purple-500" />
|
||||
<SquareUserRound class="size-4 stroke-1.5 text-ink-gray-7" />
|
||||
<div class="flex flex-col space-y-1 text-ink-gray-7">
|
||||
<span class="text-xs font-medium uppercase">
|
||||
<span class="text-xs text-ink-gray-5 font-medium uppercase">
|
||||
{{ __('Applications Received') }}
|
||||
</span>
|
||||
<span class="text-sm font-semibold">
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="text-lg font-semibold mb-4">
|
||||
{{ __('Job Details') }}
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-2 gap-5">
|
||||
<div>
|
||||
<FormControl
|
||||
v-model="job.job_title"
|
||||
@@ -45,25 +45,12 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label class="block text-ink-gray-5 text-xs mb-1">
|
||||
{{ __('Description') }}
|
||||
<span class="text-ink-red-3">*</span>
|
||||
</label>
|
||||
<TextEditor
|
||||
:content="job.description"
|
||||
@change="(val) => (job.description = val)"
|
||||
:editable="true"
|
||||
:fixedMenu="true"
|
||||
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] mb-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container mb-4 pb-4">
|
||||
<div class="container border-b mb-4 pb-4">
|
||||
<div class="text-lg font-semibold mb-4">
|
||||
{{ __('Company Details') }}
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-2 gap-5">
|
||||
<div>
|
||||
<FormControl
|
||||
v-model="job.company_name"
|
||||
@@ -128,6 +115,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container mt-4">
|
||||
<label class="block text-ink-gray-5 text-xs mb-1">
|
||||
{{ __('Description') }}
|
||||
<span class="text-ink-red-3">*</span>
|
||||
</label>
|
||||
<TextEditor
|
||||
:content="job.description"
|
||||
@change="(val) => (job.description = val)"
|
||||
:editable="true"
|
||||
:fixedMenu="true"
|
||||
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] mb-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -317,7 +317,7 @@ const breadcrumbs = computed(() => {
|
||||
},
|
||||
{
|
||||
label: props.jobName == 'new' ? 'New Job' : 'Edit Job',
|
||||
route: { name: 'JobCreation' },
|
||||
route: { name: 'JobForm' },
|
||||
},
|
||||
]
|
||||
return crumbs
|
||||
@@ -10,7 +10,7 @@
|
||||
<router-link
|
||||
v-if="user.data?.name"
|
||||
:to="{
|
||||
name: 'JobCreation',
|
||||
name: 'JobForm',
|
||||
params: {
|
||||
jobName: 'new',
|
||||
},
|
||||
@@ -29,8 +29,11 @@
|
||||
<div
|
||||
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between mb-5"
|
||||
>
|
||||
<div class="text-xl text-ink-gray-9 font-semibold">
|
||||
{{ __('Find the perfect job for you') }}
|
||||
<div
|
||||
v-if="jobCount"
|
||||
class="text-xl font-semibold text-ink-gray-7 mb-4 md:mb-0"
|
||||
>
|
||||
{{ __('{0} Open Jobs').format(jobCount) }}
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<FormControl
|
||||
@@ -58,7 +61,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-5">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<router-link
|
||||
v-for="job in jobs.data"
|
||||
:to="{
|
||||
@@ -94,6 +97,7 @@
|
||||
import {
|
||||
Button,
|
||||
Breadcrumbs,
|
||||
call,
|
||||
createResource,
|
||||
FormControl,
|
||||
usePageMeta,
|
||||
@@ -109,6 +113,7 @@ const { brand } = sessionStore()
|
||||
const searchQuery = ref('')
|
||||
const filters = ref({})
|
||||
const orFilters = ref({})
|
||||
const jobCount = ref(0)
|
||||
|
||||
onMounted(() => {
|
||||
let queries = new URLSearchParams(location.search)
|
||||
@@ -116,6 +121,7 @@ onMounted(() => {
|
||||
jobType.value = queries.get('type')
|
||||
}
|
||||
updateJobs()
|
||||
getJobCount()
|
||||
})
|
||||
|
||||
const jobs = createResource({
|
||||
@@ -155,6 +161,17 @@ const updateFilters = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const getJobCount = () => {
|
||||
call('frappe.client.get_count', {
|
||||
doctype: 'Job Opportunity',
|
||||
filters: {
|
||||
status: 'Open',
|
||||
disabled: 0,
|
||||
},
|
||||
}).then((data) => {
|
||||
jobCount.value = data
|
||||
})
|
||||
}
|
||||
const jobTypes = computed(() => {
|
||||
return [
|
||||
'',
|
||||
|
||||
@@ -134,8 +134,8 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: '/job-opening/:jobName/edit',
|
||||
name: 'JobCreation',
|
||||
component: () => import('@/pages/JobCreation.vue'),
|
||||
name: 'JobForm',
|
||||
component: () => import('@/pages/JobForm.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
"type",
|
||||
"status",
|
||||
"section_break_6",
|
||||
"description",
|
||||
"company_details_section",
|
||||
"company_name",
|
||||
"company_website",
|
||||
"column_break_11",
|
||||
"column_break_phkm",
|
||||
"company_logo",
|
||||
"company_email_address"
|
||||
"company_email_address",
|
||||
"company_details_section",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -62,7 +62,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Company Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
@@ -72,8 +73,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "company_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Company Details"
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "company_name",
|
||||
@@ -89,10 +89,6 @@
|
||||
"label": "Company Website",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "company_logo",
|
||||
"fieldtype": "Attach Image",
|
||||
@@ -111,13 +107,23 @@
|
||||
"label": "Company Email Address",
|
||||
"options": "Email",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_phkm",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "LMS Job Application",
|
||||
"link_fieldname": "job"
|
||||
}
|
||||
],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2025-01-17 12:38:57.134919",
|
||||
"modified_by": "Administrator",
|
||||
"modified": "2025-04-24 13:21:24.311596",
|
||||
"modified_by": "sayali@frappe.io",
|
||||
"module": "Job",
|
||||
"name": "Job Opportunity",
|
||||
"owner": "Administrator",
|
||||
@@ -157,8 +163,9 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "job_title"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,9 +311,14 @@ def get_job_opportunities(filters=None, orFilters=None):
|
||||
"company_logo",
|
||||
"name",
|
||||
"creation",
|
||||
"description",
|
||||
],
|
||||
order_by="creation desc",
|
||||
)
|
||||
|
||||
for job in jobs:
|
||||
job.description = frappe.utils.strip_html_tags(job.description)
|
||||
job.applicants = frappe.db.count("LMS Job Application", {"job": job.name})
|
||||
return jobs
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user