Commit 8cff260f authored by Sandro Lutz's avatar Sandro Lutz Committed by Sandro Lutz
Browse files

Add FilteredList to job offers page

parent cf332885
......@@ -19,7 +19,6 @@ import board from './views/amiv/board';
import minutes from './views/amiv/minutes';
import commissions from './views/amiv/commissions';
import jobOfferList from './views/jobs/jobofferList';
import jobOfferDetails from './views/jobs/jobofferDetails';
import companyList from './views/companies/companyList';
import companyDetail from './views/companies/companyDetail';
import legalNotice from './views/legalNotice';
......@@ -103,8 +102,8 @@ Raven.context(() => {
view: () => m(jobOfferList),
},
{
url: '/:language/jobs/:jobId',
view: vnode => m(jobOfferDetails, vnode.attrs),
url: '/:language/jobs/:offerId',
view: vnode => m(jobOfferList, vnode.attrs),
},
{
url: '/:language/companies',
......
......@@ -127,6 +127,9 @@
"companies.website": "Webseite",
"companies.employees": "Mitarbeiter",
"companies.employees_worldwide": "Weltweit",
"companies.employees_Switzerland": "in der Schweiz"
"companies.employees_Switzerland": "in der Schweiz",
"joboffers.searchfield": "Suchtext eingeben...",
"joboffers.not_found": "Jobangebot nicht gefunden",
"joboffers.no_selection": "Kein Jobangebot ausgewählt"
}
}
......@@ -127,6 +127,10 @@
"companies.website": "Website",
"companies.employees": "Employees",
"companies.employees_worldwide": "Worldwide",
"companies.employees_Switzerland": "in Switzerland"
"companies.employees_Switzerland": "in Switzerland",
"joboffers.searchfield": "Enter search text...",
"joboffers.not_found": "Job offer not found",
"joboffers.no_selection": "No job offer selected"
}
}
import m from 'mithril';
import { apiUrl } from 'config';
import { getToken } from './auth';
import { currentLanguage } from './language';
import Query from './query';
const date = `${new Date().toISOString().split('.')[0]}Z`;
let querySaved = {};
/**
* Get the loaded list of joboffers.
*
* @return {array}
*/
export function getList() {
if (!this.list) {
return [];
}
return this.list;
}
/**
* Get the selected joboffer.
*
* @return {Object} `joboffer` object returned by the AMIV API.
*/
export function getSelectedOffer() {
return this.selectedOffer;
}
/**
* Load joboffers from the AMIV API
*
* @param {*} query filter and sort query for the API request.
* @return {Promise} exports for additional response handling
*/
export function load(query = {}) {
querySaved = Query.copy(query);
const queryString = Query.buildQueryString(query);
return m
.request({
method: 'GET',
url: `${apiUrl}/joboffers?${queryString}`,
headers: {
Authorization: `Token ${getToken()}`,
},
})
.then(result => {
this.list = result._items.map(event => {
const newOffer = Object.assign({}, event);
newOffer.title = newOffer[`title_${currentLanguage()}`];
newOffer.description = newOffer[`description_${currentLanguage()}`];
return newOffer;
});
});
}
/**
* Select a joboffer from the list.
*
* @param {String} offerId joboffer id from AMIV API
*/
export function selectOffer(offerId) {
this.selectedOffer = this.getList().find(item => item._id === offerId);
if (!this.selectedOffer) {
this.load({
where: {
time_end: { $gte: date },
show_website: true,
},
sort: ['time_end'],
}).then(() => {
this.selectedOffer = this.getList().find(item => item._id === offerId);
});
}
}
/**
* Reload joboffer list with the same query as before.
*
* @return {Promise} exports for additional response handling
*/
export function reload() {
return load(querySaved);
}
import { currentLanguage } from '../language';
/**
* Joboffer class
*
* Wrapper for the event object received from the API with additional functions.
*/
export default class Joboffer {
/**
* Constructor
*
* @param {object} joboffer object loaded from the API
*/
constructor(joboffer) {
// Expose all properties of `joboffer`
Object.keys(joboffer).forEach(key => {
this[key] = joboffer[key];
});
}
getTitle() {
const otherLanguage = currentLanguage() === 'en' ? 'de' : 'en';
return this[`title_${currentLanguage()}`] || this[`title_${otherLanguage}`];
}
getDescription() {
const otherLanguage = currentLanguage() === 'en' ? 'de' : 'en';
return this[`description_${currentLanguage()}`] || this[`description_${otherLanguage}`];
}
}
import m from 'mithril';
import { apiUrl } from 'config';
import { getToken } from '../auth';
import Query from '../query';
import Joboffer from './Joboffer';
import PaginationController from '../pagination';
/**
* JobofferController class (inherited from `PaginationController`)
*
* Used to handle a list of job offers
*/
export default class JobofferController extends PaginationController {
constructor(query = {}, additionalQuery = {}) {
super(
'joboffers',
query,
Query.merge(additionalQuery, () => {
const date = `${new Date().toISOString().split('.')[0]}Z`;
return {
where: {
time_end: { $gte: date },
show_website: true,
},
sort: ['time_end'],
};
})
);
}
/**
* Set a new query to load the configured resource
*
* @public
*/
async setQuery(query) {
const newQuery = JSON.stringify(query || {});
const oldQuery = JSON.stringify(this.query);
// ignore if query has not changed
if (newQuery === oldQuery) return false;
super.setQuery(query);
return this.loadPageData(1);
}
/**
* Load a specific job offer from the AMIV API
*
* @param {String} jobofferId
* @public
*/
async loadJoboffer(jobofferId) {
this._selectedJoboffer = new Joboffer(
await m.request({
method: 'GET',
url: `${apiUrl}/joboffers/${jobofferId}`,
headers: {
Authorization: getToken(),
},
})
);
return this._selectedJoboffer;
}
/**
* Get the previously loaded job offer
*
* @return {object} job offer from the AMIV API
* @public
*/
get selectedJoboffer() {
return this._selectedJoboffer;
}
async _loadData(query) {
const items = await super._loadData(query);
return items.map(joboffer => new Joboffer(joboffer));
}
}
export { default as Joboffer } from './Joboffer';
export { default as JobofferController } from './JobofferController';
import m from 'mithril';
import { EventController } from '../models/events';
import * as jobs from '../models/joboffers';
import { JobofferController } from '../models/joboffers';
import { i18n } from '../models/language';
import { Card } from '../components';
const date = `${new Date().toISOString().split('.')[0]}Z`;
// Render the Hot Cards, with link and imageurl
const renderHotCards = (item, index) => {
if (index === 0) return m('div.hot-first-card', m(Card, item));
......@@ -24,19 +22,16 @@ export default class Frontpage {
},
false
);
jobs.load({
where: {
time_end: { $gte: date },
show_website: true,
},
sort: ['time_end'],
});
this.jobOfferController = new JobofferController({ max_results: 3 });
this.events = [];
this.eventController.upcomingEvents.getPageData(1).then(events => {
this.events = events;
});
this.jobs = jobs.getList().slice(0, 3);
this.jobs = [];
this.jobOfferController.getPageData(1).then(jobs => {
this.jobs = jobs;
});
// MOCKDATA
this.hot = [
......@@ -71,10 +66,6 @@ export default class Frontpage {
];
}
onbeforeupdate() {
this.jobs = jobs.getList().slice(0, 3);
}
view() {
return m('div#frontpage-container', [
m('h2', i18n('frontpage.whats_hot')),
......
......@@ -2,27 +2,24 @@ import m from 'mithril';
import marked from 'marked';
import escape from 'html-escape';
import { apiUrl } from 'config';
import * as jobs from '../../models/joboffers';
import { log } from '../../models/log';
import { i18n } from '../../models/language';
export default class JobOfferDetails {
export default class JobofferDetails {
static oninit(vnode) {
jobs.selectOffer(vnode.attrs.jobId);
this.controller = vnode.attrs.controller;
}
static view() {
const jobOffer = jobs.getSelectedOffer();
if (!jobOffer) {
return m('');
const joboffer = this.controller.selectedJoboffer;
if (!joboffer) {
return m('h1', i18n('joboffers.not_found'));
}
log(jobs.getSelectedOffer());
return m('div', [
m('h1', jobOffer.title),
m('img', { src: `${apiUrl}${jobOffer.logo.file}`, alt: jobOffer.company }),
m('p', m.trust(marked(escape(jobOffer.description)))),
m('a', { href: `${apiUrl}${jobOffer.pdf.file}`, target: '_blank' }, 'Download as PDF'),
m('h1', joboffer.getTitle()),
m('img', { src: `${apiUrl}${joboffer.logo.file}`, alt: joboffer.company }),
m('p', m.trust(marked(escape(joboffer.getDescription())))),
m('a', { href: `${apiUrl}${joboffer.pdf.file}`, target: '_blank' }, 'Download as PDF'),
]);
}
}
import m from 'mithril';
import { apiUrl } from 'config';
import * as jobs from '../../models/joboffers';
import { currentLanguage } from '../../models/language';
import { i18n, currentLanguage } from '../../models/language';
import { JobofferController } from '../../models/joboffers';
import { FilteredListPage, FilteredListDataStore } from '../filteredListPage';
import JobofferDetails from './jobofferDetails';
const date = `${new Date().toISOString().split('.')[0]}Z`;
const controller = new JobofferController();
const dataStore = new FilteredListDataStore();
export default class JobOfferList {
static oninit() {
jobs.load({
where: {
time_end: { $gte: date },
show_website: true,
/**
* JobOfferList class
*
* Used to show the job offers page including the FilterView and the job offer details page.
*/
export default class JobofferList extends FilteredListPage {
constructor() {
super('joboffer', dataStore, true);
}
oninit(vnode) {
super.oninit(vnode, vnode.attrs.offerId);
}
// eslint-disable-next-line class-methods-use-this
_loadItem(offerId) {
return controller.loadJoboffer(offerId);
}
// eslint-disable-next-line class-methods-use-this
_reloadData() {
return controller.loadPageData(1);
}
get _filterViewAttributes() {
return {
fields: [
{
type: 'text',
key: 'title',
label: i18n('joboffers.searchfield'),
min_length: 3,
},
{
type: 'button',
label: i18n('search'),
events: {
onclick: 'search',
},
},
{
type: 'button',
label: i18n('reset'),
className: 'flat-button',
events: {
onclick: 'reset',
},
},
],
onchange: values => {
const query = {};
this.dataStore.filterValues = values;
Object.keys(values).forEach(key => {
const value = values[key];
if (key === 'title' && value.length > 0) {
query.$or = [
{ title_en: { $regex: `^(?i).*${value}.*` } },
{ title_de: { $regex: `^(?i).*${value}.*` } },
{ description_en: { $regex: `^(?i).*${value}.*` } },
{ description_de: { $regex: `^(?i).*${value}.*` } },
{ company: { $regex: `^(?i).*${value}.*` } },
{ company: { $regex: `^(?i).*${value}.*` } },
];
}
});
controller.setQuery({ where: query });
},
sort: ['time_end'],
});
};
}
static onbeforeupdate(vnode, old) {
// when attrs are different it means we changed route
if (vnode.attrs.id !== old.attrs.id) {
jobs.reload();
get _listView() {
return controller.map(page =>
page.map(joboffer => this.constructor._renderJobofferListItem(joboffer))
);
}
// eslint-disable-next-line class-methods-use-this
get _detailsView() {
return m(JobofferDetails, { controller });
}
// eslint-disable-next-line class-methods-use-this
get _detailsPlaceholderView() {
return m('h1', i18n('joboffers.no_selection'));
}
// eslint-disable-next-line class-methods-use-this
async _loadNextPage() {
const newPage = controller.lastLoadedPage + 1;
if (newPage <= controller.totalPages) {
await controller.loadPageData(newPage);
}
}
static view() {
return m('table', [
m('thead', [m('tr', [m('th', 'Company'), m('th', 'Title'), m('th', 'Details')])]),
m(
'tbody',
jobs
.getList()
.map(job =>
m('tr', [
m(
'td',
m('img', { src: `${apiUrl}${job.logo.file}`, width: '150px', alt: job.company })
),
m('td', job.title),
m(
'td',
m(
'a',
{ href: `/${currentLanguage()}/jobs/${job._id}`, oncreate: m.route.link },
'Details'
)
),
])
)
),
]);
// eslint-disable-next-line class-methods-use-this
_hasMorePagesToLoad() {
return controller.lastLoadedPage < controller.totalPages;
}
static _renderJobofferListItem(joboffer) {
return m(
'div',
{
class: 'list-item',
onclick: () => {
m.route.set(`/${currentLanguage()}/jobs/${joboffer._id}`);
},
},
[
m('img', { src: `${apiUrl}${joboffer.logo.file}`, width: '150px', alt: joboffer.company }),
m('h2', joboffer.getTitle()),
]
);
}
}
......@@ -7,6 +7,7 @@
@import './footer.less';
@import './eventList.less';
@import './eventDetails.less';
@import './jobofferList.less';
html,body {
width: 100%;
......
@import './listview.less';
#joboffer-list {
.listview;
div {
border: none;
}
.list-item {
border-bottom: 1px solid black;
cursor: pointer;
h2 {
display: inline-block;
margin: 0;
padding: 1em 0;
}
}
.registration {
background-color: #FFF;
}
.upcoming {
background-color: #BBB;
}
.past {
background-color: #777;
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment