Fakultas Ilmu Komputer UI

Commit 3bc3125a authored by M. Reza Qorib's avatar M. Reza Qorib
Browse files

[#144502159] [Refactor] #37 refactor Pagination component and all vacancy related components

parent 170c246a
......@@ -3,7 +3,6 @@ import Tabs from './components/Tabs';
import Pane from './components/Pane';
import VacancyList from './components/VacancyList';
import Pagination from './components/Pagination';
import { Menu, Icon } from 'semantic-ui-react';
export default class VacancyPage extends React.Component {
......@@ -37,36 +36,58 @@ export default class VacancyPage extends React.Component {
<Tabs selected={0}>
<Pane label="Semua Lowongan" >
<Pagination
key={1}
url="/vacancies/"
child={
<VacancyList
user={this.props.user}
key={1}
userId={this.state.id}
url="/vacancies/"
/>
}
/>
</Pane>
<Pane label="Lamaran saya" >
<VacancyList
<Pagination
key={2}
userId={this.state.id}
status="applied"
url={`/students/${this.state.id}/applied-vacancies/`}
child={
<VacancyList
user={this.props.user}
key={2}
userId={this.state.id}
/>
}
/>
</Pane>
<Pane label="Lamaran Ditandai" >
<VacancyList
<Pagination
key={3}
userId={this.state.id}
url={`/students/${this.state.id}/bookmarked-vacancies/`}
child={
<VacancyList
key={3}
user={this.props.user}
userId={this.state.id}
/>
}
/>
</Pane>
</Tabs>
);
} else if (this.props.user.role === 'company' || this.props.user.role === 'admin') {
return (
<VacancyList key={1} userId={this.state.id} url={`/companies/${this.state.id}/vacancies/`} type="company" />
<Pagination
url={`/companies/${this.state.id}/vacancies/`}
child={
<VacancyList
key={1}
user={this.props.user}
userId={this.state.id}
/>
}
error="Anda belum diverifikasi. Harap hubungi admin"
/>
);
}
......
......@@ -54,6 +54,37 @@ describe('VacancyList', () => {
verified: true,
}];
const newResponse = [
{
company: {
id: 3,
user: {
url: 'http://localhost:8000/api/users/8/',
username: 'Tutuplapak',
email: '',
is_staff: false,
},
name: 'Tutuplapak',
created: '2017-03-28T07:30:10.535240Z',
updated: '2017-05-07T13:22:19.175033Z',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla aliquet semper neque a fermentum. Duis ac tellus vitae augue iaculis ultrices. Curabitur commodo et neque nec feugiat. Morbi ac diam vel nunc commodo cursus. Phasellus nulla sapien, hendrerit vitae bibendum at, sollicitudin eu ante. Maecenas maximus, ante eu sollicitudin convallis, mauris nunc posuere risus, eu porttitor diam lacus vitae enim. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse at lectus a elit sollicitudin tempor. Nullam condimentum, justo nec tincidunt maximus, neque mi vulputate leo, sit amet lacinia massa ex eget sem. Duis ac erat facilisis, fringilla mauris in, consequat neque. In et neque consequat, vehicula magna at, efficitur ante. Mauris ac lacinia nibh.\r\n\r\nProin sagittis, lectus quis maximus varius, libero justo sollicitudin augue, non lacinia risus orci a enim. Curabitur iaculis enim quis ullamcorper commodo. Vivamus id nisi rhoncus, dignissim tellus quis, interdum est. Fusce sollicitudin eu libero ac feugiat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas semper posuere ex, sed accumsan libero iaculis faucibus. Fusce laoreet ac ligula ut consectetur. Donec tortor mauris, rutrum at sodales et, viverra in dolor. Sed bibendum elit et maximus volutpat. Phasellus justo ipsum, laoreet sit amet faucibus eu, ultricies suscipit mauris. Nullam aliquam libero eu ante ultrices mattis. Donec non justo hendrerit neque volutpat placerat. Ut euismod est nec sem mollis, sit amet porttitor massa rhoncus. Aenean id erat sit amet nunc ultrices scelerisque non in ipsum. Curabitur sollicitudin nulla id mi accumsan venenatis.',
registeredStatus: 1,
logo: 'http://localhost:8000/files/company-logo/8a258a48-3bce-4873-b5d1-538b360d0059.png',
address: 'Jl. Kebayoran Baru nomor 13, Jakarta Barat',
},
verified: true,
open_time: '2017-04-26T03:39:11Z',
description: 'deskripsi',
close_time: '2017-04-30T03:39:11Z',
created: '2017-04-26T03:39:39.916758Z',
updated: '2017-04-26T03:41:07.157634Z',
name: 'Kepala Sekolah',
registeredStatus: 1,
bookmarked: false,
id: 4,
},
];
it('renders without problem', () => {
fetchMock.get('*', response);
const vacancyList = ReactTestUtils.renderIntoDocument(
......@@ -152,7 +183,7 @@ describe('VacancyList', () => {
fetchMock.delete('*', 404);
fetchMock.get('*', response);
const vacancyList = ReactTestUtils.renderIntoDocument(
<VacancyList userId={1} url="test" deleteCallback={() => {}} />
<VacancyList userId={1} url="test" deleteCallback={() => {}} />,
);
vacancyList.state.vacancies = response;
vacancyList.deleteVacancy(1).then(() => {
......
......@@ -8,6 +8,7 @@ export default class ApplyModal extends React.Component {
data: React.PropTypes.object.isRequired,
active: React.PropTypes.bool.isRequired,
buttonTitle: React.PropTypes.string.isRequired,
resume: React.PropTypes.string.isRequired,
studentId: React.PropTypes.number.isRequired,
updateStatus: React.PropTypes.func.isRequired,
};
......@@ -65,11 +66,13 @@ export default class ApplyModal extends React.Component {
</Modal.Description>
{this.props.active && (
<div className="coverLetter">
<br />
<div className="linkCV">
<a> your latest CV </a>
<a href={this.props.resume} target="_blank" rel="noopener noreferrer"> Klik untuk lihat CV terakhirmu</a>
</div>
<br />
<div>
<h5> Write your Cover Letter </h5>
<h5>Cover Letter </h5>
<Form >
<TextArea placeholder="Tell us more" size="big" onChange={this.handleChange} />
</Form>
......
import React from 'react';
import { Menu, Container, Icon } from 'semantic-ui-react';
import { Menu, Container, Icon, Loader } from 'semantic-ui-react';
import Server from '../lib/Server';
import ModalAlert from '../components/ModalAlert';
......@@ -9,6 +9,11 @@ export default class Pagination extends React.Component {
static propTypes = {
url: React.PropTypes.string.isRequired,
child: React.PropTypes.node.isRequired,
error: React.PropTypes.string,
};
static defaultProps = {
error: 'Gagal Mengambil Data',
};
constructor(props) {
......@@ -20,20 +25,41 @@ export default class Pagination extends React.Component {
next: '',
prev: '',
url: this.props.url,
loading: true,
dir: 0,
start: true,
finish: false,
};
this.getItemsData = this.getItemsData.bind(this);
this.handleMovement = this.handleMovement.bind(this);
this.handleNext = this.handleNext.bind(this);
this.handlePrev = this.handlePrev.bind(this);
this.getItemsData = this.getItemsData.bind(this);
this.handleMovement = this.handleMovement.bind(this);
this.refresh = this.refresh.bind(this);
this.content = this.content.bind(this);
this.pageMenu = this.pageMenu.bind(this);
this.getItemsData();
}
getItemsData = () => Server.get(this.state.url, false).then((data) => {
this.setState({ items: data.items, next: data.next, prev: data.prev });
}, error => error.then((r) => {
this.modalAlert.open('Gagal Mengambil Data', r.error);
}));
this.setState({ current: this.state.current + this.state.dir });
this.setState(
{ items: data.results,
next: `${this.props.url}?page=${this.state.current + 1}`,
prev: `${this.props.url}?page=${this.state.current - 1}`,
loading: false,
});
let first = true;
let last = true;
if (data.previous) {
first = false;
}
if (data.next) {
last = false;
}
this.setState({ first, last });
}, error => error.then((r) => {
this.modalAlert.open(this.props.error, r.error);
}));
refresh() {
this.forceUpdate();
......@@ -41,35 +67,53 @@ export default class Pagination extends React.Component {
handleMovement(dir) {
const newUrl = this.state[dir];
this.setState({ url: newUrl });
this.getItemsData();
this.setState({ url: newUrl, loading: true }, function () {
this.getItemsData();
});
}
handlePrev() {
this.handleMovement('prev');
if (!this.state.first) {
this.setState({ dir: -1 }, function () {
this.handleMovement('prev');
});
}
}
handleNext = () => {
if (!this.state.last) {
this.setState({ dir: 1 }, function () {
this.handleMovement('next');
});
}
};
pageMenu() {
return (<Container textAlign="right">
<Menu pagination icon="labeled" className="vacancyList">
<Menu.Item name="prev" disabled={this.state.first} onClick={this.handlePrev}>
<span><Icon disabled={this.state.first} name="angle left" /></span>
</Menu.Item>
<Menu.Item name="current" active onClick={this.refresh}>
{this.state.current}
</Menu.Item>
<Menu.Item name="next" disabled={this.state.last} onClick={this.handleNext}>
<span><Icon disabled={this.state.last} name="angle right" /></span>
</Menu.Item>
</Menu>
</Container>);
}
handleNext() {
this.handleMovement('next');
content() {
return React.cloneElement(this.props.child, { items: this.state.items });
}
render = () => (
<div>
<Loader active={this.state.loading} />
<ModalAlert ref={(modal) => { this.modalAlert = modal; }} />
{this.props.child}
<Container textAlign="right">
<Menu pagination icon="labeled" className="vacancyList">
<Menu.Item name="prev" disabled={this.state.current !== 1} onClick={this.handlePrev}>
<span><Icon disabled={this.state.current !== 1} name="angle left" /></span>
</Menu.Item>
<Menu.Item name="current" active onClick={this.refresh}>
{this.state.current}
</Menu.Item>
<Menu.Item name="next" onClick={this.handleNext}>
<span><Icon name="angle right" /></span>
</Menu.Item>
</Menu>
</Container>
{!this.state.loading && this.content()}
{!this.state.loading && this.pageMenu()}
</div>
);
}
......@@ -4,21 +4,32 @@ import ApplyModal from './ApplyModal';
import Server from '../lib/Server';
import ConfirmationModal from './ConfirmationModal';
import ModalAlert from './ModalAlert';
import moment from 'moment';
const defaultImage = 'http://semantic-ui.com/images/wireframe/image.png';
export default class Vacancy extends React.Component {
static propTypes = {
studentId: React.PropTypes.number.isRequired,
user: React.PropTypes.object.isRequired,
// studentId: React.PropTypes.number.isRequired,
data: React.PropTypes.object.isRequired,
bookmarked: React.PropTypes.number,
status: React.PropTypes.string.isRequired,
applicationStatus: React.PropTypes.string,
status: React.PropTypes.number,
};
static defaultProps = {
bookmarked: 0,
applicationStatus: '',
status: 0,
};
static APPLICATION_STATUS_TEXT = ['Dikirim', 'Dibaca', 'Dibaca', 'Ditolak', 'Diterima'];
static APPLICATION_STATUS = {
NEW: 0,
READ: 1,
BOOKMARKED: 2,
REJECTED: 3,
ACCEPTED: 4,
};
constructor(props) {
......@@ -26,9 +37,10 @@ export default class Vacancy extends React.Component {
/* istanbul ignore next */
this.state = {
bookmarked: this.props.bookmarked,
status: this.props.status,
registeredStatus: this.props.status ? 'registered' : 'new',
deleteLoading: false,
};
moment.locale('id');
this.bookmark = this.bookmark.bind(this);
this.updateStatus = this.updateStatus.bind(this);
this.generateAction = this.generateAction.bind(this);
......@@ -39,20 +51,20 @@ export default class Vacancy extends React.Component {
bookmark() {
const data = { vacancy_id: this.props.data.id };
if (this.state.bookmarked < 1) {
Server.post(`/students/${this.props.studentId}/bookmarked-vacancies/`, data);
Server.post(`/students/${this.props.user.data.student.id}/bookmarked-vacancies/`, data);
} else {
Server.delete(`/students/${this.props.studentId}/bookmarked-vacancies/${this.props.data.id}/`);
Server.delete(`/students/${this.props.user.data.student.id}/bookmarked-vacancies/${this.props.data.id}/`);
}
this.state.bookmarked = 1 - this.state.bookmarked;
}
updateStatus = (status = 'registered') => this.setState({ status });
updateStatus = (registeredStatus = 'registered') => this.setState({ registeredStatus });
removeVacancyApplication() {
this.setState({ deleteLoading: true });
Server.delete(`/students/${this.props.studentId}/applied-vacancies/${this.props.data.id}/`).then(() => {
Server.delete(`/students/${this.props.user.data.student.id}/applied-vacancies/${this.props.data.id}/`).then(() => {
this.modalAlert.open('Pendaftaran Berhasil Dibatalkan', 'Pendaftaran anda berhasil dihapus dari sistem\n');
this.setState({ status: 'new', deleteLoading: false });
this.setState({ registeredStatus: 'new', deleteLoading: false });
}, () => {
this.modalAlert.open('Permintaan Gagal', 'Maaf permintaan anda gagal diproses sistem. Harap ulangi pendaftaran atau hubungi administrator\n');
this.setState({ deleteLoading: false });
......@@ -69,16 +81,35 @@ export default class Vacancy extends React.Component {
}
generateAction() {
const applyModal = (<ApplyModal
updateStatus={this.updateStatus}
active={this.state.status === 'new'}
data={{ header: this.props.data.name, description: this.props.data.description, id: this.props.data.id }}
buttonTitle="Detail"
studentId={this.props.studentId}
/>);
const applyModal = (
<ApplyModal
updateStatus={this.updateStatus}
active={this.state.registeredStatus === 'new'}
data={{
header: this.props.data.name,
description: this.props.data.description,
id: this.props.data.id,
}}
resume={this.props.user.data.student.resume}
buttonTitle="Detail"
studentId={this.props.user.data.student.id}
/>
);
const cancelButton = <Button loading={this.state.deleteLoading} floated="right" color="red" onClick={this.openConfirmationModal}>Batal</Button>;
return this.state.status === 'new' ? applyModal : cancelButton;
const rejectedButton =
<Button floated="right" color="red" disabled>{Vacancy.APPLICATION_STATUS_TEXT[this.props.status]}</Button>;
const acceptedButton =
<Button floated="right" color="blue" disabled>{Vacancy.APPLICATION_STATUS_TEXT[this.props.status]}</Button>;
if (this.props.status == null) {
return applyModal;
} else if (this.props.status === Vacancy.APPLICATION_STATUS.REJECTED) {
return rejectedButton;
} else if (this.props.status === Vacancy.APPLICATION_STATUS.ACCEPTED) {
return acceptedButton;
}
return cancelButton;
}
render() {
......@@ -93,7 +124,8 @@ export default class Vacancy extends React.Component {
<Grid.Column floated="left">
<h4>{ this.props.data.name }</h4>
{ this.props.data.company.name }<br />
{ this.props.data.company.address }
{ this.props.data.company.address }<br /><br />
<b>{`Ditutup ${moment(moment(this.props.data.close_time)).fromNow()}`}</b>
</Grid.Column>
<Grid.Column floated="right" >
<Grid.Row textAlign="center">
......@@ -101,7 +133,6 @@ export default class Vacancy extends React.Component {
</Grid.Row>
<Grid.Row>
{ this.generateAction() }
<p>{ this.props.applicationStatus }</p>
</Grid.Row>
</Grid.Column>
</Grid.Row>
......
import React from 'react';
import { Item, Button, Grid, Loader } from 'semantic-ui-react';
import { Item, Button, Grid } from 'semantic-ui-react';
import { Link } from 'react-router';
import Vacancy from './Vacancy';
import CompanyVacancy from './CompanyVacancy';
import Server from '../lib/Server';
import ModalAlert from '../components/ModalAlert';
export default class VacancyList extends React.Component {
static propTypes = {
url: React.PropTypes.string.isRequired,
user: React.PropTypes.object.isRequired,
userId: React.PropTypes.number.isRequired,
type: React.PropTypes.string,
status: React.PropTypes.string,
items: React.PropTypes.array,
};
static defaultProps = {
type: 'student',
status: 'new',
items: [],
};
constructor(props) {
super(props);
/* istanbul ignore next */
this.state = { vacancies: [], bookmarkList: [], appliedList: [], loading: true };
this.updateStatusList = this.updateStatusList.bind(this);
this.state = {
vacancies: this.props.items,
bookmarkList: [],
appliedList: [],
loading: true,
};
this.generateVacancies = this.generateVacancies.bind(this);
this.checkBookmark = this.checkBookmark.bind(this);
this.checkApplied = this.checkApplied.bind(this);
this.companyHeader = this.companyHeader.bind(this);
if (this.props.type === 'student') {
this.updateStatusList();
} else Server.get(this.props.url, false).then((data) => {
this.setState({ vacancies: data, loading: false });
}, e => e.then((error) => {
this.modalAlert.open('Akun anda belum terverfikasi', 'Mohon tunggu sampai akun perusahaan anda terverifikasi atau hubungi pihak administrasi');
this.setState({ loading: false });
}));
}
checkBookmark(id) {
for (let i = 0; i < this.state.bookmarkList.length; i += 1) {
if (id === this.state.bookmarkList[i].id) { return 1; }
}
return 0;
}
checkApplied(id) {
for (let i = 0; i < this.state.appliedList.length; i += 1) {
if (id === this.state.appliedList[i].vacancy.id) { return 'registered'; }
}
return 'new';
}
updateStatusList = () => Server.get(`/students/${this.props.userId}/bookmarked-vacancies/`, false).then((data) => {
this.setState({ bookmarkList: data });
}).then(() => Server.get(`/students/${this.props.userId}/applied-vacancies/`, false).then((data) => {
this.setState({ appliedList: data });
})).then(() => Server.get(this.props.url, false).then((data) => {
this.setState({ vacancies: data, loading: false });
}));
deleteVacancy = id => Server.delete(`/vacancies/${id}/`, this.state).then(() => {
this.modalAlert.open('Hapus Lowongan', 'Lowongan berhasil dihapus');
const newVacancies = [];
......@@ -72,17 +39,16 @@ export default class VacancyList extends React.Component {
}));
generateVacancies() {
if (this.props.type === 'student') {
if (this.props.user.role === 'student') {
return this.state.vacancies.map(vacancy =>
(
<Vacancy
key={this.props.status === 'applied' ? vacancy.vacancy.id : vacancy.id}
status={this.checkApplied(this.props.status === 'applied' ? vacancy.vacancy.id : vacancy.id)}
bookmarked={this.checkBookmark(this.props.status === 'applied' ? vacancy.vacancy.id : vacancy.id)}
data={this.props.status === 'applied' ? vacancy.vacancy : vacancy}
key={vacancy.id}
status={vacancy.status}
user={this.props.user}
bookmarked={vacancy.bookmarked ? 1 : 0}
data={vacancy}
studentId={this.props.userId}
applicationStatus={this.props.status === 'applied' ? (vacancy.status === 4 ? 'Ditolak' :
(vacancy.status === 3 ? 'Diterima' : 'Terdaftar')) : ''}
/>
),
);
......@@ -97,7 +63,7 @@ export default class VacancyList extends React.Component {
}
companyHeader() {
if (this.props.type === 'company') {
if (this.props.user.role === 'company') {
return (
<Grid textAlign="center" style={{ paddingTop: '10px' }}>
<Button size="tiny" as={Link} to="/pelamar" icon="eye" labelPosition="left" color="facebook" content="Lihat Semua Pendaftar" />
......@@ -111,8 +77,6 @@ export default class VacancyList extends React.Component {
render = () => (
<div>
<Loader active={this.state.loading} />
<ModalAlert ref={(modal) => { this.modalAlert = modal; }} />
<Grid container columns="eleven" doubling>
{ this.companyHeader() }
<Item.Group relaxed style={{ width: '100%' }}>
......
......@@ -15,7 +15,7 @@ def is_admin_or_company(user):
if user.is_superuser or user.is_staff:
return True
if not hasattr(user, "company") or user.company != Company.VERIFIED :
if not hasattr(user, "company") or user.company.status != Company.VERIFIED :
raise PermissionDenied("This account is not valid company account or has not been verified")
return True
......
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