diff --git a/assets/js/ProfilePage.jsx b/assets/js/ProfilePage.jsx index fb1fe596bcbc8ef9ec429d7b502f25dac1b9f0d8..b96b2b22ee882d14352e8c540deea71c9a529d44 100644 --- a/assets/js/ProfilePage.jsx +++ b/assets/js/ProfilePage.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { Segment, Image, Header, Icon, Checkbox, Container, Button, Form } from 'semantic-ui-react'; import Server from './lib/Server'; +import Storage from './lib/Storage'; import ModalAlert from './components/ModalAlert'; export default class ProfilePage extends React.Component { @@ -35,12 +36,15 @@ export default class ProfilePage extends React.Component { show_transcript: '', }, bagikanTranskrip: '', + acceptedNo: 0, + refresh: 1, }; this.getProfile = this.getProfile.bind(this); this.handleChange = this.handleChange.bind(this); this.handleCheckbox = this.handleCheckbox.bind(this); this.handleSubmit = this.handleSubmit.bind(this); this.handleFile = this.handleFile.bind(this); + this.gotoLink = this.gotoLink.bind(this); this.getProfile(); } @@ -60,10 +64,17 @@ export default class ProfilePage extends React.Component { phone_number: data.phone_number, photo: data.photo, show_transcript: data.show_transcript, - bagikanTranskrip: (data.show_transcript ? 'Ya' : 'Tidak'), + acceptedNo: data.accepted_no, + bagikanTranskrip: data.show_transcript, + refresh: this.state.refresh + 1, }); + if (this.props.route.own) { + const newSession = this.props.user.data; + newSession.student = data; + Storage.set('user-data', newSession); + window.scrollTo(0, 0); + } }, error => error.then(() => { - // this.modalAlert.open('Gagal Mengambil ', r.error); this.state.name = 'Gagal mendapatkan informasi'; })); } @@ -78,9 +89,9 @@ export default class ProfilePage extends React.Component { } }); Server.submit(`/profiles/students/${this.state.id}/`, submitForm, 'PATCH').then(() => { - this.modalAlert.open('Profil berhasil diperbaharui', 'Silakan periksa kembali profil anda' ); + this.modalAlert.open('Profil berhasil diperbaharui', 'Silakan periksa kembali profil anda', this.getProfile); }, error => error.then((r) => { - this.modalAlert.open('Pembaharuan profil gagal', r.error); + this.modalAlert.open('Pembaharuan profil gagal', r.detail); })); }; @@ -103,6 +114,11 @@ export default class ProfilePage extends React.Component { this.setState({ form, show_transcript: d.checked }); }; + gotoLink = (url) => { + const win = window.open(url); + win.focus(); + }; + updateForm(show) { if (show) { return ( @@ -114,7 +130,7 @@ export default class ProfilePage extends React.Component { </Header.Content> </Header> <ModalAlert ref={(modal) => { this.modalAlert = modal; }} /> - <Form size="small" onSubmit={this.handleSubmit}> + <Form ref={(input) => { this.form = input; }} key={this.state.refresh} size="small" onSubmit={this.handleSubmit}> <Form.Field> <label htmlFor="photo">Profile Picture</label> <input onChange={this.handleFile} placeholder="Profile Photo.jpg" name="photo" type="File" /> @@ -134,7 +150,7 @@ export default class ProfilePage extends React.Component { <Form.Field> <Checkbox onChange={this.handleCheckbox} - checked={ this.state.show_transcript ? true : false } + checked={!!this.state.show_transcript} label="Ijinkan perusahaan tempat saya mendaftar untuk melihat transkrip akademik saya" name="show_transcript" /> @@ -163,21 +179,21 @@ export default class ProfilePage extends React.Component { <h5> { this.state.email } </h5> <h5> { this.state.phone_number } </h5> <h5> { this.state.cityOfBirth}, { this.state.dateOfBirth } </h5> + <p>Sudah diterima di { this.state.acceptedNo } perusahaan</p> </div> <div className="button-profile"> - <a href={this.state.resume ? this.state.resume : '#'} ><Button primary size="small">Resume</Button></a> - { this.state.show_transcript && - <Button primary size="small">Transkrip</Button> - } + <a target="_blank" rel="noopener noreferrer" href={this.state.resume ? this.state.resume : '#'} > + <Button primary size="small">Resume</Button> + </a> </div> <div> - Bagikan Transkrip: <b>{ this.state.bagikanTranskrip }</b> + <br /> + Bagikan Transkrip: <b>{ this.state.bagikanTranskrip ? 'Ya' : 'Tidak'}</b> </div> </Container> </Segment > { this.updateForm(this.props.route.own) } </div> - ); } } diff --git a/assets/js/VacancyPage.jsx b/assets/js/VacancyPage.jsx index 3cb6a92b7fbc9a3d5caddb39f006a89f3b2020ea..1ebe016c51db05bc5c8fc13834a040728f749e49 100644 --- a/assets/js/VacancyPage.jsx +++ b/assets/js/VacancyPage.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Item } from 'semantic-ui-react'; +import { Container, Item } from 'semantic-ui-react';S import Tabs from './components/Tabs'; import Pane from './components/Pane'; import VacancyList from './components/VacancyList'; @@ -82,17 +82,20 @@ export default class VacancyPage extends React.Component { ); } else if (this.props.user.role === 'company') { return ( - <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" - /> + <Container className="vacancies"> + <Pagination + key={1} + 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" + /> + </Container> ); } else if (this.props.user.role === 'admin' || this.props.user.role === 'supervisor') { return ( diff --git a/assets/js/__test__/components/VacancyList-test.jsx b/assets/js/__test__/components/VacancyList-test.jsx index 2afad538bdb830ea6503f0f1dbde02a4ed547c38..7cd0a71cbefd5e757cfaa22eeb5b3a26274945a6 100644 --- a/assets/js/__test__/components/VacancyList-test.jsx +++ b/assets/js/__test__/components/VacancyList-test.jsx @@ -370,6 +370,7 @@ describe('VacancyList', () => { it('fails delete vacancy', () => { fetchMock.restore(); fetchMock.delete('*', 404); + fetchMock.get('*', response2); const vacancyList = ReactTestUtils.renderIntoDocument( <VacancyList userId={1} items={newResponse} user={companyUser} deleteCallback={() => {}} />, ); diff --git a/assets/js/components/ApplyModal.jsx b/assets/js/components/ApplyModal.jsx index 6a830a8664e2dfbd252cfd0848491783a940b5d1..256466392b0e1621ececc3a8664dbcb90effe2c5 100644 --- a/assets/js/components/ApplyModal.jsx +++ b/assets/js/components/ApplyModal.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon, Modal, Button, TextArea, Form } from 'semantic-ui-react'; +import { Icon, Modal, Button, TextArea, Form, Message } from 'semantic-ui-react'; import ModalAlert from './../components/ModalAlert'; import Server from './../lib/Server'; @@ -68,7 +68,15 @@ export default class ApplyModal extends React.Component { <div className="coverLetter"> <br /> <div className="linkCV"> - <a href={this.props.resume} target="_blank" rel="noopener noreferrer"> Klik untuk lihat CV terakhirmu</a> + { this.props.resume ? (<a href={this.props.resume} target="_blank" rel="noopener noreferrer"> Klik untuk lihat CV terakhirmu</a>) + : ( + <Message + error + icon="warning sign" + header="CV Tidak Ditemukan" + content="Anda belum mengunggah CV. Harap ubah profil anda terlebih dahulu pada halaman Profil." + />) + } </div> <br /> <div> @@ -82,7 +90,7 @@ export default class ApplyModal extends React.Component { </Modal.Content> <Modal.Actions> - <Button loading={this.state.load} color="blue" disabled={!this.props.active} onClick={this.handleApply}> + <Button loading={this.state.load} color="blue" disabled={!this.props.active || this.props.resume == null} onClick={this.handleApply}> { this.props.active ? 'Daftar' : 'Sudah Terdaftar' } <Icon name="right chevron" /> </Button> </Modal.Actions> diff --git a/assets/js/components/ApproveModal.jsx b/assets/js/components/ApproveModal.jsx index 132083a6b9dcaa27338a0b8c8e95b4af37f310a4..fa04bed2af8c90b015b45b1f7e0fdcf7780b5bea 100644 --- a/assets/js/components/ApproveModal.jsx +++ b/assets/js/components/ApproveModal.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Modal, Button } from 'semantic-ui-react'; +import { Modal, Button, Icon, Segment } from 'semantic-ui-react'; import Server from './../lib/Server'; import ConfirmationModal from './../components/ConfirmationModal'; import Applicant from './../components/Applicant'; @@ -21,6 +21,7 @@ export default class ApproveModal extends React.Component { this.handleOpen = this.handleOpen.bind(this); this.reject = this.reject.bind(this); this.accept = this.accept.bind(this); + this.gotoStudentProfile = this.gotoStudentProfile.bind(this); this.gotoStudentResume = this.gotoStudentResume.bind(this); this.gotoStudentTranscript = this.gotoStudentTranscript.bind(this); } @@ -69,6 +70,8 @@ export default class ApproveModal extends React.Component { gotoStudentTranscript = () => this.gotoLink(`/transcript/${this.props.data.id}`); + gotoStudentProfile = () => this.gotoLink(`/mahasiswa/${this.props.data.student.id}`); + accept = () => { this.modal.open( 'Terima Lamaran?', @@ -89,15 +92,25 @@ export default class ApproveModal extends React.Component { <Modal.Header>Data Lamaran</Modal.Header> <Modal.Content> <h4> Cover Letter </h4> - { this.props.data.cover_letter ? this.props.data.cover_letter : 'Kosong' } - <div style={{ float: 'right', textAlign: 'right' }}> - {this.props.data.student.resume ? <a onClick={this.gotoStudentResume} href="#" >CV Pelamar </a> : ''} - <br /> - {this.props.data.student.show_transcript ? <a onClick={this.gotoStudentTranscript} href="#" >Transkrip Pelamar</a> : ''} - <br /> + <Segment> + <p> + { this.props.data.cover_letter ? this.props.data.cover_letter : 'Kosong' } + </p> + </Segment> + <br /> + <div> + <b> + {this.props.data.student.resume ? <a onClick={this.gotoStudentResume} href="#" >CV Pelamar </a> : 'Pelamar tidak memiliki CV'} + <br /> + {this.props.data.student.show_transcript ? <a onClick={this.gotoStudentTranscript} href="#" >Transkrip Pelamar</a> : 'Pelamar tidak mengijinkan transktip dilihat'} + <br /> + </b> </div> </Modal.Content> <Modal.Actions> + <Button color="facebook" onClick={this.gotoStudentProfile} floated="left" > + <Icon name="user outline" /> Lihat Profil + </Button> <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 /> diff --git a/assets/js/components/CompanyVacancy.jsx b/assets/js/components/CompanyVacancy.jsx index 8ff71d10692b46767974fb15ae97d2c61abdb424..d935ff7f095833e41d76aa6b9319edd30bab50d0 100644 --- a/assets/js/components/CompanyVacancy.jsx +++ b/assets/js/components/CompanyVacancy.jsx @@ -2,6 +2,7 @@ import React from 'react'; import moment from 'moment'; import { Button, Icon, Item, Grid } from 'semantic-ui-react'; import { Link } from 'react-router'; +import Server from '../lib/Server'; const defaultImage = 'http://semantic-ui.com/images/wireframe/image.png'; @@ -15,8 +16,12 @@ export default class CompanyVacancy extends React.Component { constructor(props) { super(props); + /* istanbul ignore next */ moment.locale('id'); - this.state = { deleteLoading: false }; + this.state = { deleteLoading: false, count: 0, countNew: 0 }; + Server.get(`/vacancies/${this.props.data.id}/count/`, false).then((data) => { + this.setState({ count: data.count, countNew: data.count_new }); + }); } getLink = `/buat-lowongan/${this.props.data.id}`; @@ -25,12 +30,13 @@ export default class CompanyVacancy extends React.Component { return ( <Item className="applicantItems"> <Item.Image src={this.props.data.company.logo ? this.props.data.company.logo : defaultImage} size="small" /> - <Item.Content> + <Item.Content verticalAlign="middle" style={{ wordWrap: 'break-word', width: '100%' }} > <Item.Header as="a">{this.props.data.name}</Item.Header> <Grid.Row> <Grid.Column floated="left"> - <h5> 105 Pendaftar </h5> - Ditutup {moment(moment(this.props.data.close_time)).fromNow()} + <p>{ this.state.count } Pendaftar<br/> + { this.state.countNew } Pendaftar Baru<br/><br/> + Ditutup {moment(moment(this.props.data.close_time)).fromNow()}</p> </Grid.Column> <Grid.Column floated="right"> {this.props.data.verified ? diff --git a/core/serializers/accounts.py b/core/serializers/accounts.py index 25440942f2b519fd502ebb6dac6b6eb1d3b84b6e..cd98fdb05f2ce63acfd11491d3bfa9ffe0cb5764 100644 --- a/core/serializers/accounts.py +++ b/core/serializers/accounts.py @@ -2,7 +2,7 @@ from django.contrib.auth.models import User from rest_framework import serializers from core.models.accounts import Supervisor, Company, Student - +from core.models.vacancies import Application class BasicUserSerializer(serializers.HyperlinkedModelSerializer): class Meta: @@ -13,11 +13,17 @@ class BasicUserSerializer(serializers.HyperlinkedModelSerializer): class StudentSerializer(serializers.ModelSerializer): user = BasicUserSerializer() name = serializers.ReadOnlyField() + accepted_no = serializers.SerializerMethodField() class Meta: model = Student fields = ['id', 'name', 'user', 'npm', 'resume', 'phone_number', 'birth_place', 'birth_date', 'major', 'batch', \ - 'show_transcript', 'photo'] + 'show_transcript', 'photo', 'accepted_no'] + + def get_accepted_no(self, obj): + apps = Application.objects.filter(student=obj, status=4) + companies = apps.values('vacancy__company').distinct() + return companies.count() class StudentUpdateSerializer(serializers.ModelSerializer): diff --git a/package.json b/package.json index ccd3cef7247924cd935e1083793866dcf9f9b30e..f103a60db5aa9c6d31fd2c338f98d379ece26df4 100755 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "object-assign": "^4.1.1", "react": "15.4.2", "react-addons-test-utils": "15.4.2", + "react-ckeditor-wrapper": "^1.0.22", "react-datepicker": "^0.44.0", "react-dom": "15.4.2", "react-router": "^3.0.2",