diff --git a/assets/js/ApplicantPage.jsx b/assets/js/ApplicantPage.jsx index 51334b1d73700944632bff36164b92e0c7e7e650..8737c0065778a57c493aa7d16db6b947f06088f9 100644 --- a/assets/js/ApplicantPage.jsx +++ b/assets/js/ApplicantPage.jsx @@ -2,9 +2,9 @@ import React from 'react'; import Tabs from './components/Tabs'; import Pane from './components/Pane'; import Storage from './lib/Storage'; -import VacancyList from './components/VacancyList'; +import ApplicantList from './components/ApplicantList'; -export default class VacancyPage extends React.Component { +export default class ApplicantPage extends React.Component { constructor(props) { super(props); @@ -13,7 +13,7 @@ export default class VacancyPage extends React.Component { email: '', password: '', errorFlag: false, - vacancies: [], + company: {id:1}, }; this.handleItemClick = this.handleItemClick.bind(this); } @@ -21,18 +21,15 @@ export default class VacancyPage extends React.Component { handleItemClick = (e, { name }) => this.setState({ activeItem: name }); render() { - const student = Storage.get('user-data').student; + const company = Storage.get('user-data').company; return ( <div className="halamanPendaftar"> <Tabs selected={0}> - <Pane label="Lamaran Baru" > - <VacancyList key={1} studentId={student.id} url="/vacancies/" /> + <Pane label="Semua Lamaran" > + <ApplicantList key={1} companyId={company.id} url={`/students/${company.id}/bookmarked-vacancies/`} /> </Pane> <Pane label="Bintangi" > - <VacancyList key={2} status="Batal" studentId={student.id} url={`/students/${student.id}/applied-vacancies/`} /> - </Pane> - <Pane label="Semua Lamaran" > - <VacancyList key={3} studentId={student.id} url={`/students/${student.id}/bookmarked-vacancies/`} /> + <ApplicantList key={2} status="Bookmarked" companyId={company.id} url={`/students/${company.id}/applied-vacancies/`} /> </Pane> </Tabs> diff --git a/assets/js/__test__/components/ApproveModal-test.jsx b/assets/js/__test__/components/ApproveModal-test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..ddf11b11de2dd06dead66c85b48d189e8cd1aa58 --- /dev/null +++ b/assets/js/__test__/components/ApproveModal-test.jsx @@ -0,0 +1,30 @@ +/* eslint-disable no-unused-expressions */ +import React from 'react'; +import ReactTestUtils from 'react-addons-test-utils'; +import ApproveModal from '../../components/ApproveModal'; + +describe('ApproveModal', () => { + it('renders without problem', () => { + const modalApproval = ReactTestUtils.renderIntoDocument( + <ApproveModal id={4} data={{ key: 'value' }} buttonTitle="approve" />); + expect(modalApproval).to.exist; + }); + + it('open without problem', () => { + const modalApproval = ReactTestUtils.renderIntoDocument( + <ApproveModal id={4} data={{ key: 'value' }} buttonTitle="approve" />); + + const modal = ReactTestUtils.findRenderedDOMComponentWithTag(modalApproval, 'Button'); + ReactTestUtils.Simulate.click(modal); + }); + + + it('close without problem', () => { + const modalApproval = ReactTestUtils.renderIntoDocument( + <ApproveModal id={4} data={{ key: 'value' }} buttonTitle="submit" />); + + modalApproval.handleClose(); + expect(modalApproval.state.modalOpen).to.equal(false); + }); + +}); \ No newline at end of file diff --git a/assets/js/components/Applicant.jsx b/assets/js/components/Applicant.jsx new file mode 100644 index 0000000000000000000000000000000000000000..b14359d29da10df4767d9dcd60ed2a4a63b43ac9 --- /dev/null +++ b/assets/js/components/Applicant.jsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { Item, Rating } from 'semantic-ui-react'; +import ApproveModal from './ApproveModal'; +import Storage from '../lib/Storage'; +import Server from '../lib/Server'; +import CancelModal from './CancelModal'; + +const defaultImage = 'http://semantic-ui.com/images/wireframe/image.png'; + +export default class Applicant extends React.Component { + static propTypes = { + data: React.PropTypes.object.isRequired, + bookmarked: React.PropTypes.number, + status: React.PropTypes.string.isRequired, + }; + + static defaultProps = { + bookmarked: 0, + }; + + constructor(props) { + super(props); + /* istanbul ignore next */ + this.state = { bookmarked: this.props.bookmarked }; + this.bookmark = this.bookmark.bind(this); + this.generateAction = this.generateAction.bind(this); + } + + bookmark() { + /* still under construction */ + const companyId = Storage.get('user-data').company.id; + const data = { application_id: this.props.data.id }; + if (this.state.bookmarked < 1) { + Server.post(`/company/${companyId}/bookmarked-applications/`, data); + } else { + Server.delete(`/company/${companyId}/bookmarked-applications/${this.props.data.id}/`); + } + this.state.bookmarked = 1 - this.state.bookmarked; + } + + generateAction() { + const approveModal = (<ApproveModal + id={this.props.data.id} data={{ header: this.props.data.name, + description: this.props.data.description }} buttonTitle="Detail" + />); + return approveModal; + } + + render() { + return ( + <Item > + <Item.Image size="small" src={defaultImage} /> + <Item.Content verticalAlign="middle"> + <Item.Extra> + <h3>{this.props.data.name}</h3> + + <div className="bookmark"> + <Rating icon="star" onRate={this.bookmark} size="massive" defaultRating={this.props.bookmarked ? this.props.bookmarked : 0} maxRating={1} /> + </div> + <h5>{this.props.data.jurusan}</h5> + <h4>{this.props.data.application}</h4> + + { this.generateAction() } + + </Item.Extra> + </Item.Content> + </Item> + ); + } +} diff --git a/assets/js/components/ApplicantList.jsx b/assets/js/components/ApplicantList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..a8a181f98ffd41523d061cd566916ce54a216b89 --- /dev/null +++ b/assets/js/components/ApplicantList.jsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Item } from 'semantic-ui-react'; +import Applicant from './Applicant'; +import Server from '../lib/Server'; + +export default class ApplicantList extends React.Component { + + static propTypes = { + url: React.PropTypes.string.isRequired, + companyId: React.PropTypes.number.isRequired, + status: React.PropTypes.string, + }; + + static defaultProps = { + status: 'Unbookmarked', + }; + + constructor(props) { + super(props); + /* istanbul ignore next */ + /* + Server.get(this.props.url, false).then((data) => { + this.setState({ applications: data }); + }); + */ + this.state = { applications: [{id:1, name:"Farasdak", description:"huehuehuehue", bookmarked:0, jurusan:"Ilmu Komputer", application:"Software Engineer"}], bookmarkList: [] }; + this.generateApplicants = this.generateApplicants.bind(this); + } + + + generateApplicants() { + console.log(this.state.applications) + return this.state.applications.map(application => + <Applicant + key={application.id} + status={this.props.status} + data={application} + />, + ); + } + + render = () => ( + <Item.Group relaxed> + { this.generateApplicants() } + </Item.Group> + ); +} diff --git a/assets/js/components/ApproveModal.jsx b/assets/js/components/ApproveModal.jsx new file mode 100644 index 0000000000000000000000000000000000000000..313d99f3e2c226fd2fa0976f4618432765ad54d9 --- /dev/null +++ b/assets/js/components/ApproveModal.jsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Modal, Button, Icon, TextArea, Form } from 'semantic-ui-react'; +import ModalAlert from './ModalAlert'; + +export default class ApproveModal extends React.Component { + static propTypes = { + data: React.PropTypes.object.isRequired, + id: React.PropTypes.number.isRequired, + buttonTitle: React.PropTypes.string.isRequired, + }; + + constructor(props) { + super(props); + /* istanbul ignore next */ + this.state = { + modalOpen: false, + }; + this.handleOpen = this.handleOpen.bind(this); + } + + + handleOpen() { + this.setState({ modalOpen: true }); + } + + handleClose = () => this.setState({ + modalOpen: false, + }); + + render = () => ( + <Modal + + trigger={<Button primary onClick={this.handleOpen} floated="right">{this.props.buttonTitle}</Button>} + closeIcon="close" + open={this.state.modalOpen} + onClose={this.handleClose} + > + + <Modal.Header>{this.props.data.header}</Modal.Header> + + <Modal.Content> + + <div className="coverLetter"> + + <div className="linkCV"> + <a> Applicant's CV </a> + </div> + + <h4> Cover Letter </h4> + <h5>{this.props.data.description}</h5> + + </div> + + </Modal.Content> + <Modal.Actions> + <ModalAlert + id={this.props.id} + onChangeValue={this.handleClose} + status="Terima" + /> + </Modal.Actions> + </Modal> + ) +} + diff --git a/assets/js/index.jsx b/assets/js/index.jsx index ed5cc5256416beee08f4f8bc7524ca76e185ffc7..afb771dd5f955738033780f394706bd8468212fc 100644 --- a/assets/js/index.jsx +++ b/assets/js/index.jsx @@ -5,6 +5,7 @@ import { Segment } from 'semantic-ui-react'; import Dashboard from './Dashboard'; import Login from './Login'; import VacancyPage from './VacancyPage'; +import ApplicantPage from './ApplicantPage'; import HomeCompany from './HomeCompany'; import Server from './lib/Server'; import Storage from './lib/Storage'; @@ -31,7 +32,13 @@ export default class App extends React.Component { handleHome= (nextState, replace) => { if (Server.isLoggedIn()) { - Storage.get('user-data').student ? replace({pathname: '/lowongan'}) : replace({pathname: '/profile'}); + if(Storage.get('user-data').student){ + replace({pathname: '/lowongan'}); + }else if(Storage.get('user-data').company) + replace({pathname: '/pelamar'}); + else{ + replace({pathname: '/profile'}); + } } else { replace({ pathname: '/login' }); } @@ -45,6 +52,7 @@ export default class App extends React.Component { <Route path="/profile" component={Profile} /> <Route path="/lowongan" component={VacancyPage} /> <Route path="/company" component={HomeCompany} /> + <Route path="/pelamar" component={ApplicantPage} /> <Route path="/users" component={Profile} /> </Route> <Route path="/home" onEnter={this.handleHome} /> diff --git a/core/models/vacancies.py b/core/models/vacancies.py index f165a2b562545ef8feddf717ffda2f1db3063f7f..fd4aed6a955ec0c966ecad44963cde368eed1049 100644 --- a/core/models/vacancies.py +++ b/core/models/vacancies.py @@ -19,6 +19,7 @@ class Application(models.Model): allow_transcript = models.BooleanField(null=False, default=True) student = models.ForeignKey(Student, on_delete=models.CASCADE) vacancy = models.ForeignKey(Vacancy, on_delete=models.CASCADE) + status = models.IntegerField(default=0) class Meta: unique_together = (("student", "vacancy"),)