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'; ...@@ -3,7 +3,6 @@ import Tabs from './components/Tabs';
import Pane from './components/Pane'; import Pane from './components/Pane';
import VacancyList from './components/VacancyList'; import VacancyList from './components/VacancyList';
import Pagination from './components/Pagination'; import Pagination from './components/Pagination';
import { Menu, Icon } from 'semantic-ui-react';
export default class VacancyPage extends React.Component { export default class VacancyPage extends React.Component {
...@@ -37,36 +36,58 @@ export default class VacancyPage extends React.Component { ...@@ -37,36 +36,58 @@ export default class VacancyPage extends React.Component {
<Tabs selected={0}> <Tabs selected={0}>
<Pane label="Semua Lowongan" > <Pane label="Semua Lowongan" >
<Pagination <Pagination
key={1}
url="/vacancies/" url="/vacancies/"
child={ child={
<VacancyList <VacancyList
user={this.props.user}
key={1} key={1}
userId={this.state.id} userId={this.state.id}
url="/vacancies/"
/> />
} }
/> />
</Pane> </Pane>
<Pane label="Lamaran saya" > <Pane label="Lamaran saya" >
<VacancyList <Pagination
key={2} key={2}
userId={this.state.id}
status="applied"
url={`/students/${this.state.id}/applied-vacancies/`} url={`/students/${this.state.id}/applied-vacancies/`}
child={
<VacancyList
user={this.props.user}
key={2}
userId={this.state.id}
/>
}
/> />
</Pane> </Pane>
<Pane label="Lamaran Ditandai" > <Pane label="Lamaran Ditandai" >
<VacancyList <Pagination
key={3} key={3}
userId={this.state.id}
url={`/students/${this.state.id}/bookmarked-vacancies/`} url={`/students/${this.state.id}/bookmarked-vacancies/`}
child={
<VacancyList
key={3}
user={this.props.user}
userId={this.state.id}
/>
}
/> />
</Pane> </Pane>
</Tabs> </Tabs>
); );
} else if (this.props.user.role === 'company' || this.props.user.role === 'admin') { } else if (this.props.user.role === 'company' || this.props.user.role === 'admin') {
return ( 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', () => { ...@@ -54,6 +54,37 @@ describe('VacancyList', () => {
verified: true, 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', () => { it('renders without problem', () => {
fetchMock.get('*', response); fetchMock.get('*', response);
const vacancyList = ReactTestUtils.renderIntoDocument( const vacancyList = ReactTestUtils.renderIntoDocument(
...@@ -152,7 +183,7 @@ describe('VacancyList', () => { ...@@ -152,7 +183,7 @@ describe('VacancyList', () => {
fetchMock.delete('*', 404); fetchMock.delete('*', 404);
fetchMock.get('*', response); fetchMock.get('*', response);
const vacancyList = ReactTestUtils.renderIntoDocument( const vacancyList = ReactTestUtils.renderIntoDocument(
<VacancyList userId={1} url="test" deleteCallback={() => {}} /> <VacancyList userId={1} url="test" deleteCallback={() => {}} />,
); );
vacancyList.state.vacancies = response; vacancyList.state.vacancies = response;
vacancyList.deleteVacancy(1).then(() => { vacancyList.deleteVacancy(1).then(() => {
......
...@@ -8,6 +8,7 @@ export default class ApplyModal extends React.Component { ...@@ -8,6 +8,7 @@ export default class ApplyModal extends React.Component {
data: React.PropTypes.object.isRequired, data: React.PropTypes.object.isRequired,
active: React.PropTypes.bool.isRequired, active: React.PropTypes.bool.isRequired,
buttonTitle: React.PropTypes.string.isRequired, buttonTitle: React.PropTypes.string.isRequired,
resume: React.PropTypes.string.isRequired,
studentId: React.PropTypes.number.isRequired, studentId: React.PropTypes.number.isRequired,
updateStatus: React.PropTypes.func.isRequired, updateStatus: React.PropTypes.func.isRequired,
}; };
...@@ -65,11 +66,13 @@ export default class ApplyModal extends React.Component { ...@@ -65,11 +66,13 @@ export default class ApplyModal extends React.Component {
</Modal.Description> </Modal.Description>
{this.props.active && ( {this.props.active && (
<div className="coverLetter"> <div className="coverLetter">
<br />
<div className="linkCV"> <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> </div>
<br />
<div> <div>
<h5> Write your Cover Letter </h5> <h5>Cover Letter </h5>
<Form > <Form >
<TextArea placeholder="Tell us more" size="big" onChange={this.handleChange} /> <TextArea placeholder="Tell us more" size="big" onChange={this.handleChange} />
</Form> </Form>
......
import React from 'react'; 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 Server from '../lib/Server';
import ModalAlert from '../components/ModalAlert'; import ModalAlert from '../components/ModalAlert';
...@@ -9,6 +9,11 @@ export default class Pagination extends React.Component { ...@@ -9,6 +9,11 @@ export default class Pagination extends React.Component {
static propTypes = { static propTypes = {
url: React.PropTypes.string.isRequired, url: React.PropTypes.string.isRequired,
child: React.PropTypes.node.isRequired, child: React.PropTypes.node.isRequired,
error: React.PropTypes.string,
};
static defaultProps = {
error: 'Gagal Mengambil Data',
}; };
constructor(props) { constructor(props) {
...@@ -20,20 +25,41 @@ export default class Pagination extends React.Component { ...@@ -20,20 +25,41 @@ export default class Pagination extends React.Component {
next: '', next: '',
prev: '', prev: '',
url: this.props.url, 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.handleNext = this.handleNext.bind(this);
this.handlePrev = this.handlePrev.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.refresh = this.refresh.bind(this);
this.content = this.content.bind(this);
this.pageMenu = this.pageMenu.bind(this);
this.getItemsData(); this.getItemsData();
} }
getItemsData = () => Server.get(this.state.url, false).then((data) => { getItemsData = () => Server.get(this.state.url, false).then((data) => {
this.setState({ items: data.items, next: data.next, prev: data.prev }); this.setState({ current: this.state.current + this.state.dir });
}, error => error.then((r) => { this.setState(
this.modalAlert.open('Gagal Mengambil Data', r.error); { 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() { refresh() {
this.forceUpdate(); this.forceUpdate();
...@@ -41,35 +67,53 @@ export default class Pagination extends React.Component { ...@@ -41,35 +67,53 @@ export default class Pagination extends React.Component {
handleMovement(dir) { handleMovement(dir) {
const newUrl = this.state[dir]; const newUrl = this.state[dir];
this.setState({ url: newUrl }); this.setState({ url: newUrl, loading: true }, function () {
this.getItemsData(); this.getItemsData();
});
} }
handlePrev() { 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() { content() {
this.handleMovement('next'); return React.cloneElement(this.props.child, { items: this.state.items });
} }
render = () => ( render = () => (
<div> <div>
<Loader active={this.state.loading} />
<ModalAlert ref={(modal) => { this.modalAlert = modal; }} /> <ModalAlert ref={(modal) => { this.modalAlert = modal; }} />
{this.props.child} {!this.state.loading && this.content()}
<Container textAlign="right"> {!this.state.loading && this.pageMenu()}
<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>
</div> </div>
); );
} }
...@@ -4,21 +4,32 @@ import ApplyModal from './ApplyModal'; ...@@ -4,21 +4,32 @@ import ApplyModal from './ApplyModal';
import Server from '../lib/Server'; import Server from '../lib/Server';
import ConfirmationModal from './ConfirmationModal'; import ConfirmationModal from './ConfirmationModal';
import ModalAlert from './ModalAlert'; import ModalAlert from './ModalAlert';
import moment from 'moment';
const defaultImage = 'http://semantic-ui.com/images/wireframe/image.png'; const defaultImage = 'http://semantic-ui.com/images/wireframe/image.png';
export default class Vacancy extends React.Component { export default class Vacancy extends React.Component {
static propTypes = { static propTypes = {
studentId: React.PropTypes.number.isRequired, user: React.PropTypes.object.isRequired,
// studentId: React.PropTypes.number.isRequired,
data: React.PropTypes.object.isRequired, data: React.PropTypes.object.isRequired,
bookmarked: React.PropTypes.number, bookmarked: React.PropTypes.number,
status: React.PropTypes.string.isRequired, status: React.PropTypes.number,
applicationStatus: React.PropTypes.string,
}; };
static defaultProps = { static defaultProps = {
bookmarked: 0, 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) { constructor(props) {
...@@ -26,9 +37,10 @@ export default class Vacancy extends React.Component { ...@@ -26,9 +37,10 @@ export default class Vacancy extends React.Component {
/* istanbul ignore next */ /* istanbul ignore next */
this.state = { this.state = {
bookmarked: this.props.bookmarked, bookmarked: this.props.bookmarked,
status: this.props.status, registeredStatus: this.props.status ? 'registered' : 'new',
deleteLoading: false, deleteLoading: false,
}; };
moment.locale('id');
this.bookmark = this.bookmark.bind(this); this.bookmark = this.bookmark.bind(this);
this.updateStatus = this.updateStatus.bind(this); this.updateStatus = this.updateStatus.bind(this);
this.generateAction = this.generateAction.bind(this); this.generateAction = this.generateAction.bind(this);
...@@ -39,20 +51,20 @@ export default class Vacancy extends React.Component { ...@@ -39,20 +51,20 @@ export default class Vacancy extends React.Component {
bookmark() { bookmark() {
const data = { vacancy_id: this.props.data.id }; const data = { vacancy_id: this.props.data.id };
if (this.state.bookmarked < 1) { 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 { } 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; this.state.bookmarked = 1 - this.state.bookmarked;
} }
updateStatus = (status = 'registered') => this.setState({ status }); updateStatus = (registeredStatus = 'registered') => this.setState({ registeredStatus });
removeVacancyApplication() { removeVacancyApplication() {
this.setState({ deleteLoading: true }); 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.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.modalAlert.open('Permintaan Gagal', 'Maaf permintaan anda gagal diproses sistem. Harap ulangi pendaftaran atau hubungi administrator\n');
this.setState({ deleteLoading: false }); this.setState({ deleteLoading: false });
...@@ -69,16 +81,35 @@ export default class Vacancy extends React.Component { ...@@ -69,16 +81,35 @@ export default class Vacancy extends React.Component {
} }
generateAction() { generateAction() {
const applyModal = (<ApplyModal const applyModal = (
updateStatus={this.updateStatus} <ApplyModal
active={this.state.status === 'new'} updateStatus={this.updateStatus}
data={{ header: this.props.data.name, description: this.props.data.description, id: this.props.data.id }} active={this.state.registeredStatus === 'new'}
buttonTitle="Detail" data={{
studentId={this.props.studentId} 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>; 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() { render() {
...@@ -93,7 +124,8 @@ export default class Vacancy extends React.Component { ...@@ -93,7 +124,8 @@ export default class Vacancy extends React.Component {
<Grid.Column floated="left"> <Grid.Column floated="left">
<h4>{ this.props.data.name }</h4> <h4>{ this.props.data.name }</h4>
{ this.props.data.company.name }<br /> { 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>
<Grid.Column floated="right" > <Grid.Column floated="right" >
<Grid.Row textAlign="center"> <Grid.Row textAlign="center">
...@@ -101,7 +133,6 @@ export default class Vacancy extends React.Component { ...@@ -101,7 +133,6 @@ export default class Vacancy extends React.Component {
</Grid.Row> </Grid.Row>
<Grid.Row> <Grid.Row>
{ this.generateAction() } { this.generateAction() }
<p>{ this.props.applicationStatus }</p>
</Grid.Row> </Grid.Row>
</Grid.Column> </Grid.Column>
</Grid.Row> </Grid.Row>
......
import React from 'react'; 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 { Link } from 'react-router';
import Vacancy from './Vacancy'; import Vacancy from './Vacancy';
import CompanyVacancy from './CompanyVacancy'; import CompanyVacancy from './CompanyVacancy';
import Server from '../lib/Server'; import Server from '../lib/Server';
import ModalAlert from '../components/ModalAlert';
export default class VacancyList extends React.Component { export default class VacancyList extends React.Component {
static propTypes = { static propTypes = {
url: React.PropTypes.string.isRequired, user: React.PropTypes.object.isRequired,
userId: React.PropTypes.number.isRequired, userId: React.PropTypes.number.isRequired,
type: React.PropTypes.string, items: React.PropTypes.array,
status: React.PropTypes.string,
}; };
static defaultProps = { static defaultProps = {
type: 'student', items: [],
status: 'new',
}; };
constructor(props) { constructor(props) {
super(props);