Fakultas Ilmu Komputer UI

Commit 307ae0a3 authored by sirinbaisa's avatar sirinbaisa
Browse files

Merge branch 'Features/CompanyBrowseByVacancy' of...

Merge branch 'Features/CompanyBrowseByVacancy' of https://gitlab.com/PPL2017csui/PPLA1 into Features/CompanyBrowseByVacancy
parents 2f773a13 2190b7f5
......@@ -10,6 +10,8 @@ test:
- export CHROME_BIN=/usr/bin/google-chrome
- curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
- sudo apt-get install -y nodejs
- sudo apt-get install -y build-essential
- npm install npm -g
- npm install
- npm run build-production
- service postgresql start
......
......@@ -3,9 +3,9 @@ import { Segment, Button, Form, Header, Icon, Input } from 'semantic-ui-react';
import { browserHistory } from 'react-router';
import DatePicker from 'react-datepicker';
import moment from 'moment';
import CKEditor from 'react-ckeditor-wrapper';
import ModalAlert from './components/ModalAlert';
import Server from './lib/Server';
import Dumper from './lib/Dumper';
export default class CreateVacancy extends React.Component {
......@@ -19,11 +19,14 @@ export default class CreateVacancy extends React.Component {
/* istanbul ignore next */
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleEditorChange = this.handleEditorChange.bind(this);
this.handleEditor = this.handleEditor.bind(this);
this.setCloseTime = this.setCloseTime.bind(this);
this.setOpenTime = this.setOpenTime.bind(this);
this.state = {
formLoading: false,
loading: !!this.props.params.id,
company: this.props.user.data.company,
vacancyId: this.props.params.id,
open_time: moment(),
......@@ -32,14 +35,17 @@ export default class CreateVacancy extends React.Component {
description: '',
};
this.state.vacancyId && Server.get(`/vacancies/${this.state.vacancyId}/`).then((r) => {
this.setState({
description: r.description,
name: r.name,
open_time: moment(r.open_time),
close_time: moment(r.close_time),
if (this.state.vacancyId) {
Server.get(`/vacancies/${this.state.vacancyId}/`).then((r) => {
this.setState({
description: r.description,
name: r.name,
open_time: moment(r.open_time),
close_time: moment(r.close_time),
loading: false,
});
});
});
}
}
setOpenTime(date) {
......@@ -54,14 +60,29 @@ export default class CreateVacancy extends React.Component {
this.setState({ [e.target.name]: e.target.value });
};
handleEditor(value) {
this.setState({ description: value });
console.log('dor');
}
handleEditorChange = (e) => {
this.setState({ description: e.target.getContent() });
console.log('Content was updated:', this.state.description);
};
handleSubmit = (e) => {
e.preventDefault();
console.log(this.state);
this.setState({ formLoading: true });
const data = Object.assign({}, this.state);
data.open_time = data.open_time.format();
data.close_time = data.close_time.format();
data.company = this.state.company.id;
const data = {};
data.name = this.state.name;
data.description = this.state.description;
data.open_time = this.state.open_time.format();
data.close_time = this.state.close_time.format();
if (!this.state.vacancyId) {
data.company = this.state.company.id;
}
const url = this.state.vacancyId ? `/vacancies/${this.state.vacancyId}/` : '/vacancies/';
const method = this.state.vacancyId ? 'PATCH' : 'POST';
......@@ -74,6 +95,21 @@ export default class CreateVacancy extends React.Component {
}));
};
modules = {
toolbar: [
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[{ list: 'ordered' }, { list: 'bullet' }, { indent: '-1' }, { indent: '+1' }],
['link', 'image'],
['clean'],
],
};
formats = ['header', 'bold', 'italic', 'underline', 'strike', 'blockquote',
'list', 'bullet', 'indent',
'link', 'image',
];
render = () => (
<div className="create-lowongan" >
<ModalAlert ref={(modal) => { this.modalAlert = modal; }} />
......@@ -86,13 +122,23 @@ export default class CreateVacancy extends React.Component {
</Header>
<Form loading={this.state.formLoading} onSubmit={this.handleSubmit}>
<Form.Field label="Posisi" name="name" control={Input} onChange={this.handleChange} value={this.state.name} required />
<Form.TextArea
name="description"
label="Deskripsi"
placeholder="Deskripsi Lowongan..."
onChange={this.handleChange}
value={this.state.description} required
/>
{ !this.state.loading && <CKEditor value={this.state.description} onChange={this.handleEditor} /> }
{/*<TinyMCE*/}
{/*content={this.state.description}*/}
{/*config={{*/}
{/*plugins: 'link image code',*/}
{/*toolbar: 'undo redo | bold italic | alignleft aligncenter alignright | code image',*/}
{/*}}*/}
{/*onChange={this.handleEditorChange}*/}
{/*/>*/}
{/*<Form.TextArea*/}
{/*name="description"*/}
{/*label="Deskripsi"*/}
{/*placeholder="Deskripsi Lowongan..."*/}
{/*onChange={this.handleChange}*/}
{/*value={this.state.description} required*/}
{/*/>*/}
<script>CKEDITOR.replace( 'description' );</script>
<Form.Group widths="equal">
<Form.Field
className="open-time-field"
......
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>
);
}
}
......@@ -48,7 +48,7 @@ export default class VacancyPage extends React.Component {
}
/>
</Pane>
<Pane label="Lamaran saya" >
<Pane label="Lamaran Saya" >
<Pagination
key={2}
url={`/students/${this.state.id}/applied-vacancies/`}
......@@ -80,6 +80,7 @@ export default class VacancyPage extends React.Component {
return (
<Segment className="paginationCompany">
<Pagination
key={1}
url={`/companies/${this.state.id}/vacancies/`}
child={
<VacancyList
......@@ -87,7 +88,7 @@ export default class VacancyPage extends React.Component {
user={this.props.user}
userId={this.state.id}
/>
}
}
error="Anda belum diverifikasi. Harap hubungi admin"
/>
</Segment>
......
......@@ -316,6 +316,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={() => {}} />,
);
......
import React from 'react';
import { Item, Grid } from 'semantic-ui-react';
import { Item, Grid, Container } from 'semantic-ui-react';
import Applicant from './Applicant';
export default class ApplicantList extends React.Component {
......@@ -33,6 +33,17 @@ export default class ApplicantList extends React.Component {
}
generateApplicants() {
if (this.state.applications.length == 0){
return (
<Item className="applicantItems">
<Grid.Row>
<Container textAlign="center">
<p>Tidak ada pelamar<br /></p>
</Container>
</Grid.Row>
</Item>
);
}
return this.state.applications.map(application =>
application.status === this.props.status && (<Applicant
key={application.id} data={application}
......
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';
......@@ -49,43 +49,51 @@ export default class ApplyModal extends React.Component {
);
};
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>
<ModalAlert ref={(modal) => { this.modalAlert = modal; }} />
<Modal.Description>
<Modal.Header> <h3> Deskripsi Lowongan </h3></Modal.Header>
{this.props.data.description}
</Modal.Description>
{this.props.active && (
<div className="coverLetter">
<br />
<div className="linkCV">
<a href={this.props.resume} target="_blank" rel="noopener noreferrer"> Klik untuk lihat CV terakhirmu</a>
</div>
<br />
<div>
<h5>Cover Letter </h5>
<Form >
<TextArea placeholder="Tell us more" size="big" onChange={this.handleChange} />
</Form>
render() {
return (
<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>
<ModalAlert ref={(modal) => { this.modalAlert = modal; }} />
<Modal.Description>
<Modal.Header> <h3> Deskripsi Lowongan </h3></Modal.Header>
{ <div dangerouslySetInnerHTML={{ __html: this.props.data.description }} /> }
</Modal.Description>
{this.props.active && (
<div className="coverLetter">
<br />
<div className="linkCV">
{ 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>
<h5>Cover Letter </h5>
<Form >
<TextArea placeholder="Tell us more" size="big" onChange={this.handleChange} />
</Form>
</div>
</div>
</div>
)}
</Modal.Content>
<Modal.Actions>
<Button loading={this.state.load} color="blue" disabled={!this.props.active} onClick={this.handleApply}>
{ this.props.active ? 'Daftar' : 'Sudah Terdaftar' } <Icon name="right chevron" />
</Button>
</Modal.Actions>
</Modal>
)
)}
</Modal.Content>
<Modal.Actions>
<Button loading={this.state.load} color="blue" disabled={!this.props.active} onClick={this.handleApply}>
{ this.props.active ? 'Daftar' : 'Sudah Terdaftar' } <Icon name="right chevron" />
</Button>
</Modal.Actions>
</Modal>
);
}
}
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 />
......
......@@ -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}`;
......@@ -26,12 +31,13 @@ export default class CompanyVacancy extends React.Component {
<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 ?
......
......@@ -56,13 +56,13 @@ export default class VacancyList extends React.Component {
return this.state.vacancies.map(vacancy => (
<Item.Group relaxed style={{ width: '100%' }}>
<CompanyVacancy
key={vacancy.id}
data={vacancy}
deleteCallback={() => this.deleteVacancy(vacancy.id)}
/>
</Item.Group>
<Item.Group key={vacancy.id} relaxed style={{ width: '100%' }}>
<CompanyVacancy
key={vacancy.id}
data={vacancy}
deleteCallback={() => this.deleteVacancy(vacancy.id)}
/>
</Item.Group>
),
);
......
......@@ -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):
......
......@@ -8,6 +8,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Yuk Cari Tempat Kape :)</title>
<link rel="stylesheet" href="{% static 'css/custom.css' %}"/>
<link rel="stylesheet" href="../../../node_modules/react-quill/dist/quill.snow.css">
<script src="https://cdn.ckeditor.com/4.6.2/standard/ckeditor.js"></script>
<!--<script src="https://cloud.tinymce.com/stable/tinymce.min.js"></script>-->
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.2/semantic.min.css"/>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/react-datepicker/0.44.0/react-datepicker.min.css"/>
<link rel="icon" type="image/png" href="{% static 'img/logo-sm.png'%}" sizes="32x32" />
......
Markdown is supported
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