diff --git a/assets/js/ApplicantPage.jsx b/assets/js/ApplicantPage.jsx index 8737c0065778a57c493aa7d16db6b947f06088f9..318930ac7f0dbc22d757fc0cbba0f43c8c5ea72d 100644 --- a/assets/js/ApplicantPage.jsx +++ b/assets/js/ApplicantPage.jsx @@ -3,9 +3,9 @@ import Tabs from './components/Tabs'; import Pane from './components/Pane'; import Storage from './lib/Storage'; import ApplicantList from './components/ApplicantList'; +import Applicant from './components/Applicant'; export default class ApplicantPage extends React.Component { - constructor(props) { super(props); /* istanbul ignore next */ @@ -13,7 +13,7 @@ export default class ApplicantPage extends React.Component { email: '', password: '', errorFlag: false, - company: {id:1}, + company: { id: 1 }, }; this.handleItemClick = this.handleItemClick.bind(this); } @@ -23,18 +23,23 @@ export default class ApplicantPage extends React.Component { render() { const company = Storage.get('user-data').company; return ( - <div className="halamanPendaftar"> <Tabs selected={0}> - <Pane label="Semua Lamaran" > - <ApplicantList key={1} companyId={company.id} url={`/students/${company.id}/bookmarked-vacancies/`} /> + <Pane label="Lamaran Baru" > + <ApplicantList key={1} companyId={company.id} url={`/companies/${company.id}/applications/?status=${Applicant.APPLICATION_STATUS.NEW}`} status={Applicant.APPLICATION_STATUS.NEW} /> + </Pane> + <Pane label="Lamaran Dibaca" > + <ApplicantList key={2} companyId={company.id} url={`/companies/${company.id}/applications/?status=${Applicant.APPLICATION_STATUS.READ}`} status={Applicant.APPLICATION_STATUS.READ} /> + </Pane> + <Pane label="Lamaran Ditandai" > + <ApplicantList key={3} companyId={company.id} url={`/companies/${company.id}/applications/?status=${Applicant.APPLICATION_STATUS.BOOKMARKED}`} status={Applicant.APPLICATION_STATUS.BOOKMARKED} /> </Pane> - <Pane label="Bintangi" > - <ApplicantList key={2} status="Bookmarked" companyId={company.id} url={`/students/${company.id}/applied-vacancies/`} /> + <Pane label="Lamaran Diterima" > + <ApplicantList key={4} companyId={company.id} url={`/companies/${company.id}/applications/?status=${Applicant.APPLICATION_STATUS.ACCEPTED}`} status={Applicant.APPLICATION_STATUS.ACCEPTED} /> + </Pane> + <Pane label="Lamaran Ditolak" > + <ApplicantList key={5} companyId={company.id} url={`/companies/${company.id}/applications/?status=${Applicant.APPLICATION_STATUS.REJECTED}`} status={Applicant.APPLICATION_STATUS.REJECTED} /> </Pane> </Tabs> - - </div> - ); } } diff --git a/assets/js/components/Applicant.jsx b/assets/js/components/Applicant.jsx index b14359d29da10df4767d9dcd60ed2a4a63b43ac9..ce10b848d6cf29f167a4fa464372e777aece70d0 100644 --- a/assets/js/components/Applicant.jsx +++ b/assets/js/components/Applicant.jsx @@ -1,67 +1,71 @@ import React from 'react'; -import { Item, Rating } from 'semantic-ui-react'; -import ApproveModal from './ApproveModal'; -import Storage from '../lib/Storage'; +import { Item, Rating, Grid } from 'semantic-ui-react'; import Server from '../lib/Server'; -import CancelModal from './CancelModal'; +import ModalAlert from './ModalAlert'; +import ApproveModal from './ApproveModal'; 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, + updateStatus: React.PropTypes.func.isRequired, }; - static defaultProps = { - bookmarked: 0, + static APPLICATION_STATUS = { + NEW: 0, + READ: 1, + BOOKMARKED: 2, + REJECTED: 3, + ACCEPTED: 4, }; 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}/`); + bookmark = () => { + let data = { status: Applicant.APPLICATION_STATUS.BOOKMARKED }; + if (this.props.data.status === Applicant.APPLICATION_STATUS.BOOKMARKED) { + data = { status: Applicant.APPLICATION_STATUS.READ }; } - 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; - } + if (this.props.data.status > 2) { + this.modalAlert.open('Gagal Menandai', 'Lamaran yang sudah ditolak atau diterima tidak bisa ditandai'); + } else { + Server.patch(`/applications/${this.props.data.id}/`, data).then((status) => { + this.props.updateStatus(this.props.data.id, status.status); + }); + } + }; render() { return ( <Item > + <ModalAlert ref={(modal) => { this.modalAlert = modal; }} /> <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() } - + <Grid.Row> + <Grid.Column floated="left"> + <h4> {this.props.data.student.name} </h4> + {this.props.data.vacancy.name} <br /> + {this.props.data.student.major} + </Grid.Column> + <Grid.Column floated="right" textAlign="center"> + <Grid.Row> + <Rating + icon="star" size="massive" + defaultRating={this.props.data.status === Applicant.APPLICATION_STATUS.BOOKMARKED ? 1 : 0} + onRate={this.bookmark} + maxRating={1} + /> + </Grid.Row> + <ApproveModal updateStatus={this.props.updateStatus} data={this.props.data} /> + </Grid.Column> + </Grid.Row> </Item.Extra> </Item.Content> </Item> diff --git a/assets/js/components/ApplicantList.jsx b/assets/js/components/ApplicantList.jsx index a8a181f98ffd41523d061cd566916ce54a216b89..828685c8f5214079eae0cac928b686e89769cb9a 100644 --- a/assets/js/components/ApplicantList.jsx +++ b/assets/js/components/ApplicantList.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Item } from 'semantic-ui-react'; +import { Item, Grid } from 'semantic-ui-react'; import Applicant from './Applicant'; import Server from '../lib/Server'; @@ -7,41 +7,47 @@ export default class ApplicantList extends React.Component { static propTypes = { url: React.PropTypes.string.isRequired, - companyId: React.PropTypes.number.isRequired, - status: React.PropTypes.string, + status: React.PropTypes.number.isRequired, }; - static defaultProps = { - status: 'Unbookmarked', - }; constructor(props) { super(props); /* istanbul ignore next */ - /* + this.state = { applications: [] }; 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); + this.updateStatus = this.updateStatus.bind(this); } + updateStatus(id, status) { + const obj = []; + this.state.applications.map((application) => { + const clonedObj = {}; + Object.assign(clonedObj, application); + if (application.id === id) clonedObj.status = status; + return obj.push(clonedObj); + }); + this.setState({ applications: obj }); + } generateApplicants() { - console.log(this.state.applications) return this.state.applications.map(application => - <Applicant - key={application.id} - status={this.props.status} - data={application} - />, + application.status === this.props.status && (<Applicant + key={application.id} data={application} + updateStatus={this.updateStatus} + />), ); } render = () => ( - <Item.Group relaxed> - { this.generateApplicants() } - </Item.Group> + <Grid container doubling> + <Item.Group relaxed style={{ width: '100%' }}> + { this.generateApplicants() } + </Item.Group> + </Grid> ); } diff --git a/assets/js/components/ApproveModal.jsx b/assets/js/components/ApproveModal.jsx index 313d99f3e2c226fd2fa0976f4618432765ad54d9..db1839cef9694de67a52cadb40c77bdae5033a31 100644 --- a/assets/js/components/ApproveModal.jsx +++ b/assets/js/components/ApproveModal.jsx @@ -1,12 +1,13 @@ import React from 'react'; -import { Modal, Button, Icon, TextArea, Form } from 'semantic-ui-react'; -import ModalAlert from './ModalAlert'; +import { Modal, Button } from 'semantic-ui-react'; +import Server from './../lib/Server'; +import ConfirmationModal from './../components/ConfirmationModal'; +import Applicant from './../components/Applicant'; export default class ApproveModal extends React.Component { static propTypes = { data: React.PropTypes.object.isRequired, - id: React.PropTypes.number.isRequired, - buttonTitle: React.PropTypes.string.isRequired, + updateStatus: React.PropTypes.func.isRequired, }; constructor(props) { @@ -14,50 +15,73 @@ export default class ApproveModal extends React.Component { /* istanbul ignore next */ this.state = { modalOpen: false, + rejectLoading: false, + acceptLoading: false, }; this.handleOpen = this.handleOpen.bind(this); + this.reject = this.reject.bind(this); + this.accept = this.accept.bind(this); } + handleOpen = () => this.setState({ modalOpen: true }); + handleClose = () => this.setState({ modalOpen: false }); - handleOpen() { - this.setState({ modalOpen: true }); - } + rejectApplication = () => { + const data = { status: Applicant.APPLICATION_STATUS.REJECTED }; + this.setState({ rejectLoading: true }); + Server.patch(`/applications/${this.props.data.id}/`, data).then((status) => { + this.props.updateStatus(this.props.data.id, status.status); + }); + }; + + reject = () => { + this.modal.open( + 'Tolak Lamaran?', + 'Apakah anda yakin untuk menolak lamaran ini?', + 'trash', + this.rejectApplication, + ); + }; - handleClose = () => this.setState({ - modalOpen: false, - }); + acceptApplication = () => { + const data = { status: Applicant.APPLICATION_STATUS.ACCEPTED }; + this.setState({ acceptLoading: true }); + Server.patch(`/applications/${this.props.data.id}/`, data).then((status) => { + this.props.updateStatus(this.props.data.id, status.status); + }); + }; + + accept = () => { + this.modal.open( + 'Terima Lamaran?', + 'Apakah anda yakin untuk menerima lamaran ini?', + 'checkmark', + this.acceptApplication, + ); + }; render = () => ( <Modal - - trigger={<Button primary onClick={this.handleOpen} floated="right">{this.props.buttonTitle}</Button>} + trigger={<Button primary onClick={this.handleOpen} floated="right">Detail</Button>} closeIcon="close" open={this.state.modalOpen} onClose={this.handleClose} > - - <Modal.Header>{this.props.data.header}</Modal.Header> - + <ConfirmationModal ref={(modal) => { this.modal = modal; }} /> + <Modal.Header>Data Lamaran</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> - + <h4> Cover Letter </h4> + { this.props.data.cover_letter } + <div style={{ float: 'right' }}> + <a href={this.props.data.resume} >CV Pelamar </a> + </div> </Modal.Content> <Modal.Actions> - <ModalAlert - id={this.props.id} - onChangeValue={this.handleClose} - status="Terima" - /> + <Button.Group> + <Button disabled={this.props.data.status === Applicant.APPLICATION_STATUS.REJECTED} loading={this.state.rejectLoading} color="red" onClick={this.reject}>Tolak Lamaran</Button> + <Button.Or /> + <Button disabled={this.props.data.status === Applicant.APPLICATION_STATUS.ACCEPTED} loading={this.state.acceptLoading} color="green" onClick={this.accept}>Terima Lamaran</Button> + </Button.Group> </Modal.Actions> </Modal> ) diff --git a/assets/js/components/Vacancy.jsx b/assets/js/components/Vacancy.jsx index 80069414adf64f8b7e48199ca05bf4ee409efd77..e299d5b50b806e1cf864fed18b2b604764ddc0d6 100644 --- a/assets/js/components/Vacancy.jsx +++ b/assets/js/components/Vacancy.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Item, Rating, Button } from 'semantic-ui-react'; +import { Item, Rating, Button, Grid } from 'semantic-ui-react'; import ApplyModal from './ApplyModal'; import Server from '../lib/Server'; import ConfirmationModal from './ConfirmationModal'; @@ -92,16 +92,21 @@ export default class Vacancy extends React.Component { <Item.Image size="small" src={this.props.data.company.logo ? this.props.data.company.logo : 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} maxRating={1} /> - </div> - <h4>{ this.props.data.company.name }</h4> - <h5>{ this.props.data.company.address }</h5> - - { this.generateAction() } - + <Grid.Row> + <Grid.Column floated="left"> + <h4>{ this.props.data.name }</h4> + { this.props.data.company.name }<br /> + { this.props.data.company.address } + </Grid.Column> + <Grid.Column floated="right" > + <Grid.Row textAlign="center"> + <Rating icon="star" onRate={this.bookmark} size="massive" defaultRating={this.props.bookmarked} maxRating={1} /> + </Grid.Row> + <Grid.Row> + { this.generateAction() } + </Grid.Row> + </Grid.Column> + </Grid.Row> </Item.Extra> </Item.Content> </Item> diff --git a/assets/js/components/VacancyList.jsx b/assets/js/components/VacancyList.jsx index fdd6237514cfdc07dd25448da9b54f9cdda93265..c5224b9cfafaad95f67ab1ed51e3b9a0e821ad12 100644 --- a/assets/js/components/VacancyList.jsx +++ b/assets/js/components/VacancyList.jsx @@ -105,7 +105,7 @@ export default class VacancyList extends React.Component { } render = () => ( - <Segment> + <div> <ModalAlert ref={(modal) => { this.modalAlert = modal; }} /> <Grid container columns="eleven" doubling> { this.companyHeader() } @@ -113,6 +113,6 @@ export default class VacancyList extends React.Component { { this.generateVacancies() } </Item.Group> </Grid> - </Segment> + </div> ); } diff --git a/core/views/vacancies.py b/core/views/vacancies.py index bd3d2ec9e4f9125b5115c5eae6a4362d1f971356..242e8f086ad4570806d12a952f391b8f34dc54ba 100644 --- a/core/views/vacancies.py +++ b/core/views/vacancies.py @@ -92,8 +92,10 @@ class CompanyApplicationViewSet(viewsets.GenericViewSet): --- """ company = get_object_or_404(Company.objects.all(), pk=company_id) - vacancies = Vacancy.objects.filter(company = company) - applications = Application.objects.filter(vacancy__in = vacancies) + vacancies = Vacancy.objects.filter(company=company) + applications = Application.objects.filter(vacancy__in=vacancies) + if 'status' in request.query_params: + applications = applications.filter(status=request.query_params['status']) return Response(ApplicationSerializer(applications, many=True, context={'request': request}).data)