Merge pull request #1254 from pateljannat/jobs-page-responsive
fix: improved jobs page ui
This commit is contained in:
@@ -1,71 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex rounded p-1 lg:px-2 lg:py-4 hover:bg-gray-100">
|
<div class="flex space-x-4 border rounded-md p-2">
|
||||||
<div class="flex w-3/5 md:w-2/5">
|
<Avatar :image="job.company_logo" :label="job.job_title" size="2xl" />
|
||||||
<img
|
<div class="flex flex-col space-y-2 flex-1">
|
||||||
:src="job.company_logo"
|
<div class="flex items-center justify-between">
|
||||||
class="w-12 h-12 rounded-lg object-contain mr-4"
|
<span class="font-semibold">
|
||||||
:alt="job.company_name"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<div class="font-medium mb-1">
|
|
||||||
{{ job.job_title }}
|
{{ job.job_title }}
|
||||||
</div>
|
|
||||||
<div class="text-gray-700">
|
|
||||||
{{ job.company_name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-end w-1/5 text-gray-700">
|
|
||||||
{{ job.location.replace(',', '').split(' ')[0] }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex justify-end w-1/5 text-gray-700 text-right hidden md:block"
|
|
||||||
>
|
|
||||||
{{ job.type }}
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-end w-1/5 text-sm text-gray-700 text-right">
|
|
||||||
{{ dayjs(job.creation).format('DD MMM YYYY') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- <div class="flex flex-col shadow rounded-md p-4 h-full">
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<div>
|
|
||||||
<div class="text-xl font-semibold mb-2">
|
|
||||||
{{ job.job_title }}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ __("posted by") }}
|
|
||||||
<span class="font-medium">
|
|
||||||
{{ job.company_name }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
:src="job.company_logo"
|
|
||||||
class="w-12 h-12 rounded-lg object-contain"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between mt-8">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<Badge :label="job.type" theme="green" size="lg" class="mr-4"/>
|
|
||||||
<Badge :label="job.location" theme="gray" size="lg">
|
|
||||||
<template #prefix>
|
|
||||||
<MapPin class="h-4 w-4 stroke-1.5" />
|
|
||||||
</template>
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="font-medium">
|
|
||||||
{{ dayjs(job.creation).format('DD MMM YYYY') }}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<Building2 class="w-4 h-4 stroke-1.5 text-gray-600" />
|
||||||
|
<span>
|
||||||
|
{{ job.company_name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<MapPin class="w-4 h-4 stroke-1.5 text-gray-600" />
|
||||||
|
<span>
|
||||||
|
{{ job.location }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<Shapes class="w-4 h-4 stroke-1.5 text-gray-600" />
|
||||||
|
<span>
|
||||||
|
{{ job.type }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<Calendar class="w-4 h-4 stroke-1.5 text-gray-600" />
|
||||||
|
<span> {{ __('posted') }} {{ dayjs(job.creation).fromNow() }} </span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { MapPin } from 'lucide-vue-next'
|
import { Building2, Calendar, MapPin, Shapes } from 'lucide-vue-next'
|
||||||
import { Badge } from 'frappe-ui'
|
|
||||||
import { inject } from 'vue'
|
import { inject } from 'vue'
|
||||||
|
import { Avatar } from 'frappe-ui'
|
||||||
|
|
||||||
const dayjs = inject('$dayjs')
|
const dayjs = inject('$dayjs')
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@@ -7,47 +7,63 @@
|
|||||||
class="h-7"
|
class="h-7"
|
||||||
:items="[{ label: __('Jobs'), route: { name: 'Jobs' } }]"
|
:items="[{ label: __('Jobs'), route: { name: 'Jobs' } }]"
|
||||||
/>
|
/>
|
||||||
<div class="flex space-x-2">
|
<router-link
|
||||||
<div class="w-40 md:w-44">
|
v-if="user.data?.name"
|
||||||
<FormControl
|
:to="{
|
||||||
v-model="jobType"
|
name: 'JobCreation',
|
||||||
type="select"
|
params: {
|
||||||
:options="jobTypes"
|
jobName: 'new',
|
||||||
:placeholder="__('Type')"
|
},
|
||||||
/>
|
}"
|
||||||
</div>
|
>
|
||||||
<div class="w-28 md:w-36">
|
<Button variant="solid">
|
||||||
<FormControl type="text" placeholder="Search" v-model="searchQuery">
|
<template #prefix>
|
||||||
<template #prefix>
|
<Plus class="h-4 w-4" />
|
||||||
<Search class="w-4 h-4 stroke-1.5 text-gray-600" name="search" />
|
</template>
|
||||||
</template>
|
{{ __('New Job') }}
|
||||||
</FormControl>
|
</Button>
|
||||||
</div>
|
</router-link>
|
||||||
<router-link
|
|
||||||
v-if="user.data?.name"
|
|
||||||
:to="{
|
|
||||||
name: 'JobCreation',
|
|
||||||
params: {
|
|
||||||
jobName: 'new',
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<Button variant="solid">
|
|
||||||
<template #prefix>
|
|
||||||
<Plus class="h-4 w-4" />
|
|
||||||
</template>
|
|
||||||
{{ __('New Job') }}
|
|
||||||
</Button>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div v-if="jobsList?.length">
|
<div>
|
||||||
<div class="lg:w-3/4 mx-auto p-5">
|
<div class="lg:w-3/4 mx-auto p-5">
|
||||||
<div class="text-xl font-semibold mb-5">
|
<div
|
||||||
{{ __('Find the perfect job for you') }}
|
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 font-semibold">
|
||||||
|
{{ __('Find the perfect job for you') }}
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<FormControl
|
||||||
|
type="text"
|
||||||
|
:placeholder="__('Search')"
|
||||||
|
v-model="searchQuery"
|
||||||
|
class="min-w-40 lg:min-w-0 lg:w-32 xl:w-40"
|
||||||
|
@input="updateJobs"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<Search
|
||||||
|
class="w-4 h-4 stroke-1.5 text-gray-600"
|
||||||
|
name="search"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl
|
||||||
|
v-model="jobType"
|
||||||
|
type="select"
|
||||||
|
:options="jobTypes"
|
||||||
|
class="min-w-40 lg:min-w-0 lg:w-32 xl:w-40"
|
||||||
|
:placeholder="__('Type')"
|
||||||
|
@change="updateJobs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="job in jobsList" class="divide-y">
|
|
||||||
|
<div
|
||||||
|
v-if="jobs.data?.length"
|
||||||
|
class="grid grid-cols-1 lg:grid-cols-2 gap-5"
|
||||||
|
>
|
||||||
<router-link
|
<router-link
|
||||||
|
v-for="job in jobs.data"
|
||||||
:to="{
|
:to="{
|
||||||
name: 'JobDetail',
|
name: 'JobDetail',
|
||||||
params: { job: job.name },
|
params: { job: job.name },
|
||||||
@@ -57,15 +73,15 @@
|
|||||||
<JobCard :job="job" />
|
<JobCard :job="job" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="text-gray-700 italic p-5 w-fit mx-auto">
|
||||||
|
{{ __('No jobs posted') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-gray-700 italic p-5 w-fit mx-auto">
|
|
||||||
{{ __('No jobs posted') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Button, Breadcrumbs, createResource, FormControl } from 'frappe-ui'
|
import { Button, Breadcrumbs, createListResource, FormControl } from 'frappe-ui'
|
||||||
import { Plus, Search } from 'lucide-vue-next'
|
import { Plus, Search } from 'lucide-vue-next'
|
||||||
import { inject, computed, ref, onMounted } from 'vue'
|
import { inject, computed, ref, onMounted } from 'vue'
|
||||||
import JobCard from '@/components/JobCard.vue'
|
import JobCard from '@/components/JobCard.vue'
|
||||||
@@ -74,43 +90,59 @@ import { updateDocumentTitle } from '@/utils'
|
|||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const jobType = ref(null)
|
const jobType = ref(null)
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
|
const filters = ref({})
|
||||||
|
const orFilters = ref({})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let queries = new URLSearchParams(location.search)
|
let queries = new URLSearchParams(location.search)
|
||||||
if (queries.has('type')) {
|
if (queries.has('type')) {
|
||||||
jobType.value = queries.get('type')
|
jobType.value = queries.get('type')
|
||||||
}
|
}
|
||||||
|
updateJobs()
|
||||||
})
|
})
|
||||||
|
|
||||||
const jobs = createResource({
|
const jobs = createListResource({
|
||||||
url: 'lms.lms.api.get_job_opportunities',
|
doctype: 'Job Opportunity',
|
||||||
cache: ['jobs'],
|
fields: [
|
||||||
auto: true,
|
'name',
|
||||||
|
'job_title',
|
||||||
|
'company_name',
|
||||||
|
'company_logo',
|
||||||
|
'location',
|
||||||
|
'type',
|
||||||
|
'creation',
|
||||||
|
],
|
||||||
|
start: 0,
|
||||||
|
pageLength: 20,
|
||||||
|
cache: ['jobOpportunities'],
|
||||||
})
|
})
|
||||||
|
|
||||||
const pageMeta = computed(() => {
|
const updateJobs = () => {
|
||||||
return {
|
updateFilters()
|
||||||
title: 'Jobs',
|
jobs.update({
|
||||||
description: 'An open job board for the community',
|
filters: filters.value,
|
||||||
|
orFilters: orFilters.value,
|
||||||
|
})
|
||||||
|
jobs.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateFilters = () => {
|
||||||
|
if (jobType.value) {
|
||||||
|
filters.value.type = jobType.value
|
||||||
|
} else {
|
||||||
|
delete filters.value.type
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
const jobsList = computed(() => {
|
|
||||||
let jobData = jobs.data
|
|
||||||
if (jobType.value && jobType.value != '') {
|
|
||||||
jobData = jobData.filter((job) => job.type == jobType.value)
|
|
||||||
}
|
|
||||||
if (searchQuery.value) {
|
if (searchQuery.value) {
|
||||||
let query = searchQuery.value.toLowerCase()
|
orFilters.value = {
|
||||||
jobData = jobData.filter(
|
job_title: ['like', `%${searchQuery.value}%`],
|
||||||
(job) =>
|
company_name: ['like', `%${searchQuery.value}%`],
|
||||||
job.job_title.toLowerCase().includes(query) ||
|
location: ['like', `%${searchQuery.value}%`],
|
||||||
job.company_name.toLowerCase().includes(query) ||
|
}
|
||||||
job.location.toLowerCase().includes(query)
|
} else {
|
||||||
)
|
orFilters.value = {}
|
||||||
}
|
}
|
||||||
return jobData
|
}
|
||||||
})
|
|
||||||
|
|
||||||
const jobTypes = computed(() => {
|
const jobTypes = computed(() => {
|
||||||
return [
|
return [
|
||||||
@@ -121,6 +153,12 @@ const jobTypes = computed(() => {
|
|||||||
{ label: __('Freelance'), value: 'Freelance' },
|
{ label: __('Freelance'), value: 'Freelance' },
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
const pageMeta = computed(() => {
|
||||||
|
return {
|
||||||
|
title: 'Jobs',
|
||||||
|
description: 'An open job board for the community',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
updateDocumentTitle(pageMeta)
|
updateDocumentTitle(pageMeta)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user