diff --git a/assets/css/custom.css b/assets/css/custom.css index ceebedf59322eb026c1a41d6d355d70022117fbd..b5edc728b041b3dda387a0376bdb2ca475f02d62 100755 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -248,6 +248,37 @@ card .formRegis{ .ui.segment.paginationCompany{ margin-left: 10%; margin-right: 10%; + +.ui.segment.biodata-section{ + background-color: white; +} + +.ui.segment.biodata-section h1{ + color: black; +} + +.ui.segment.biodata-section h3{ + color: black; +} + +.ui.segment.biodata-section h4{ + color: black; +} + +.ui.segment.biodata-section h5{ + color: black; +} + +.ui.segments.biodataBorder{ +border-color: transparent; +} + +.transkrip{ + color: black; +} + +.ui.segment.biodata-section b{ + color: white; } .admin-bar{ @@ -269,4 +300,4 @@ card .formRegis{ margin-left:7%; margin-top:2%; margin-bottom: 0; -} \ No newline at end of file +} diff --git a/assets/js/AdminVacancyPage.jsx b/assets/js/AdminVacancyPage.jsx new file mode 100644 index 0000000000000000000000000000000000000000..55f70ae56522faba1830f97a8dc3f97eea6b5162 --- /dev/null +++ b/assets/js/AdminVacancyPage.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import Tabs from './components/Tabs'; +import Pane from './components/Pane'; +import AdminVacancy from './components/AdminVacancy'; +import { Item } from 'semantic-ui-react'; + + +export default class AdminVacancyPage extends React.Component { + + render() { + return ( + <Tabs selected={0}> + <Pane label="Lowongan Belum Terferivikasi" > + <Item.Group> + <AdminVacancy /> + </Item.Group> + </Pane> + <Pane label=" Semua Lamaran" /> + </Tabs> + ); + } +} diff --git a/assets/js/CompanyPage.jsx b/assets/js/CompanyPage.jsx index d2f467f3ee70a8669088322aa70f551b1670dba0..161fe6e6cd26290d9085ba7d51fa2a89931e0673 100644 --- a/assets/js/CompanyPage.jsx +++ b/assets/js/CompanyPage.jsx @@ -12,6 +12,7 @@ export default class CompanyPage extends React.Component { }; handleClick = () => window.open('/admin/'); + handleVacancy = () => window.open('/lowongan'); render() { return ( @@ -19,6 +20,11 @@ export default class CompanyPage extends React.Component { <div className="button-administrasi"> <Button onClick={this.handleClick} icon="dashboard" labelPosition="left" color="facebook" content="Buka Menu Administrasi" /> </div> + + <div style={{ paddingLeft: '10px', paddingTop : '10px'}}> + <Button onClick={this.handleVacancy} icon="dashboard" labelPosition="left" color="facebook" content="Halaman Verifikasi Lowongan" /> + </div> + <Tabs selected={0}> <Pagination key={1} diff --git a/assets/js/Dashboard.jsx b/assets/js/Dashboard.jsx index ca0dbdecc22c16094c15f02833ee02df3f9bcf63..2b0c69dcc3218f3cf1cafb85faaf9b75e21f1d4a 100755 --- a/assets/js/Dashboard.jsx +++ b/assets/js/Dashboard.jsx @@ -1,21 +1,29 @@ import React from 'react'; import TopMenu from './components/TopMenu'; +import Server from './lib/Server'; import Footer from './components/Footer'; export default class Dashboard extends React.Component { static propTypes = { + user: React.PropTypes.object.isRequired, + route: React.PropTypes.object.isRequired, + params: React.PropTypes.object.isRequired, children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.node), React.PropTypes.node, ]).isRequired, - user: React.PropTypes.object.isRequired, }; + constructor(props) { + super(props); + /* istanbul ignore next */ + } + render = () => ( <div> <TopMenu user={this.props.user} /> - <div className="content"> {this.props.children} </div> - <Footer /> + {this.props.children} </div> ) } + diff --git a/assets/js/ProfilePage.jsx b/assets/js/ProfilePage.jsx index b96b2b22ee882d14352e8c540deea71c9a529d44..c56f441bce8da2e5843046e69c9348fb047b15d5 100644 --- a/assets/js/ProfilePage.jsx +++ b/assets/js/ProfilePage.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Segment, Image, Header, Icon, Checkbox, Container, Button, Form } from 'semantic-ui-react'; +import { Segment, Image, Header, Icon, Checkbox, Container, Button, Form, Grid } from 'semantic-ui-react'; import Server from './lib/Server'; import Storage from './lib/Storage'; import ModalAlert from './components/ModalAlert'; @@ -168,29 +168,79 @@ export default class ProfilePage extends React.Component { const defaultPicture = 'http://semantic-ui.com/images/wireframe/square-image.png'; return ( <div className="profilePage"> - <Segment className="biodata-section"> - <Header as="h2" icon textAlign="center"> - <Image src={this.state.photo ? this.state.photo : defaultPicture} size="small" shape="circular" /> - </Header> - <Container textAlign="center" className="profile-biodata"> - <div className="biodata"> - <h3> { this.state.name } </h3> - <h5> { this.state.major }, { this.state.batch } </h5> - <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 target="_blank" rel="noopener noreferrer" href={this.state.resume ? this.state.resume : '#'} > - <Button primary size="small">Resume</Button> - </a> - </div> - <div> - <br /> - Bagikan Transkrip: <b>{ this.state.bagikanTranskrip ? 'Ya' : 'Tidak'}</b> - </div> - </Container> + <Segment className="biodata-section" > + <Grid> + <Grid.Column width={7}> + <Header as="h2" icon textAlign="center"> + <br /> + <Image src={this.state.photo || defaultPicture} size="medium" /> + </Header> + </Grid.Column> + + <Grid.Column width={6}> + <Container textAlign="left" className="profile-biodata"> + <div className="biodata"> + <Segment basic textAlign="center"> + <h1> { this.state.name } </h1> + </Segment> + + <Segment basic vertical> + <Grid> + <Grid.Column width={2}> + <Icon name="university" size="big" /> + </Grid.Column> + <Grid.Column width={13}> + <h3> { this.state.major }, { this.state.batch } </h3> + </Grid.Column> + </Grid> + </Segment> + + <Segment basic vertical> + + <Grid> + <Grid.Column width={2}> + <Icon name="mail" size="big" /> + </Grid.Column> + <Grid.Column width={13}> + <h3> { this.state.email } </h3> + </Grid.Column> + </Grid> + </Segment> + + <Segment basic vertical> + <Grid> + <Grid.Column width={2}> + <Icon name="phone" size="big" /> + </Grid.Column> + <Grid.Column width={13}> + <h3> { this.state.phone_number }</h3> + </Grid.Column> + </Grid> + </Segment> + + <Segment basic vertical> + <Grid> + <Grid.Column width={2}> + <Icon name="gift" size="big" /> + </Grid.Column> + <Grid.Column width={13}> + <h3> { this.state.cityOfBirth}, { this.state.dateOfBirth } </h3> + </Grid.Column> + </Grid> + </Segment> + </div> + + <Container textAlign="center"> + <div className="button-profile"> + <a href={this.state.resume || '#'} ><Button primary size="small">Resume</Button></a> + </div> + <div> + <h4> Bagikan Transkrip : { this.state.bagikanTranskrip }</h4> + </div> + </Container> + </Container> + </Grid.Column > + </Grid> </Segment > { this.updateForm(this.props.route.own) } </div> diff --git a/assets/js/SupervisorPage.jsx b/assets/js/SupervisorPage.jsx new file mode 100644 index 0000000000000000000000000000000000000000..4d4fe7492a9ab656d18325021b7528e716f6445f --- /dev/null +++ b/assets/js/SupervisorPage.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { Header, Icon, Grid } from 'semantic-ui-react'; +import Pagination from './components/Pagination'; +import Server from './lib/Server'; +import ApplicationList from './components/ApplicationList'; + +const cols = [ + { key: 'StudentName', label: 'Nama' }, + { key: 'StudentID', label: 'NPM' }, + { key: 'Perusahaan', label: 'Perusahaan' }, + { key: 'Posisi', label: 'Posisi' }, + { key: 'Status', label: 'Status' }, +]; + +export default class SupervisorPage extends React.Component { + + constructor(props) { + super(props); + /* istanbul ignore next */ + this.state = { list: [] }; + } + + componentDidMount() { + this.UserList(); + } + + UserList() { + Server.get('/student-applications/', false).then((data) => { + this.setState({ list: data.results }); + }); + } + + render = () => { + return ( + <Grid container columns="eleven" doubling> + <Grid.Row> + <br /> + </Grid.Row> + <Grid.Row> + <Header as="h2"> + <Icon name="list" /> + Daftar Mahasiswa + </Header> + </Grid.Row> + <Grid.Row> + <div id="layout-content" className="layout-content-wrapper"> + <Pagination url={'/student-applications/'} child={<ApplicationList cols={cols} />} /> + </div> + </Grid.Row> + </Grid> + ); + } +} diff --git a/assets/js/VacancyPage.jsx b/assets/js/VacancyPage.jsx index b8f039ee0209ce69d093513ea180ead41a1fb9ca..7efdb932760d68ba97012c966ae0a801b36d2529 100644 --- a/assets/js/VacancyPage.jsx +++ b/assets/js/VacancyPage.jsx @@ -15,8 +15,10 @@ export default class VacancyPage extends React.Component { const role = user.role; if (role === 'student') { return user.data.student.id; - } else if (role === 'company' || role === 'admin') { + } else if (role === 'company' || (role === 'admin' && user.data.company != null)) { return user.data.company.id; + } else if (role === 'supervisor' || (role === 'admin' && user.data.supervisor != null)) { + return user.data.supervisor.id; } return 0; @@ -31,6 +33,7 @@ export default class VacancyPage extends React.Component { }; } + generateVacancies() { if (this.props.user.role === 'student') { return ( @@ -76,7 +79,8 @@ export default class VacancyPage extends React.Component { </Pane> </Tabs> ); - } else if (this.props.user.role === 'company' || this.props.user.role === 'admin') { + } else if ((this.props.user.role === 'admin' && this.props.user.data.company != null) + || this.props.user.role === 'company') { return ( <Segment className="paginationCompany"> <Pagination @@ -93,6 +97,37 @@ export default class VacancyPage extends React.Component { /> </Segment> ); + } else if (this.props.user.role === 'admin' || this.props.user.role === 'supervisor') { + return ( + <Tabs selected={0}> + <Pane label="Lowongan Belum Terverifikasi" > + <Pagination + key={1} + url="/vacancies/?verified=false" + child={ + <VacancyList + user={this.props.user} + key={1} + userId={this.state.id} + /> + } + /> + </Pane> + <Pane label="Lowongan Terverifikasi" > + <Pagination + key={2} + url="/vacancies/?verified=true" + child={ + <VacancyList + user={this.props.user} + key={2} + userId={this.state.id} + /> + } + /> + </Pane> + </Tabs> + ); } return ( diff --git a/assets/js/__test__/ApplicantPage-test.jsx b/assets/js/__test__/ApplicantPage-test.jsx index d8d03185cf80da8327500226d6887327caca49e8..e2c9d00d87be3cda180e83999daf0e2c431e8c74 100644 --- a/assets/js/__test__/ApplicantPage-test.jsx +++ b/assets/js/__test__/ApplicantPage-test.jsx @@ -4,7 +4,6 @@ import fetchMock from 'fetch-mock'; import ApplicantPage from '../ApplicantPage'; describe('ApplicantPage', () => { - fetchMock.get('*', { data: 'value' }); const companyUser = { role: 'company', @@ -34,9 +33,174 @@ describe('ApplicantPage', () => { }, }; + 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: null, + 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', + status: 0, + bookmarked: false, + id: 4, + }, + { + 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', + status: 1, + bookmarked: false, + id: 4, + }, + { + 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: null, + 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', + status: 2, + bookmarked: true, + id: 4, + }, + { + 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: null, + 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', + status: 3, + bookmarked: true, + id: 4, + }, + { + 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: null, + 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', + status: 4, + bookmarked: true, + id: 4, + }, + ]; + it('renders for companies without problem', () => { const applicantPage = ReactTestUtils.renderIntoDocument( <ApplicantPage user={companyUser} />); expect(applicantPage).to.exist; }); + + it('handle dropdown changes without problem', () => { + fetchMock.get('*', newResponse); + const applicantPage = ReactTestUtils.renderIntoDocument( + <ApplicantPage user={companyUser} />); + const dropdown = ReactTestUtils.findRenderedDOMComponentWithTag(applicantPage, 'select'); + ReactTestUtils.Simulate.change(dropdown); + applicantPage.handleChange({}, {value: 'pilih'}); + expect(applicantPage).to.exist; + fetchMock.restore(); + }); + + it('handle dropdown changes without problem', () => { + fetchMock.get('*', 404); + const applicantPage = ReactTestUtils.renderIntoDocument( + <ApplicantPage user={companyUser} />); + const dropdown = ReactTestUtils.findRenderedDOMComponentWithTag(applicantPage, 'select'); + ReactTestUtils.Simulate.change(dropdown); + applicantPage.handleChange({}, {value: 'pilih'}); + expect(applicantPage).to.exist; + fetchMock.restore(); + }); }); diff --git a/assets/js/__test__/CompanyPage-test.jsx b/assets/js/__test__/CompanyPage-test.jsx index 04dfdcaa8c24bab3b7be2c0aa5f5bf13df59c004..1daa1ab8fd34bad0a70dd8377246164700c48756 100644 --- a/assets/js/__test__/CompanyPage-test.jsx +++ b/assets/js/__test__/CompanyPage-test.jsx @@ -43,7 +43,7 @@ describe('CompanyPage', () => { it('click dashboard button problem', () => { const companyPage = ReactTestUtils.renderIntoDocument( <CompanyPage user={adminUser} />); - const dashboardButton = ReactTestUtils.findRenderedDOMComponentWithTag(companyPage, 'Button'); + const dashboardButton = ReactTestUtils.scryRenderedDOMComponentsWithTag(companyPage, 'Button')[0]; ReactTestUtils.Simulate.click(dashboardButton); expect(companyPage).to.exist; expect(dashboardButton).to.exist; diff --git a/assets/js/__test__/Dashboard-test.jsx b/assets/js/__test__/Dashboard-test.jsx index 892fbabf98466949ad103034a99a90d3deff1a90..1e01baf0b8a823f910eb9fb99fefcb0be0218a8b 100644 --- a/assets/js/__test__/Dashboard-test.jsx +++ b/assets/js/__test__/Dashboard-test.jsx @@ -90,7 +90,28 @@ describe('Dashboard', () => { email: '', is_staff: false, company: null, - supervisor: null, + supervisor: { + id: 3, + user: { + url: 'http://localhost:8000/api/users/9/', + username: 'muhammad.reza42', + email: 'muhammad.reza42@ui.ac.id', + is_staff: false, + }, + name: 'Muhammad R.', + created: '2017-03-28T13:33:46.147241Z', + updated: '2017-03-28T13:33:46.148248Z', + npm: 1406543593, + resume: null, + phone_number: null, + bookmarked_vacancies: [ + 3, + ], + applied_vacancies: [ + 3, + 1, + ], + }, student: null, }, }; diff --git a/assets/js/__test__/SupervisorPage-test.jsx b/assets/js/__test__/SupervisorPage-test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..b2c7de8160a05368821c46ab830cb9e8dde803d0 --- /dev/null +++ b/assets/js/__test__/SupervisorPage-test.jsx @@ -0,0 +1,58 @@ +import React from 'react'; +import ReactTestUtils from 'react-addons-test-utils'; +import fetchMock from 'fetch-mock'; +import SupervisorPage from '../SupervisorPage'; + +describe('SupervisorPage', () => { + const data = { + count: 5, + next: null, + previous: null, + results: [ + { + company_name: 'Tutuplapak', + name: 'Joshua Casey Darian Gunawan', + npm: 1406622616, + vacancy_name: 'Software Engineer', + status: 'accepted', + }, + { + company_name: 'Tutuplapak', + name: 'Muhammad Reza Qorib', + npm: 1406543593, + vacancy_name: 'Software Engineer', + status: 'accepted', + }, + { + company_name: 'Tutuplapak', + name: 'Muhammad Reza Qorib', + npm: 1406543593, + vacancy_name: 'Kepala Sekolah', + status: 'read', + }, + { + company_name: 'company1', + name: 'Farhan Farasdak', + npm: 1406572321, + vacancy_name: 'Data Scientist', + status: 'new', + }, + { + company_name: 'company1', + name: 'student2', + npm: 1406527513, + vacancy_name: 'Data Scientist', + status: 'new', + }, + ], + }; + + fetchMock.get('*', data); + + it('renders for admin without problem', () => { + const supervisorPage = ReactTestUtils.renderIntoDocument( + <SupervisorPage />); + expect(supervisorPage).to.exist; + fetchMock.restore(); + }); +}); diff --git a/assets/js/__test__/components/AdminVacancy-test.jsx b/assets/js/__test__/components/AdminVacancy-test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..3ed5a1b0f011ca14c10eddcbc532023bdaa8896a --- /dev/null +++ b/assets/js/__test__/components/AdminVacancy-test.jsx @@ -0,0 +1,98 @@ +/* eslint-disable no-unused-expressions */ +import React from 'react'; +import ReactTestUtils from 'react-addons-test-utils'; +import AdminVacancy from '../../components/AdminVacancy'; +import Storage from '../../lib/Storage'; + +describe('AdminVacancy', () => { + const fetchMock = require('fetch-mock'); + const response = { + close_time: '2019-03-28T05:55:42Z', + company: { + address: 'kebayoran baru', + id: 1, + logo: null, + name: 'tutup lapak', + }, + created: '2017-03-28T07:05:47.128672Z', + description: 'Lorem ipsum dolbh.', + id: 3, + name: 'Software Engineer', + open_time: '2017-03-28T05:55:38Z', + updated: '2017-03-28T07:34:13.122093Z', + verified: true, + }; + + const response2 = { + close_time: '2019-03-28T05:55:42Z', + company: { + address: 'kebayoran baru', + id: 1, + logo: 'pictures', + name: 'tutup lapak', + }, + created: '2017-03-28T07:05:47.128672Z', + description: 'Lorem ipsum dolbh.', + id: 3, + name: 'Software Engineer', + open_time: '2017-03-28T05:55:38Z', + updated: '2017-03-28T07:34:13.122093Z', + verified: false, + }; + + const supervisorUser = { + role: 'supervisor', + data: { + url: 'http://localhost:8001/api/users/8/', + username: 'Tutuplapak', + email: '', + is_staff: false, + student: { + id: 3, + user: { + url: 'http://localhost:8000/api/users/9/', + username: 'muhammad.reza42', + email: 'muhammad.reza42@ui.ac.id', + is_staff: false, + }, + name: 'Muhammad R.', + created: '2017-03-28T13:33:46.147241Z', + updated: '2017-03-28T13:33:46.148248Z', + npm: 1406543593, + resume: null, + phone_number: null, + birth_place: null, + birth_date: null, + major: null, + batch: null, + show_resume: false, + bookmarked_vacancies: [ + 3, + 2, + ], + applied_vacancies: [ + 3, + 1, + ], + }, + }, + }; + + it('renders for verified without problem', () => { + const lowongan = ReactTestUtils.renderIntoDocument( + <AdminVacancy status={4} data={response} />); + expect(lowongan).to.exist; + }); + + it('renders for unverified without problem', () => { + const lowongan = ReactTestUtils.renderIntoDocument( + <AdminVacancy status={3} data={response2} />); + expect(lowongan).to.exist; + }); + + it('generate button without problem', () => { + const lowongan = ReactTestUtils.renderIntoDocument( + <AdminVacancy status={3} data={response2} />); + expect(lowongan.generateButton()).to.exist; + }); +}); diff --git a/assets/js/__test__/components/AdminVerificationModal-test.jsx b/assets/js/__test__/components/AdminVerificationModal-test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..3d5106533f59c3146004674f1051021bca7e09da --- /dev/null +++ b/assets/js/__test__/components/AdminVerificationModal-test.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import ReactTestUtils from 'react-addons-test-utils'; +import AdminVerificationModal from '../../components/AdminVerificationModal'; + +describe('AdminVerificationModal', () => { + it('renders without problem', () => { + const modalAdmin = ReactTestUtils.renderIntoDocument( + <AdminVerificationModal />); + expect(modalAdmin).to.exist; + }); + + it('close without problem', () => { + const modalAdmin = ReactTestUtils.renderIntoDocument( + <AdminVerificationModal />); + modalAdmin.handleClose(); + expect(modalAdmin.state.modalOpen).to.equal(false); + }); + + it('open without problem', () => { + const modalAdmin = ReactTestUtils.renderIntoDocument( + <AdminVerificationModal />); + modalAdmin.handleOpen(); + expect(modalAdmin.state.modalOpen).to.equal(true); +}); + +}); + + diff --git a/assets/js/__test__/components/Footer-test.jsx b/assets/js/__test__/components/Footer-test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..d6cb3ee94dbdccc7e0d7b0e1f53371a1b8faf3da --- /dev/null +++ b/assets/js/__test__/components/Footer-test.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import ReactTestUtils from 'react-addons-test-utils'; +import Footer from '../../components/Footer'; + +describe('Footer', () => { + + it('renders without problem', () => { + const footer = ReactTestUtils.renderIntoDocument( + <Footer params={{ id: 1 }} />); + expect(footer).to.exist; + }); +}); diff --git a/assets/js/__test__/components/TopMenu-test.jsx b/assets/js/__test__/components/TopMenu-test.jsx index 7f841e107bb7c79274fbd1312fbe4d5e1116d399..b33982630be5208446e9211056ebe737580b7a22 100644 --- a/assets/js/__test__/components/TopMenu-test.jsx +++ b/assets/js/__test__/components/TopMenu-test.jsx @@ -119,7 +119,28 @@ describe('TopMenu', () => { email: '', is_staff: false, company: null, - supervisor: null, + supervisor: { + id: 3, + user: { + url: 'http://localhost:8000/api/users/9/', + username: 'muhammad.reza42', + email: 'muhammad.reza42@ui.ac.id', + is_staff: false, + }, + name: 'Muhammad R.', + created: '2017-03-28T13:33:46.147241Z', + updated: '2017-03-28T13:33:46.148248Z', + npm: 1406543593, + resume: null, + phone_number: null, + bookmarked_vacancies: [ + 3, + ], + applied_vacancies: [ + 3, + 1, + ], + }, student: null, }, }; diff --git a/assets/js/__test__/components/VacancyList-test.jsx b/assets/js/__test__/components/VacancyList-test.jsx index a3fdb795594a595e2e3b1bc61d53f06b2fa5360a..0ac2b46c3132983fe241dd3c7567ba7b3949b9e5 100644 --- a/assets/js/__test__/components/VacancyList-test.jsx +++ b/assets/js/__test__/components/VacancyList-test.jsx @@ -34,8 +34,48 @@ describe('VacancyList', () => { }, }; + const supervisorUser = { + role: 'supervisor', + data: { + url: 'http://localhost:8001/api/users/8/', + username: 'Tutuplapak', + email: '', + is_staff: false, + company: null, + supervisor: { + id: 3, + user: { + url: 'http://localhost:8000/api/users/9/', + username: 'muhammad.reza42', + email: 'muhammad.reza42@ui.ac.id', + is_staff: false, + }, + name: 'Muhammad R.', + created: '2017-03-28T13:33:46.147241Z', + updated: '2017-03-28T13:33:46.148248Z', + npm: 1406543593, + resume: null, + phone_number: null, + birth_place: null, + birth_date: null, + major: null, + batch: null, + show_resume: false, + bookmarked_vacancies: [ + 3, + 2, + ], + applied_vacancies: [ + 3, + 1, + ], + }, + student: null, + }, + }; + const studentUser = { - role: 'company', + role: 'student', data: { url: 'http://localhost:8001/api/users/8/', username: 'Tutuplapak', @@ -69,6 +109,8 @@ describe('VacancyList', () => { 1, ], }, + company: null, + supervisor: null, }, }; @@ -215,6 +257,38 @@ describe('VacancyList', () => { }, ]; + const response = [{ + close_time: '2019-03-28T05:55:42Z', + company: { + address: 'kebayoran baru', + id: 1, + logo: null, + name: 'tutup lapak', + }, + created: '2017-03-28T07:05:47.128672Z', + description: 'Lorem ipsum dolbh.', + id: 1, + name: 'Software Engineer', + open_time: '2017-03-28T05:55:38Z', + updated: '2017-03-28T07:34:13.122093Z', + verified: true, + }, { + close_time: '2019-03-28T05:55:42Z', + company: { + address: 'kebayoran baru', + id: 2, + logo: null, + name: 'tutup lapak', + }, + created: '2017-03-28T07:05:47.128672Z', + description: 'Lorem ipsum dolbh.', + id: 2, + name: 'Software Engineer', + open_time: '2017-03-28T05:55:38Z', + updated: '2017-03-28T07:34:13.122093Z', + verified: true, + }]; + const response2 = [{ close_time: '2019-03-28T05:55:42Z', company: { @@ -235,97 +309,60 @@ describe('VacancyList', () => { it('renders without problem', () => { const vacancyList = ReactTestUtils.renderIntoDocument( <VacancyList items={newResponse} userId={1} user={studentUser} />); + vacancyList.generateVacancies(); expect(vacancyList).to.exist; }); it('renders without problem for company', () => { const vacancyList = ReactTestUtils.renderIntoDocument( <VacancyList items={newResponse} userId={1} user={companyUser} />); + vacancyList.generateVacancies(); + vacancyList.state.vacancies = newResponse; + expect(vacancyList.generateVacancies()).to.exist; + }); + + it('renders without problem for supervisor', () => { + const vacancyList = ReactTestUtils.renderIntoDocument( + <VacancyList items={newResponse} userId={3} user={supervisorUser} />); vacancyList.state.vacancies = newResponse; expect(vacancyList.generateVacancies()).to.exist; }); - // it('renders with problem for company', () => { - // const vacancyList = ReactTestUtils.renderIntoDocument( - // <VacancyList type="company" userId={1} url="test" />); - // vacancyList.state.vacancies = response; - // expect(vacancyList.generateVacancies()).to.exist; - // }); - // - // it('update bookmarks without problem', () => { - // const vacancyList = ReactTestUtils.renderIntoDocument( - // <VacancyList userId={1} url="test" />); - // vacancyList.state.appliedList = [{ id: 1 }, { id: 3 }]; - // vacancyList.updateStatusList().then(() => { - // expect(JSON.stringify(vacancyList.state.bookmarkList)).to.be.defined; - // }); - // }); - // - // it('check applied vacancies without problem', () => { - // const vacancyList = ReactTestUtils.renderIntoDocument( - // <VacancyList userId={1} url="test" />); - // vacancyList.updateStatusList().then(() => { - // expect(JSON.stringify(vacancyList.state.bookmarkList)).to.be.defined; - // }); - // }); - // - // it('renders marked bookmarked vacancies without problem', (done) => { - // const vacancyList = ReactTestUtils.renderIntoDocument( - // <VacancyList userId={1} url="test" />); - // vacancyList.state.vacancies = response; - // vacancyList.state.bookmarkList = [{ id: 5 }, { id: 3 }, { id: 1 }]; - // vacancyList.updateStatusList().then(() => { - // expect(vacancyList.generateVacancies()).to.exist; - // done(); - // }, () => done()); - // }); - // - // it('renders not marked vacancies without problem', () => { - // fetchMock.get('*', response); - // const vacancyList = ReactTestUtils.renderIntoDocument( - // <VacancyList userId={1} url="test" />); - // vacancyList.state.vacancies = response; - // vacancyList.state.bookmarkList = [{ id: 6 }, { id: 4 }, { id: 2 }]; - // expect(vacancyList.generateVacancies()).to.exist; - // fetchMock.restore(); - // }); - // - // it('success calling API', () => { - // fetchMock.get('*', response); - // const vacancyList = ReactTestUtils.renderIntoDocument( - // <VacancyList userId={1} url="test" />); - // vacancyList.state.vacancies = response; - // expect(JSON.stringify(vacancyList.state.vacancies)).to.equal(JSON.stringify(response)); - // fetchMock.restore(); - // }); - // + it('update status without problem', () => { + const vacancyList = ReactTestUtils.renderIntoDocument( + <VacancyList items={newResponse} userId={3} user={supervisorUser} />); + vacancyList.state.vacancies = newResponse; + vacancyList.updateStatus(4, 1); + }); + it('success delete vacancy', () => { fetchMock.restore(); fetchMock.delete('*', response2); + fetchMock.get('*', newResponse); const vacancyList = ReactTestUtils.renderIntoDocument( - <VacancyList userId={1} url="test" deleteCallback={() => {}} user={companyUser} />); + <VacancyList items={newResponse} userId={1} deleteCallback={() => {}} user={companyUser} />); vacancyList.state.vacancies = newResponse; vacancyList.deleteVacancy(1).then(() => { - expect(JSON.stringify(vacancyList.state.vacancies)).to.equal(JSON.stringify(response)); + expect(JSON.stringify(vacancyList.state.vacancies)).to.equal(JSON.stringify(newResponse)); fetchMock.restore(); }, () => { fetchMock.restore(); }); }); - it('fails delete vacancy', () => { + it('fails delete vacancy', (done) => { fetchMock.restore(); fetchMock.delete('*', 404); fetchMock.get('*', response2); const vacancyList = ReactTestUtils.renderIntoDocument( <VacancyList userId={1} items={newResponse} user={companyUser} deleteCallback={() => {}} />, ); - vacancyList.state.vacancies = newResponse; + vacancyList.state.vacancies = response; vacancyList.deleteVacancy(1).then(() => { fetchMock.restore(); done(); }, () => { - expect(JSON.stringify(vacancyList.state.vacancies)).to.equal(JSON.stringify(newResponse)); + expect(JSON.stringify(vacancyList.state.vacancies)).to.equal(JSON.stringify(response)); fetchMock.restore(); done(); }); diff --git a/assets/js/__test__/components/VerifyAdminModal-test.jsx b/assets/js/__test__/components/VerifyAdminModal-test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..84523ac190ce9df8fdfedfd23491284dd2e3804b --- /dev/null +++ b/assets/js/__test__/components/VerifyAdminModal-test.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import ReactTestUtils from 'react-addons-test-utils'; +import VerifyAdminModal from '../../components/VerifyAdminModal'; +import fetchMock from 'fetch-mock'; +import Storage from '../../lib/Storage'; + +describe('VerifyAdminModal', () => { + + it('renders without problem', () => { + const verifyModal = ReactTestUtils.renderIntoDocument( + <VerifyAdminModal />, + ); + expect(verifyModal).to.exist; + }); + + it('open without problem', () => { + const verifyModal = ReactTestUtils.renderIntoDocument( + <VerifyAdminModal id={4} />); + + const modal = ReactTestUtils.findRenderedDOMComponentWithTag(verifyModal, 'Button'); + ReactTestUtils.Simulate.click(modal); + expect(verifyModal.state.modalOpen).to.equal(true); + }); + + it('close without problem', () => { + const verifyModal = ReactTestUtils.renderIntoDocument( + <VerifyAdminModal id={4} />); + + verifyModal.handleClose(); + expect(verifyModal.state.modalOpen).to.equal(false); + }); +}); diff --git a/assets/js/components/AdminVacancy.jsx b/assets/js/components/AdminVacancy.jsx new file mode 100644 index 0000000000000000000000000000000000000000..3983a7cda5214da343393a2048edf5a1292ef428 --- /dev/null +++ b/assets/js/components/AdminVacancy.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { Item, Grid, Button } from 'semantic-ui-react'; +import VerifyAdminModal from './VerifyAdminModal'; +import Server from '../lib/Server'; + +export default class AdminVacancy extends React.Component { + static propTypes = { + data: React.PropTypes.object.isRequired, + updateStatus: React.PropTypes.func.isRequired, + }; + + changeVerifiedStatus() { + let data = {}; + if (this.props.data.verified) { + data = { verified: false }; + } else { + data = { verified: true }; + } + Server.patch(`/vacancies/${this.props.data.id}/verify/`, data).then((status) => { + this.props.updateStatus(this.props.data.id, status.status); + }); + } + + generateButton() { + const unverifyButton = <Button floated="right" color="red" onClick={this.changeVerifiedStatus.bind(this)}>Batalkan Verifikasi</Button>; + const verifyButton = <Button floated="right" color="blue" onClick={this.changeVerifiedStatus.bind(this)}>Verifikasi</Button>; + + if (this.props.data.verified) { + return unverifyButton; + } + return verifyButton; + } + + render() { + return ( + <Item className="adminItems"> + <Item.Image src="http://semantic-ui.com/images/wireframe/image.png" size="small" /> + <Item.Content> + <Item.Header as="a">{this.props.data.name}</Item.Header> + <Grid.Row> + <Grid.Column floated="left"> + <h4>{this.props.data.company.name} </h4> + {this.props.data.company.address} + </Grid.Column> + <Grid.Column floated="right"> + {this.generateButton()} + </Grid.Column> + </Grid.Row> + </Item.Content> + </Item> + ); + } +} diff --git a/assets/js/components/AdminVerificationModal.jsx b/assets/js/components/AdminVerificationModal.jsx new file mode 100644 index 0000000000000000000000000000000000000000..37a1c63b281c9063dc3b29951749fa58f23502b2 --- /dev/null +++ b/assets/js/components/AdminVerificationModal.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { Button, Header, Modal, Grid } from 'semantic-ui-react'; + +export default class AdminVerificationModal extends React.Component { + + state = { modalOpen: false } + + handleOpen = () => this.setState({ + modalOpen: true, + }); + + handleClose = () => this.setState({ + modalOpen: false, + }); + + render() { + return ( + + <Modal + trigger={ + <Button color="blue" icon="right chevron" labelPosition="right" floated="right" content="ubah" onClick={this.handleOpen} /> + } + closeIcon="close" + open={this.state.modalOpen} + onClose={this.handleClose} + > + <Modal.Header>Software Engineer</Modal.Header> + <Modal.Content > + <Modal.Description> + <Header>Deskripsi Lowongan</Header> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, + sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco + laboris nisi ut aliquip ex ea commodo consequat. + </Modal.Description> + </Modal.Content> + + <Modal.Actions> + <Grid columns={2} > + <Grid.Column> + <Button color="red" floated="left" onClick={this.handleClose} >Hapus</Button> + </Grid.Column> + <Grid.Column> + <Button color="blue" floated="right" onClick={this.handleClose}>Ubah</Button> + <Button color="green" floated="right" onClick={this.handleClose}>Verifikasi</Button> + </Grid.Column> + </Grid> + </Modal.Actions> + + </Modal> + ); + } +} diff --git a/assets/js/components/ApplicationList.jsx b/assets/js/components/ApplicationList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..bf2decbd0d10eac594f0f4ce91099a1a2c9e1781 --- /dev/null +++ b/assets/js/components/ApplicationList.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Table } from 'semantic-ui-react'; + +export default class ApplicationList extends React.Component { + static propTypes = { + cols: React.PropTypes.any.isRequired, + items: React.PropTypes.any, + }; + + static defaultProps = { + items: [], + }; + + generateHeaders() { + const cols2 = this.props.cols; // [{key, label}] + + // generate our header (th) cell components + return cols2.map(colData => + <Table.HeaderCell singleLine key={colData.key}> {colData.label} </Table.HeaderCell> + ); + } + + generateRows() { + return this.props.items.map(item => + ( + <Table.Row key={`${item.npm}_${item.company}_${item.position}_${item.status}_row`}> + <Table.Cell key={`${item.name}_name`}> {item.name} </Table.Cell> + <Table.Cell key={`${item.name}_npm`}> {item.npm} </Table.Cell> + <Table.Cell key={`${item.name}_company`}> + {item.company_name} + </Table.Cell> + <Table.Cell key={`${item.name}_position`}> + {item.vacancy_name} + </Table.Cell> + <Table.Cell key={`${item.name}_status`}> + {item.status} + </Table.Cell> + </Table.Row> + ) + ); + } + + render() { + return ( + <Table celled padded> + <Table.Header > + <Table.Row> + {this.generateHeaders()} + </Table.Row> + </Table.Header> + <Table.Body>{this.generateRows()}</Table.Body> + </Table> + ); + } +} diff --git a/assets/js/components/TopMenu.jsx b/assets/js/components/TopMenu.jsx index b69ae9e6c4ffcb6a255daaa7589d2844c42081b5..b366217482d7eafe0872c17d16eb88acaa41d073 100644 --- a/assets/js/components/TopMenu.jsx +++ b/assets/js/components/TopMenu.jsx @@ -1,12 +1,33 @@ import React from 'react'; -import { Menu, Image } from 'semantic-ui-react'; +import { Menu, Image, Popup, Button, Card } from 'semantic-ui-react'; import { Link, browserHistory } from 'react-router'; - import Server from '../lib/Server'; import Storage from '../lib/Storage'; +const defaultPicture = 'http://semantic-ui.com/images/avatar/small/elliot.jpg'; + export default class TopMenu extends React.Component { + static getInfo(user) { + const adminRole = { + name: 'admin', + user: { + email: '', + }, + photo: null, + }; + const role = user.role; + if (role === 'student') { + return user.data.student; + } else if (role === 'company') { + return user.data.company; + } else if (role === 'supervisor') { + return user.data.supervisor; + } + + return adminRole; + } + static propTypes = { user: React.PropTypes.object.isRequired, }; @@ -39,6 +60,7 @@ export default class TopMenu extends React.Component { render() { const { activeItem } = this.state; + const data = TopMenu.getInfo(this.props.user); return ( <div> { this.props.user.data.is_staff && this.props.user.data.company && ( @@ -51,8 +73,24 @@ export default class TopMenu extends React.Component { <Image as="a" size="small" src="/assets/img/logo.png" href="/" /> <Menu.Menu position="right"> <Menu.Item as={Link} to="/home" name="home" active={activeItem === 'home'} onClick={this.handleItemClick} /> - <Menu.Item as={Link} to="/profile" name="profil" active={activeItem === 'profil'} onClick={this.handleItemClick} /> - <Menu.Item as={Link} onClick={this.logout} name="logout" /> + <Menu.Item> + <Popup + trigger={<Image + as={Link} to="/profil" src={data.photo || defaultPicture} avatar + onClick={this.handleItemClick} + />} + flowing + hoverable + > + <Card + header={data.name} + description={data.user.email} + /> + <Button as={Link} onClick={this.logout} name="logout" color="blue" size="tiny">Logout</Button> + + </Popup> + + </Menu.Item> </Menu.Menu> </Menu> </div> diff --git a/assets/js/components/VacancyList.jsx b/assets/js/components/VacancyList.jsx index faba34a07c2da4d742c34f0276bec07cdf544ded..9aef483e11dd27f5661b9e8429ca3fec0ffdeecb 100644 --- a/assets/js/components/VacancyList.jsx +++ b/assets/js/components/VacancyList.jsx @@ -1,8 +1,9 @@ import React from 'react'; -import { Item, Button, Grid, Segment } from 'semantic-ui-react'; +import { Item, Button, Grid } from 'semantic-ui-react'; import { Link } from 'react-router'; import Vacancy from './Vacancy'; import CompanyVacancy from './CompanyVacancy'; +import AdminVacancy from './AdminVacancy'; import Server from '../lib/Server'; export default class VacancyList extends React.Component { @@ -17,6 +18,7 @@ export default class VacancyList extends React.Component { items: [], }; + constructor(props) { super(props); /* istanbul ignore next */ @@ -27,6 +29,16 @@ export default class VacancyList extends React.Component { loading: true, }; this.generateVacancies = this.generateVacancies.bind(this); + this.updateStatus = this.updateStatus.bind(this); + } + + updateStatus(id, status) { + const obj = []; + this.state.vacancies.map((vacancy) => { + if (vacancy.id !== id) return obj.push(vacancy); + return null; + }); + this.setState({ vacancies: obj }); } deleteVacancy = id => Server.delete(`/vacancies/${id}/`, this.state).then(() => { @@ -53,9 +65,17 @@ export default class VacancyList extends React.Component { ), ); } + if (this.props.user.role === 'admin' || this.props.user.role === 'supervisor') { + return this.state.vacancies.map(vacancy => ( + <AdminVacancy + key={vacancy.id} + data={vacancy} + updateStatus={this.updateStatus} + />), + ); + } return this.state.vacancies.map(vacancy => ( - <Item.Group key={vacancy.id} relaxed style={{ width: '100%' }}> <CompanyVacancy key={vacancy.id} @@ -63,8 +83,7 @@ export default class VacancyList extends React.Component { deleteCallback={() => this.deleteVacancy(vacancy.id)} /> </Item.Group> - - ), + ), ); } diff --git a/assets/js/components/VerifyAdminModal.jsx b/assets/js/components/VerifyAdminModal.jsx new file mode 100644 index 0000000000000000000000000000000000000000..b5dbccaae44bb770cecf5ae716537e317116acf2 --- /dev/null +++ b/assets/js/components/VerifyAdminModal.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { Button, Header, Modal } from 'semantic-ui-react'; + +export default class VerifyAdminModal extends React.Component { + + state = { modalOpen: false } + + handleOpen = () => this.setState({ + modalOpen: true, + }); + + handleClose = () => this.setState({ + modalOpen: false, + }); + + render() { + return ( + + <Modal + trigger={ + <Button color="blue" icon="right chevron" labelPosition="right" floated="right" content="ubah" onClick={this.handleOpen} /> + } + closeIcon="close" + open={this.state.modalOpen} + onClose={this.handleClose} + > + <Modal.Header>Software Engineer</Modal.Header> + <Modal.Content > + <Modal.Description> + <Header>Deskripsi Lowongan</Header> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, + sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco + laboris nisi ut aliquip ex ea commodo consequat. + </Modal.Description> + </Modal.Content> + + <Modal.Actions> + <Button color="green" floated="right" onClick={this.handleClose}>Verifikasi</Button> + </Modal.Actions> + + </Modal> + ); + } +} diff --git a/assets/js/index.jsx b/assets/js/index.jsx index d75da841c6ab557636720b4c90bd0b2fc27524ab..accbc110ee314f1af10c5bd45d318e45d101648e 100644 --- a/assets/js/index.jsx +++ b/assets/js/index.jsx @@ -5,13 +5,15 @@ import Dashboard from './Dashboard'; import Login from './Login'; import VacancyPage from './VacancyPage'; import ProfilePage from './ProfilePage'; -import CompanyPage from './CompanyPage'; import CompanyProfile from './CompanyProfile'; import CreateVacancy from './CreateVacancy'; import Server from './lib/Server'; import Storage from './lib/Storage'; import ApplicantPage from './ApplicantPage'; import TranscriptPage from './TranscriptPage'; +import AdminVacancyPage from './AdminVacancyPage'; +import CompanyPage from './CompanyPage'; +import SupervisorPage from './SupervisorPage'; export default class App extends React.Component { @@ -71,11 +73,20 @@ export default class App extends React.Component { handleHome= (nextState, replace, cb) => { if (Server.isLoggedIn()) { if (App.getRole() === 'student') { - replace({ pathname: '/lowongan' }); cb(); + replace({ pathname: '/lowongan' }); + cb(); + } else if (App.getRole() === 'company') { + replace({ pathname: '/lowongan' }); + cb(); } else if (App.getRole() === 'company') { - replace({ pathname: '/lowongan' }); cb(); + replace({ pathname: '/lowongan' }); + cb(); } else if (App.getRole() === 'admin') { - replace({ pathname: '/perusahaan' }); cb(); + replace({ pathname: '/perusahaan' }); + cb(); + } else if (App.getRole() === 'supervisor') { + replace({ pathname: '/lowongan' }); + cb(); } } replace({ pathname: '/login' }); cb(); @@ -84,18 +95,20 @@ export default class App extends React.Component { render() { const staff = this.authorization(['admin']); const student = this.authorization(['admin', 'student']); - // const supervisor = this.authorization(['admin', 'supervisor']); + const supervisor = this.authorization(['admin', 'supervisor']); const company = this.authorization(['admin', 'company']); const commonUser = this.authorization(['admin', 'student', 'company']); const grownups = this.authorization(['admin', 'company', 'supervisor']); const facultyMember = this.authorization(['admin', 'student', 'supervisor']); + const all = this.authorization(['admin', 'company', 'supervisor', 'student']); return ( <Router history={browserHistory}> <Route path="/login" component={Login} /> - <Route component={commonUser(Dashboard)} onEnter={this.handleAuth}> + <Route component={all(Dashboard)} onEnter={this.handleAuth}> <Route path="/transcript/:id" component={company(TranscriptPage)} /> <Route path="/lowongan" component={commonUser(VacancyPage)} /> + <Route path="/admin-lowongan" component={AdminVacancyPage} /> <Route path="/buat-lowongan" component={company(CreateVacancy)} /> <Route path="/buat-lowongan/:id" component={company(CreateVacancy)} /> <Route path="/pelamar" component={company(ApplicantPage)} /> @@ -103,9 +116,11 @@ export default class App extends React.Component { <Route path="/mahasiswa/:id" component={grownups(ProfilePage)} /> <Route path="/perusahaan/:id" component={facultyMember(CompanyProfile)} /> <Route path="/perusahaan" component={staff(CompanyPage)} /> + <Route path="/rekap" component={supervisor(SupervisorPage)} /> <Route path="/transkrip/:id" component={facultyMember(CompanyProfile)} /> </Route> <Route path="/home" onEnter={this.handleHome} /> + <Route path="/admin-vacancy" component={VacancyPage} /> <Redirect from="*" to="/home" /> </Router> ); diff --git a/core/lib/permissions.py b/core/lib/permissions.py index 78091c84eef10e28d0119a2b0868bf810f53805b..0e85f561def05dbec4d981f3c182943fa3104e46 100644 --- a/core/lib/permissions.py +++ b/core/lib/permissions.py @@ -5,6 +5,7 @@ from core.models import Company from core.models import Student from core.models import Supervisor from core.models import Application +from core.models import Vacancy def is_admin_or_student(user): @@ -145,3 +146,14 @@ class IsAdminOrVacancyOwner(permissions.BasePermission): raise PermissionDenied( "Checking owner permission on non-application object" ) + +class AsAdminOrSupervisor(permissions.BasePermission): + def has_permission(self, request, view): + return is_admin_or_supervisor(request.user) + +class VacancyApprovalPermission(permissions.BasePermission): + def has_permission(self, request, view): + return is_admin_or_supervisor(request.user) + + def has_object_permission(self, request, view, obj): + return isinstance(obj, Vacancy) diff --git a/core/serializers/vacancies.py b/core/serializers/vacancies.py index 1b4f15dd69bc02e42d009f32064312a6ddf2a36b..313f2064622806a59994a83eba2ea82f0eb7a4a8 100644 --- a/core/serializers/vacancies.py +++ b/core/serializers/vacancies.py @@ -58,3 +58,33 @@ class ApplicationStatusSerializer(serializers.ModelSerializer): class Meta: model = Application fields = ['status'] + +class SupervisorStudentApplicationSerializer(serializers.ModelSerializer): + def to_representation(self, instance): + status_map = ["new", "read", "bookmarked", "rejected", "accepted" ] + return { + 'name' : instance.student.full_name, + 'npm' : instance.student.npm, + 'vacancy_name' : instance.vacancy.name, + 'company_name' : instance.vacancy.company.name, + 'status' : status_map[instance.status] + } + + class Meta: + model = Application + fields = ['name', 'npm', 'vacancy_name', 'company_name', 'status'] + read_only_fields = ['name', 'npm', 'vacancy_name', 'company_name', 'status'] + +class VacancyApplicationSerializer(serializers.ModelSerializer): + vacancy = VacancySerializer() + + class Meta: + model = Application + fields = ['cover_letter', 'vacancy', 'status'] + + +class VacancyVerifiedSerializer(serializers.ModelSerializer): + + class Meta: + model = Vacancy + fields = ['verified'] diff --git a/core/tests/test_vacancies.py b/core/tests/test_vacancies.py index 19ec9091e38bf23dd401e15c0b38fcebc0e489eb..198a2744a1e15d66a751b124abcde80e636acbe5 100644 --- a/core/tests/test_vacancies.py +++ b/core/tests/test_vacancies.py @@ -5,7 +5,7 @@ from django.contrib.auth.models import User from rest_framework import status from rest_framework.test import APITestCase -from core.models.accounts import Company, Student +from core.models.accounts import Company, Student, Supervisor from core.models.vacancies import Vacancy, Application @@ -365,3 +365,57 @@ class CompanyListsTests(APITestCase): url = '/api/companies/' + str(new_company.pk) + '/applications/' + str(new_vacancy.pk) + '/by_vacancy/?status=5' response = self.client.get(url, format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + +class SupervisorStudentApplicationTests(APITestCase): + + def test_list_student_application(self): + new_user = User.objects.create_user('dummy.supervisor', 'dummy.supervisor@asd.asd', 'lalala123') + new_supervisor = Supervisor.objects.create(user=new_user, nip=1212121212) + self.client.force_authenticate(user=new_user) + + url = '/api/student-applications/' + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_list_student_application_unauthorized(self): + new_user = User.objects.create_user('dummy.supervisor', 'dummy.supervisor@asd.asd', 'lalala123') + self.client.force_authenticate(user=new_user) + + url = '/api/student-applications/' + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + +class SupervisorApprovalTests(APITestCase): + + def test_supervisor_approve_vacancy(self): + new_user = User.objects.create_user('dummy.supervisor', 'dummy.supervisor@asd.asd', 'lalala123') + new_supervisor = Supervisor.objects.create(user=new_user, nip=1212121212) + self.client.force_authenticate(user=new_user) + + new_user2 = User.objects.create_user('dummy.company2', 'dummy.compan2y@company.com', 'lalala123') + new_company2 = Company.objects.create(user=new_user2, description="lalala", status=Company.VERIFIED, logo=None, + address=None) + new_vacancy2 = Vacancy.objects.create(company=new_company2, verified=False, open_time=datetime.fromtimestamp(0), + description="lalala", close_time=datetime.today()) + + url = '/api/vacancies/' + str(new_vacancy2.pk) + '/verify/' + response = self.client.patch(url, {'verified' : True}, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + retrieve_vacancy = Vacancy.objects.get(pk=new_vacancy2.pk) + self.assertEqual(retrieve_vacancy.verified, True) + + def test_unauthorized_approve_vacancy(self): + new_user = User.objects.create_user('dummy.companyz', 'dummy.companyz@company.com', 'lalala123') + new_company = Company.objects.create(user=new_user, description="lalalaz", status=Company.VERIFIED, logo=None, + address=None) + self.client.force_authenticate(user=new_user) + + new_vacancy = Vacancy.objects.create(company=new_company, verified=False, open_time=datetime.fromtimestamp(0), + description="lalala", close_time=datetime.today()) + + url = '/api/vacancies/' + str(new_vacancy.pk) + '/verify/' + response = self.client.patch(url, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(new_vacancy.verified, False) + diff --git a/core/views/vacancies.py b/core/views/vacancies.py index e73663451cbe9f63ad940f41cc86bf30be249826..4de498f24a6e52fcc1e72677a2fc54443b9f864d 100644 --- a/core/views/vacancies.py +++ b/core/views/vacancies.py @@ -8,11 +8,11 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.pagination import PageNumberPagination from core.lib.mixins import MultiSerializerViewSetMixin -from core.lib.permissions import IsAdminOrStudent, IsAdminOrCompany, IsAdminOrVacancyOwner +from core.lib.permissions import IsAdminOrStudent, IsAdminOrCompany, IsAdminOrVacancyOwner, AsAdminOrSupervisor, VacancyApprovalPermission from core.models import Student, Company from core.models.vacancies import Vacancy, Application from core.serializers.vacancies import VacancySerializer, ApplicationSerializer, ApplicationStatusSerializer, \ - PostVacancySerializer + VacancyApplicationSerializer, PostVacancySerializer, VacancyVerifiedSerializer, SupervisorStudentApplicationSerializer class VacancyViewSet(MultiSerializerViewSetMixin, viewsets.ModelViewSet): @@ -34,6 +34,8 @@ class VacancyViewSet(MultiSerializerViewSetMixin, viewsets.ModelViewSet): verified = request.query_params['verified'] if 'verified' in request.query_params else "True" if verified.lower() in ("yes", "true", "t", "1"): vacancies = vacancies.filter(verified=True) + if verified.lower() in {"no", "false", "f", "0"}: + vacancies = vacancies.filter(verified=False) page = self.paginate_queryset(vacancies) if page is not None: return self.get_paginated_response(VacancySerializer(page, many=True, context={'request': request}).data) @@ -46,6 +48,15 @@ class VacancyViewSet(MultiSerializerViewSetMixin, viewsets.ModelViewSet): count_new = Application.objects.filter(vacancy=vacancy, status=Application.NEW).count() return Response({"count": count, "count_new": count_new}, status=status.HTTP_200_OK) + @detail_route(methods=['patch'], permission_classes=[VacancyApprovalPermission], serializer_class=VacancyVerifiedSerializer) + def verify(self, request, pk=None): + vacancy = self.get_object() + serializer = self.get_serializer_class()(vacancy, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + return Response({"error" : "bad request"}, status=status.HTTP_400_BAD_REQUEST) + class ApplicationViewSet(viewsets.GenericViewSet): serializer_class = ApplicationSerializer @@ -238,3 +249,17 @@ class BookmarkedVacancyByStudentViewSet(viewsets.GenericViewSet): student = get_object_or_404(Student.objects.all(), pk=student_id) student.bookmarked_vacancies.remove(vacancy) return Response(self.serializer_class(student.bookmarked_vacancies, many=True, context={'request': request}).data) + + +class SupervisorStudentApplicationViewSet(viewsets.GenericViewSet): + queryset = Student.objects.all() + serializer_class = SupervisorStudentApplicationSerializer + pagination_class = PageNumberPagination + permission_classes = [AsAdminOrSupervisor] + + def list(self, request): + applications = Application.objects.order_by('student') + page = self.paginate_queryset(applications) + if page is not None: + return self.get_paginated_response(self.serializer_class(applications, many=True, context={'request': request}).data) + return Response(self.serializer_class(applications, many=True, context={'request': request}).data) diff --git a/kape/urls.py b/kape/urls.py index da59e3aa14b08d2ed1e43f55f6f1a985d66cd6e4..0a398fecd42e05caa1eb96333c05f1a862875929 100755 --- a/kape/urls.py +++ b/kape/urls.py @@ -25,7 +25,8 @@ from core import apps from core.views.accounts import StudentViewSet, CompanyViewSet, SupervisorViewSet, UserViewSet, LoginViewSet, \ CompanyRegisterViewSet, StudentProfileViewSet from core.views.vacancies import VacancyViewSet, BookmarkedVacancyByStudentViewSet, ApplicationViewSet, \ - CompanyApplicationViewSet, CompanyVacanciesViewSet, CompanyApplicationStatusViewSet + CompanyApplicationViewSet, CompanyVacanciesViewSet, CompanyApplicationStatusViewSet, \ + SupervisorStudentApplicationViewSet schema_view = get_swagger_view() router = routers.DefaultRouter() @@ -38,6 +39,7 @@ router.register(r'register', CompanyRegisterViewSet) router.register(r'vacancies', VacancyViewSet) router.register(r'profiles/students', StudentProfileViewSet) router.register(r'applications', CompanyApplicationStatusViewSet) +router.register(r'student-applications', SupervisorStudentApplicationViewSet) router.register(r'students/(?P<student_id>\d+)/bookmarked-vacancies', BookmarkedVacancyByStudentViewSet, base_name='bookmarked-vacancy-list') router.register(r'students/(?P<student_id>\d+)/applied-vacancies', ApplicationViewSet,