6 Commits
0.2.0 ... 0.4.0

Author SHA1 Message Date
1f50faaea7 Add ability to preview application 2020-07-12 19:36:12 +01:00
e978a5417b Added ability to delete single application
Also moved User observer code to Application observer
2020-07-12 17:01:33 +01:00
4dc412e53c Added check for constrained models when deleting 2020-07-12 06:39:39 +01:00
bd0664ce0d Add ability to edit Vacancies 2020-07-11 20:34:26 +01:00
4b390ea536 Added full Vacancy description
Also added support for Markdown
2020-07-11 05:34:12 +01:00
035c9399a6 Add "All Applications" page 2020-07-11 02:43:59 +01:00
40 changed files with 1350 additions and 10490 deletions

View File

@@ -119,13 +119,14 @@ class Install extends Command
$this->info('>> Saved configuration settings!');
$this->info('>> Preparing database...');
$this->callSilent('config:cache');
$this->call('migrate');
$this->call('db:seed');
touch($basePath . '/INSTALLED');
$this->call('up');
$this->info('>> All done! Visit ' . $baseURL . ' to start using your brand new installation of Raspberry Teams!');
$this->info('>> All done! Visit ' . $basePath . ' to start using your brand new installation of Raspberry Teams!');
}
else

View File

@@ -16,7 +16,7 @@ class Form extends Model
public function vacancies()
{
return $this->hasMany('vacancies', 'vacancyFormID', 'id');
return $this->hasMany('App\Vacancy', 'vacancyFormID', 'id');
}
public function responses()

View File

@@ -45,10 +45,9 @@ class ApplicationController extends Controller
}
public function showUserApp(Request $request, $applicationID)
{
// TODO: Inject it instead (do this where there is no injection, not just here)
$application = Application::find($applicationID);
$this->authorize('view', $application);
@@ -77,6 +76,12 @@ class ApplicationController extends Controller
public function showAllApps()
{
return view('dashboard.appmanagement.all')
->with('applications', Application::paginate(6));
}
public function showAllPendingApps()
{
@@ -87,9 +92,6 @@ class ApplicationController extends Controller
}
public function showPendingInterview()
{
$this->authorize('viewAny', Application::class);
@@ -280,4 +282,16 @@ class ApplicationController extends Controller
return redirect()->back();
}
public function delete(Request $request, Application $application)
{
$this->authorize('delete', $application);
$application->delete(); // observers will run, cleaning it up
$request->session()->flash('success', 'Application deleted. Comments, appointments and responses have also been deleted.');
return redirect()->back();
}
}

View File

@@ -80,19 +80,34 @@ class FormController extends Controller
$form = Form::find($id);
$this->authorize('delete', $form);
$deletable = true;
// TODO: Check if form is linked to vacancies before allowing deletion
if (!is_null($form))
if (!is_null($form) && !is_null($form->vacancies) && $form->vacancies->count() !== 0 || !is_null($form->responses))
{
$form->delete();
$request->session()->flash('success', 'Form deleted successfully.');
return redirect()->back();
$deletable = false;
}
if ($deletable)
{
$form->delete();
$request->session()->flash('success', 'Form deleted successfully.');
}
else
{
$request->session()->flash('error', 'You cannot delete this form because it\'s tied to one or more applications and ranks, or because it doesn\'t exist.');
}
$request->session()->flash('error', 'The form you\'re trying to delete does not exist.');
return redirect()->back();
}
public function preview(Request $request, Form $form)
{
return view('dashboard.administration.formpreview')
->with('form', json_decode($form->formStructure, true))
->with('title', $form->formName);
}
}

View File

@@ -15,15 +15,12 @@ class HomeController extends Controller
*/
public function index()
{
// TODO: Relationships for Applications, Users and Responses
// Also prevent apps if user already has one in the space of 30d
// Display apps in the relevant menus
$positions = DB::table('vacancies')
->where('vacancyStatus', 'OPEN')
->where('vacancyCount', '!=', 0)
->get();
$positions = Vacancy::where('vacancyStatus', 'OPEN')
->where('vacancyCount', '<>', 0)
->get();
return view('home')
->with('positions', $positions);
}

View File

@@ -3,15 +3,19 @@
namespace App\Http\Controllers;
use App\Http\Requests\VacancyRequest;
use App\Http\Requests\VacancyEditRequest;
use App\Vacancy;
use App\User;
use App\Form;
use App\Notifications\VacancyClosed;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;
class VacancyController extends Controller
{
public function index()
@@ -31,10 +35,16 @@ class VacancyController extends Controller
if (!is_null($form))
{
/* note: since we can't convert HTML back to Markdown, we'll have to do the converting when the user requests a page,
* and leave the database with Markdown only so it can be used and edited everywhere.
* for several vacancies, this would require looping through all of them and replacing MD with HTML, which is obviously not the most clean solution;
* however, the Model can be configured to return MD instead of HTML on that specific field saving us from looping.
*/
Vacancy::create([
'vacancyName' => $request->vacancyName,
'vacancyDescription' => $request->vacancyDescription,
'vacancyFullDescription' => $request->vacancyFullDescription,
'vacancySlug' => Str::slug($request->vacancyName),
'permissionGroupName' => $request->permissionGroup,
'discordRoleID' => $request->discordRole,
@@ -101,4 +111,29 @@ class VacancyController extends Controller
return redirect()->back();
}
public function edit(Request $request, Vacancy $position)
{
$this->authorize('update', $vacancy);
return view('dashboard.administration.editposition')
->with('vacancy', $position);
}
public function update(VacancyEditRequest $request, Vacancy $position)
{
$this->authorize('update', $vacancy);
$position->vacancyFullDescription = $request->vacancyFullDescription;
$position->vacancyDescription = $request->vacancyDescription;
$position->vacancyCount = $request->vacancyCount;
$position->save();
$request->session()->flash('success', 'Vacancy successfully updated.');
return redirect()->back();
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class VacancyEditRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->can('admin.hiring.vacancy.edit');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'vacancyDescription' => 'required|string',
'vacancyFullDescription' => 'nullable|string',
'vacancyCount' => 'required|integer|min:1'
];
}
}

View File

@@ -26,6 +26,7 @@ class VacancyRequest extends FormRequest
return [
'vacancyName' => 'required|string',
'vacancyDescription' => 'required|string',
'vacancyFullDescription' => 'nullable|string',
'permissionGroup' => 'required|string',
'discordRole' => 'required|string',
'vacancyCount' => 'required|integer',

View File

@@ -6,12 +6,13 @@ use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Queue\SerializesModels;
use App\Vacancy;
class VacancyClosed extends Notification implements ShouldQueue
{
use Queueable;
use Queueable, SerializesModels;
protected $vacancy;

View File

@@ -0,0 +1,93 @@
<?php
namespace App\Observers;
use App\Application;
class ApplicationObserver
{
/**
* Handle the application "created" event.
*
* @param \App\Application $application
* @return void
*/
public function created(Application $application)
{
//
}
/**
* Handle the application "updated" event.
*
* @param \App\Application $application
* @return void
*/
public function updated(Application $application)
{
//
}
public function deleting(Application $application)
{
$application->response()->delete();
$votes = $application->votes;
foreach ($votes as $vote)
{
Log::debug('Referential integrity cleanup: Deleting and detaching vote ' . $vote->id);
$vote->application()->detach($application->id);
$vote->delete();
}
if (!is_null($application->appointment))
{
Log::debug('RIC: Deleting appointment!');
$application->appointment()->delete();
}
if (!$application->comments->isEmpty())
{
Log::debug('RIC: Deleting comments!');
foreach($application->comments as $comment)
{
$comment->delete();
}
}
// application can now be deleted
}
/**
* Handle the application "deleted" event.
*
* @param \App\Application $application
* @return void
*/
public function deleted(Application $application)
{
//
}
/**
* Handle the application "restored" event.
*
* @param \App\Application $application
* @return void
*/
public function restored(Application $application)
{
//
}
/**
* Handle the application "force deleted" event.
*
* @param \App\Application $application
* @return void
*/
public function forceDeleted(Application $application)
{
//
}
}

View File

@@ -48,30 +48,7 @@ class UserObserver
Log::debug('RIC: Now trying to delete applications and responses...');
foreach($applications as $application)
{
$application->response()->delete();
$votes = $application->votes;
foreach ($votes as $vote)
{
Log::debug('RIC: Deleting and detaching vote ' . $vote->id);
$vote->application()->detach($application->id);
$vote->delete();
}
if (!is_null($application->appointment))
{
Log::debug('RIC: Deleting appointment!');
$application->appointment()->delete();
}
if (!$application->comments->isEmpty())
{
Log::debug('RIC: Deleting comments!');
foreach($application->comments as $comment)
{
$comment->delete();
}
}
// code moved to Application observer, where it gets rid of attached elements individually
Log::debug('RIC: Deleting application ' . $application->id);
$application->delete();

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Observers;
use App\Vacancy;
class VacancyObserver
{
/**
* Handle the vacancy "created" event.
*
* @param \App\Vacancy $vacancy
* @return void
*/
public function created(Vacancy $vacancy)
{
//
}
/**
* Handle the vacancy "updated" event.
*
* @param \App\Vacancy $vacancy
* @return void
*/
public function updated(Vacancy $vacancy)
{
//
}
/**
* Handle the vacancy "deleted" event.
*
* @param \App\Vacancy $vacancy
* @return void
*/
public function deleted(Vacancy $vacancy)
{
// TODO: Handle deletion of children's data
}
/**
* Handle the vacancy "restored" event.
*
* @param \App\Vacancy $vacancy
* @return void
*/
public function restored(Vacancy $vacancy)
{
//
}
/**
* Handle the vacancy "force deleted" event.
*
* @param \App\Vacancy $vacancy
* @return void
*/
public function forceDeleted(Vacancy $vacancy)
{
//
}
}

View File

@@ -45,4 +45,11 @@ class ApplicationPolicy
{
return $user->hasAnyRole('admin', 'hiringManager');
}
public function delete(User $user, Application $application)
{
return $user->hasRole('admin');
}
}

View File

@@ -53,7 +53,7 @@ class VacancyPolicy
*/
public function update(User $user, Vacancy $vacancy)
{
return $user->hasRole('admin', 'hiringManager');
return $user->hasAnyRole('admin', 'hiringManager');
}
/**

View File

@@ -6,6 +6,9 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use GrahamCampbell\Markdown\Facades\Markdown;
class Vacancy extends Model
{
public $fillable = [
@@ -13,6 +16,7 @@ class Vacancy extends Model
'permissionGroupName',
'vacancyName',
'vacancyDescription',
'vacancyFullDescription',
'discordRoleID',
'vacancyFormID',
'vacancyCount',
@@ -21,6 +25,26 @@ class Vacancy extends Model
];
/**
* Get the HTML variant of the vacancyFullDescription attribute.
*
* @param string $value The original value
* @return string
*/
public function getVacancyFullDescriptionAttribute($value)
{
if (!is_null($value))
{
return Markdown::convertToHTML($value);
}
else
{
return null;
}
}
public function forms()
{
return $this->belongsTo('App\Form', 'vacancyFormID', 'id');

View File

@@ -0,0 +1,33 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Form extends Component
{
public $formFields;
public $disableFields = false;
/**
* Create a new component instance.
*
* @return void
*/
public function __construct($disableFields = false)
{
$this->disableFields = $disableFields;
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\View\View|string
*/
public function render()
{
return view('components.form');
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class NoPermission extends Component
{
public $type;
public $inDashboard;
/**
* Create a new component instance.
*
* @return void
*/
public function __construct($type, $inDashboard = true)
{
$this->type = $type;
$this->inDashboard = $inDashboard;
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\View\View|string
*/
public function render()
{
return view('components.no-permission');
}
}

View File

@@ -15,6 +15,7 @@
"fideloper/proxy": "^4.2",
"fruitcake/laravel-cors": "^1.0",
"geo-sot/laravel-env-editor": "^0.9.9",
"graham-campbell/markdown": "^12.0",
"guzzlehttp/guzzle": "^6.5",
"jeroennoten/laravel-adminlte": "^3.2",
"laravel/framework": "^7.0",

69
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "16e8efbacd91ef3417b21bd2648e1629",
"content-hash": "51429857899e8134bbe6b4fa61145cc3",
"packages": [
{
"name": "almasaeed2010/adminlte",
@@ -1091,6 +1091,73 @@
],
"time": "2020-04-17T23:33:36+00:00"
},
{
"name": "graham-campbell/markdown",
"version": "v12.0.2",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Laravel-Markdown.git",
"reference": "584eb9f24004238b80ee98b6e7be82f0933554dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GrahamCampbell/Laravel-Markdown/zipball/584eb9f24004238b80ee98b6e7be82f0933554dd",
"reference": "584eb9f24004238b80ee98b6e7be82f0933554dd",
"shasum": ""
},
"require": {
"illuminate/contracts": "^6.0|^7.0",
"illuminate/support": "^6.0|^7.0",
"illuminate/view": "^6.0|^7.0",
"league/commonmark": "^1.3",
"php": "^7.2.5"
},
"require-dev": {
"graham-campbell/analyzer": "^3.0",
"graham-campbell/testbench": "^5.4",
"mockery/mockery": "^1.3.1",
"phpunit/phpunit": "^8.5|^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "12.0-dev"
},
"laravel": {
"providers": [
"GrahamCampbell\\Markdown\\MarkdownServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"GrahamCampbell\\Markdown\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "graham@alt-three.com"
}
],
"description": "Markdown Is A CommonMark Wrapper For Laravel",
"keywords": [
"Graham Campbell",
"GrahamCampbell",
"Laravel Markdown",
"Laravel-Markdown",
"common mark",
"commonmark",
"framework",
"laravel",
"markdown"
],
"time": "2020-04-14T16:14:52+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "6.5.4",

View File

@@ -251,6 +251,12 @@ return [
'header' => 'Application Management',
'can' => ['applications.view.all', 'applications.vote']
],
[
'text' => 'All applications',
'url' => 'applications/staff/all',
'icon' => 'fas fa-list-ol',
'can' => 'applications.view.all'
],
[
'text' => 'Outstanding Applications',
'url' => '/applications/staff/outstanding',
@@ -521,6 +527,7 @@ return [
'location' => 'https://cdn.jsdelivr.net/npm/fullcalendar@5.0.1/main.min.css'
]
]
]
],
],
];

View File

@@ -164,7 +164,9 @@ return [
/*
* Package Service Providers...
*/
GrahamCampbell\Markdown\MarkdownServiceProvider::class,
/*
* Application Service Providers...
@@ -230,7 +232,8 @@ return [
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'UUID' => App\Facades\UUID::class,
'IP' => App\Facades\IP::class
'IP' => App\Facades\IP::class,
'Markdown' => GrahamCampbell\Markdown\Facades\Markdown::class,
],

158
config/markdown.php Normal file
View File

@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
/*
* This file is part of Laravel Markdown.
*
* (c) Graham Campbell <graham@alt-three.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
/*
|--------------------------------------------------------------------------
| Enable View Integration
|--------------------------------------------------------------------------
|
| This option specifies if the view integration is enabled so you can write
| markdown views and have them rendered as html. The following extensions
| are currently supported: ".md", ".md.php", and ".md.blade.php". You may
| disable this integration if it is conflicting with another package.
|
| Default: true
|
*/
'views' => true,
/*
|--------------------------------------------------------------------------
| CommonMark Extensions
|--------------------------------------------------------------------------
|
| This option specifies what extensions will be automatically enabled.
| Simply provide your extension class names here.
|
| Default: []
|
*/
'extensions' => [],
/*
|--------------------------------------------------------------------------
| Renderer Configuration
|--------------------------------------------------------------------------
|
| This option specifies an array of options for rendering HTML.
|
| Default: [
| 'block_separator' => "\n",
| 'inner_separator' => "\n",
| 'soft_break' => "\n",
| ]
|
*/
'renderer' => [
'block_separator' => "\n",
'inner_separator' => "\n",
'soft_break' => "\n",
],
/*
|--------------------------------------------------------------------------
| Enable Em Tag Parsing
|--------------------------------------------------------------------------
|
| This option specifies if `<em>` parsing is enabled.
|
| Default: true
|
*/
'enable_em' => true,
/*
|--------------------------------------------------------------------------
| Enable Strong Tag Parsing
|--------------------------------------------------------------------------
|
| This option specifies if `<strong>` parsing is enabled.
|
| Default: true
|
*/
'enable_strong' => true,
/*
|--------------------------------------------------------------------------
| Enable Asterisk Parsing
|--------------------------------------------------------------------------
|
| This option specifies if `*` should be parsed for emphasis.
|
| Default: true
|
*/
'use_asterisk' => true,
/*
|--------------------------------------------------------------------------
| Enable Underscore Parsing
|--------------------------------------------------------------------------
|
| This option specifies if `_` should be parsed for emphasis.
|
| Default: true
|
*/
'use_underscore' => true,
/*
|--------------------------------------------------------------------------
| HTML Input
|--------------------------------------------------------------------------
|
| This option specifies how to handle untrusted HTML input.
|
| Default: 'strip'
|
*/
'html_input' => 'strip',
/*
|--------------------------------------------------------------------------
| Allow Unsafe Links
|--------------------------------------------------------------------------
|
| This option specifies whether to allow risky image URLs and links.
|
| Default: true
|
*/
'allow_unsafe_links' => false,
/*
|--------------------------------------------------------------------------
| Maximum Nesting Level
|--------------------------------------------------------------------------
|
| This option specifies the maximum permitted block nesting level.
|
| Default: INF
|
*/
'max_nesting_level' => INF,
];

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDetailedDescriptionToVacancy extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('vacancies', function (Blueprint $table) {
$table->longText('vacancyFullDescription')->nullable()->after('vacancyDescription');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('vacancies', function (Blueprint $table) {
$table->dropColumn('vacancyFullDescription');
});
}
}

0
install.sh Normal file → Executable file
View File

10395
package-lock.json generated

File diff suppressed because it is too large Load Diff

1
public/img/403.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.1 KiB

1
public/img/preview.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -0,0 +1,32 @@
@foreach($form['fields'] as $fieldName => $field)
@switch ($field['type'])
@case('textarea')
<div class="form-group mt-2 mb-2">
<label for="{{$fieldName}}">{{$field['title']}}</label>
<textarea class="form-control" rows="7" name="{{$fieldName}}" id="{{$fieldName}}" {{ ($disableFields) ? 'disabled' : '' }}>
</textarea>
</div>
@break
@case('textbox')
<div class="form-group mt-2 mb-2">
<label for="{{$fieldName}}">{{$field['title']}}</label>
<input type="text" name="{{$fieldName}}" id="{{$fieldName}}" {{ ($disableFields) ? 'disabled' : '' }} class="form-control">
</div>
@break
@endswitch
@endforeach

View File

@@ -0,0 +1,74 @@
@if ($inDashboard)
<div class="row mb-4">
<div class="col-6 offset-5">
<img src="/img/403.svg" width="150px" alt="Access denied" />
</div>
</div>
<div class="row">
<!-- People find pleasure in different ways. I find it in keeping my mind clear. - Marcus Aurelius -->
<div class="col">
<div class="alert alert-{{$type}}">
<h4><i class="fas fa-user-lock"></i> Access Denied</h2>
<p>
We're sorry, but you do not have permission to access this web page.
</p>
<p>
Please contact your administrator if you believe this was in error.
</p>
</div>
</div>
</div>
@else
@extends('adminlte::page')
@section('title', 'Raspberry Network | Access Denied')
@section('content_header')
<h4>Access Denied - HTTP 403</h4>
@stop
@section('content')
<div class="row mb-4">
<div class="col-6 offset-5">
<img src="/img/403.svg" width="150px" alt="Access denied" />
</div>
</div>
<div class="row">
<div class="col">
<div class="alert alert-{{$type}}">
<h4><i class="fas fa-user-lock"></i> Access Denied</h2>
<p class="text-muted">
@if (isset($slot))
{{ $slot }}
@endif
</p>
<p>
We're sorry, but you do not have permission to access this web page.
</p>
<p>
Please contact your administrator if you believe this was in error.
</p>
</div>
</div>
</div>
@stop
@endif

View File

@@ -0,0 +1,155 @@
@extends('adminlte::page')
@section('title', 'Raspberry Network | Edit Positions')
@section('content_header')
<h4>Administration / Positions / Edit</h4>
@stop
@section('js')
<x-global-errors>
</x-global-errors>
@stop
@section('content')
<div class="row">
<div class="col center">
<h3>Vacancy Editor</h3>
</div>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-clipboard"></i> {{ $vacancy->vacancyName }}</h3>
</div>
<div class="card-body">
<p class="text-muted"><i class="fas fa-question-circle"></i> For consistency purposes, grayed out fields can't be edited.</p>
<form method="POST" id="editPositionForm" action="{{ route('updatePosition', ['position' => $vacancy->id]) }}">
@csrf
@method('PATCH')
<div class="row">
<div class="col">
<label for="vacancyName">Vacancy Name</label>
<input type="text" value="{{ $vacancy->vacancyName }}" class="form-control" disabled />
</div>
<div class="col">
<label for="vacancyDescription">Vacancy description</label>
<input type="vacancyDescription" class="form-control" name="vacancyDescription" value="{{ $vacancy->vacancyDescription }}" />
</div>
</div>
<div class="row">
<div class="col">
<!-- skipping the accessor for obvious reasons -->
<label for="vacanyDetails">Vacancy details</label>
<textarea name="vacancyFullDescription" class="form-control" placeholder="{{ (is_null($vacancy->vacancyFullDescription)) ? 'No details yet. Add some!' : '' }}" rows="20">{{ $vacancy->getAttributes()['vacancyFullDescription'] }}</textarea>
<span class="text-muted"><i class="fab fa-markdown"></i> Markdown supported</span>
</div>
</div>
<div class="row">
<div class="col">
<label for "permissionGroupName">Permission Group</label>
<input type="text" class="form-control" value="{{ $vacancy->permissionGroupName }}" id="permissionGroupName" disabled />
</div>
<div class="col">
<label for "discordRoleID">Discord Role ID</label>
<input type="text" class="form-control" value="{{ $vacancy->discordRoleID }}" id="discordRoleID" disabled />
</div>
</div>
<div class="row">
<div class="col">
<label for "currentForm">Current Form (uneditable)</label>
<input type="text" class="form-control" value="{{ $vacancy->forms->formName }}" id="currentForm" disabled />
<label for "remainingSlots">Remaining slots</label>
<input type="text" class="form-control" value="{{ $vacancy->vacancyCount }}" id="remainingSlots" name="vacancyCount" />
</div>
</div>
</form>
</div>
<div class="card-footer">
<button type="button" class="btn btn-warning" onclick="$('#editPositionForm').submit()"><i class="fas fa-edit"></i> Save Changes</button>
<button type="button" class="btn btn-danger" onclick="window.location.href='{{ route('showPositions') }}'"><i class="fas fa-times"></i> Cancel</button>
@if($vacancy->vacancyStatus == 'OPEN')
<form method="POST" action="{{ route('updatePositionAvailability', ['id' => $vacancy->id, 'status' => 'close']) }}" style="display: inline">
@method('PATCH')
@csrf
<button type="submit" class="ml-4 btn btn-danger"><i class="fas fa-ban"></i> Close Position</button>
</form>
@endif
</div>
</div>
</div>
</div>
@stop

View File

@@ -0,0 +1,79 @@
@extends('adminlte::page')
@section('title', 'Raspberry Network | Application Form Preview')
@section('content_header')
<h4>Administration / Form Builder / Preview</h4>
@stop
@section('js')
<x-global-errors></x-global-errors>
@stop
@section('content')
<div class="row">
<div class="col-6 offset-4">
<img src="/img/preview.svg" width="250px" alt="Form preview illustration" />
</div>
</div>
<div class="row mt-4">
<div class="col">
<div class="alert alert-success">
<h5><i class="fas fa-eye"></i> This is how your form looks like to applicants</h3>
<p>
You may edit it and add more fields later.
</p>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
<h3>{{ $title }}'s form</h2>
</div>
<div class="card-body">
@component('components.form', ['form' => $form, 'disableFields' => true])
@endcomponent
</div>
<div class="card-footer text-center">
<button type="button" class="btn btn-success ml-2" onlick="window.location.href='{{ route('showForms') }}'"><i class="fas fa-chevron-left"></i> Go back</button>
<button type="button" class="btn btn-warning ml-2"><i class="far fa-edit"></i> Edit</button>
</div>
</div>
</div>
</div>
@stop

View File

@@ -59,7 +59,7 @@
<button type="submit" class="btn btn-sm btn-danger mr-2"><i class="fa fa-trash"></i> Delete</button>
</form>
<button type="button" class="btn btn-sm btn-success"><i class="fa fa-eye"></i> Preview</button>
<button type="button" class="btn btn-sm btn-success" onclick="window.location.href='{{ route('previewForm', ['form' => $form->id]) }}'"><i class="fa fa-eye"></i> Preview</button>
</td>
</tr>

View File

@@ -4,7 +4,11 @@
@section('content_header')
<h4>Administration / Open Positions</h4>
@if (Auth::user()->hasAnyRole('admin', 'hiringManager'))
<h4>Administration / Open Positions</h4>
@else
<h4>Application Access Denied</h4>
@endif
@stop
@@ -33,7 +37,8 @@
@stop
@section('content')
@if (Auth::user()->hasAnyRole('admin', 'hiringManager'))
<!-- todo: switch to modal component -->
<div class="modal fade" tabindex="-1" id="newVacancyForm" role="dialog" aria-labelledby="modalFormLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
@@ -55,6 +60,9 @@
<label for="vacancyDescription">Vacancy Description</label>
<input type="text" id="vacancyDescription" name="vacancyDescription" class="form-control">
<label for="vacancyFullDescription">Vacancy Details</label>
<textarea name="vacancyFullDescription" class="form-control" rel="txtTooltip" title="Add things like admission requirements, rank resposibilities and roles, and anything else you feel is necessary" data-toggle="tooltip" data-placement="bottom"></textarea>
<span class="right text-muted"><i class="fab fa-markdown"></i> Markdown supported</span>
<div class="row mt-3">
<div class="col">
@@ -153,9 +161,8 @@
<thead>
<tr>
<th>#</th>
<th>Vacancy Name</th>
<th>Vacancy Description</th>
<th>Name</th>
<th>Description</th>
<th>Discord Role ID</th>
<th>Perm. Group Name</th>
<th>Open Slots</th>
@@ -171,8 +178,6 @@
@foreach($vacancies as $vacancy)
<tr>
<td>{{$vacancy->id}}</td>
<td>{{$vacancy->vacancyName}}</td>
<td>{{substr($vacancy->vacancyDescription, 0, 20)}}...</td>
<td><span class="badge badge-success">{{$vacancy->discordRoleID}}</span></td>
@@ -185,20 +190,23 @@
@endif
<td>{{$vacancy->created_at}}</td>
<td>
<button type="button" class="btn btn-sm btn-warning" onclick="window.location.href='{{ route('editPosition', ['position' => $vacancy->id]) }}'"><i class="fas fa-edit"></i></button>
@if ($vacancy->vacancyStatus == 'OPEN')
<form action="{{route('updatePositionAvailability', ['status' => 'close', 'id' => $vacancy->id])}}" method="POST" id="closePosition">
<form action="{{route('updatePositionAvailability', ['status' => 'close', 'id' => $vacancy->id])}}" method="POST" id="closePosition" style="display: inline">
@csrf
@method('PATCH')
<button type="submit" class="btn btn-sm btn-danger"><i class="fa fa-ban"></i> Close</button>
<button type="submit" class="btn btn-sm btn-danger"><i class="fa fa-ban"></i></button>
</form>
@else
<form action="{{route('updatePositionAvailability', ['status' => 'open', 'id' => $vacancy->id])}}" method="POST" id="openPosition">
<form action="{{route('updatePositionAvailability', ['status' => 'open', 'id' => $vacancy->id])}}" method="POST" id="openPosition" style="display: inline">
@csrf
@method('PATCH')
<button type="submit" class="btn btn-sm btn-success"><i class="fa fa-check"></i> Open</button>
<button type="submit" class="btn btn-sm btn-success"><i class="fa fa-check"></i></button>
</form>
@endif
@@ -232,5 +240,7 @@
</div>
</div>
@else
<x-no-permission type="danger"></x-no-permission>
@endif
@stop

View File

@@ -93,38 +93,10 @@
<form action="{{route('saveApplicationForm', ['vacancySlug' => $vacancy->vacancySlug])}}" method="POST" id="submitApplicationForm">
@csrf
@foreach($preprocessedForm['fields'] as $fieldName => $field)
@switch ($field['type'])
@component('components.form', ['form' => $preprocessedForm, 'disableFields' => false])
@case('textarea')
<div class="form-group mt-2 mb-2">
<label for="{{$fieldName}}">{{$field['title']}}</label>
<textarea class="form-control" rows="7" name="{{$fieldName}}" id="{{$fieldName}}">
</textarea>
</div>
@break
@case('textbox')
<div class="form-group mt-2 mb-2">
<label for="{{$fieldName}}">{{$field['title']}}</label>
<input type="text" name="{{$fieldName}}" id="{{$fieldName}}" class="form-control">
</div>
@break
@endswitch
@endforeach
@endcomponent
</form>

View File

@@ -0,0 +1,243 @@
@extends('adminlte::page')
@section('title', 'Raspberry Network | Profile')
@section('content_header')
<h4>Application Management / All Applications</h4>
@stop
@section('js')
<script type="text/javascript" src="/js/app.js"></script>
<x-global-errors></x-global-errors>
@stop
@section('content')
@foreach($applications as $application)
<x-modal id="deletionConfirmationModal-{{ $application->id }}" modal-label="deletion-{{ $application->id }}" modal-title="Are you sure?" include-close-button="true">
<h4><i class="fas fa-exclamation-triangle"></i> Really delete this?</h3>
<p>
This action is <b>IRREVERSBILE.</b>
</p>
<p>Comments, appointments and any votes attached to this application WILL be deleted too. Please make sure this application really needs to be deleted.</p>
<x-slot name="modalFooter">
<form method="POST" action="{{ route('deleteApplication', ['application' => $application->id]) }}">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger"><i class="fas fa-check-double"></i> Confirm</button>
</form>
</x-slot>
</x-modal>
@endforeach
<div class="row">
<div class="col">
<div class="callout callout-info">
<div class="row">
<div class="col-3">
<img src="/img/applications_all.svg" alt="Applications illustration" class="img-responsive" width="200px"/>
</div>
<div class="col">
<h3><i class="fas fa-info-circle"></i> You're looking at all applications ever received</h3>
<p>
Here, you have quick and easy access to all applications ever received by the system.
</p>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-5">
<div class="col">
<div class="card">
<!-- MAIN CONTENT - APPS AND PICS -->
<div class="card-header">
<div class="row">
<div class="col-3">
<h3>All applications</h3>
</div>
<div class="col">
<div class="navbtn right" style="whitespace: nowrap">
<button type="button" class="btn btn-sm btn-primary" onclick="window.location.href='{{ route('staffPendingApps') }}'"><i class="far fa-folder-open"></i> Outstanding Applications</button>
<button type="button" class="btn btn-sm btn-primary" onclick="window.location.href='{{ route('pendingInterview') }}'"><i class="fas fa-microphone-alt"></i> Interview Queue</button>
<button type="button" class="btn btn-sm btn-primary" onclick="window.location.href='{{ route('peerReview') }}'"><i class="fas fa-search"></i> Peer Review</button>
</div>
</div>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-3 center">
<img src="/img/placeholders.svg" alt="Placeholder illustration" class="img-responsive" width="200px"/>
</div>
<div class="col">
@if (!$applications->isEmpty())
<table class="table table-borderless" style="whitespace: nowrap">
<thead>
<tr>
<th>#</th>
<th>Applicant</th>
<th>Status</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($applications as $application)
<tr>
<td>{{ $application->id }}</td>
<td><a href="{{ route('showSingleProfile', ['user' => $application->user->id]) }}">{{ $application->user->name }}</a></td>
<td>
@switch($application->applicationStatus)
@case('STAGE_SUBMITTED')
<span class="badge badge-primary"><i class="far fa-clock"></i> Outstanding (Submitted)</span>
@break
@case('STAGE_PEERAPPROVAL')
<span class="badge badge-warning"><i class="fas fa-vote-yea"></i> Peer Approval</span>
@break
@case('STAGE_INTERVIEW')
<span class="badge badge-warning"><i class="fas fa-microphone-alt"></i> Interview</span>
@break
@case('STAGE_INTERVIEW_SCHEDULED')
<span class="badge badge-warning"><i class="far fa-clock"></i>Interview Scheduled</span>
@break
@case('APPROVED')
<span class="badge badge-success"><i class="fas fa-check"></i> Approved</span>
@break
@case('DENIED')
<span class="badge badge-danger"><i class="fas fa-times"></i> Denied</span>
@break;
@default
<span class="badge badge-secondary"><i class="fas fa-question-circle"></i> Unknown</span>
@endswitch
</td>
<td>{{ $application->created_at }}</td>
<td>
<button type="button" class="btn btn-success btn-sm" onclick="window.location.href='{{ route('showUserApp', ['id' => $application->id]) }}'"><i class="fas fa-eye"></i> View</button>
<button type="button" class="btn btn-danger btn-sm ml-2" onclick="$('#deletionConfirmationModal-{{ $application->id }}').modal('show')"><i class="fa fa-trash"></i> Delete</button>
</td>
</tr>
@endforeach
</tbody>
</table>
@else
<div class="alert alert-warning">
<h3><i class="fas fa-question-circle"></i> There are no applications here</h3>
<p>
We couldn't find any applications. Maybe no one has applied yet?
Please try again later.
</p>
</div>
@endif
</div>
</div>
</div>
<!-- end main content card -->
</div>
@if (!$applications->isEmpty() && isset($applications->links))
<div class="card-footer">
{{ $applications->links }}
</div>
@endif
</div>
</div>
@stop

View File

@@ -14,6 +14,38 @@
@section('content')
@if (!$vacancies->isEmpty())
@foreach($vacancies as $vacancy)
<x-modal id="{{ $vacancy->vacancySlug . '-details' }}" modal-label="{{ $vacancy->vacancySlug . '-details-label' }}" modal-title="Vacancy details" include-close-button="true">
@if (is_null($vacancy->vacancyFullDescription))
<div class="alert alert-warning">
<h3><i class="fas fa-question-circle"></i> There don't seem to be any details</h3>
<p>
This vacancy does not have any details yet.
</p>
</div>
@else
{!! $vacancy->vacancyFullDescription !!}
<p class="text-sm text-muted">
Last updated @ {{ $vacancy->updated_at }}
</p>
@endif
<x-slot name="modalFooter"></x-slot>
</x-modal>
@endforeach
@endif
<div class="row mt-5">
<div class="col">
@@ -190,7 +222,7 @@
<div class="card-footer text-center">
<button type="button" class="btn btn-primary btn-sm" onclick="window.location.href='{{ route('renderApplicationForm', ['vacancySlug' => $vacancy->vacancySlug]) }}'">Apply</button>
<button type="button" class="btn btn-warning btn-sm">Learn More</button>
<button type="button" class="btn btn-warning btn-sm" onclick="$('#{{ $vacancy->vacancySlug }}-details').modal('show')">Learn More</button>
</div>
</div>

View File

@@ -2,6 +2,40 @@
@section('content')
@if(!$positions->isEmpty())
<!-- todo: details component -->
@foreach($positions as $position)
<x-modal id="{{ $position->vacancySlug . '-details' }}" modal-label="{{ $position->vacancySlug . '-details-label' }}" modal-title="Opening details" include-close-button="true">
@if (is_null($position->vacancyFullDescription))
<div class="alert alert-warning">
<h3><i class="fas fa-question-circle"></i> There don't seem to be any details</h3>
<p>
This opening does not have any details yet.
</p>
</div>
@else
{!! $position->vacancyFullDescription !!}
<p class="text-sm text-muted">
Last updated @ {{ $position->updated_at }}
</p>
@endif
<x-slot name="modalFooter"></x-slot>
</x-modal>
@endforeach
@endif
<!--Main Layout-->
<main class="py-5">
@@ -57,6 +91,7 @@
@guest
<button type="button" class="btn btn-success" onclick="window.location.href='{{route('renderApplicationForm', ['vacancySlug' => $position->vacancySlug])}}'">Apply</button>
<button type="button" class="btn btn-info" onclick="$('#{{ $position->vacancySlug }}-details').modal('show')">Learn more</button>
@endguest
</div>

View File

@@ -40,7 +40,6 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
->name('showUserApps')
->middleware('eligibility');
Route::get('/view/{id}', 'ApplicationController@showUserApp')
->name('showUserApp');
@@ -58,16 +57,27 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
Route::patch('/update/{id}/{newStatus}', 'ApplicationController@updateApplicationStatus')
->name('updateApplicationStatus');
Route::delete('{application}/delete', 'ApplicationController@delete')
->name('deleteApplication');
Route::get('/staff/all', 'ApplicationController@showAllApps')
->name('allApplications');
Route::get('/staff/outstanding', 'ApplicationController@showAllPendingApps')
->name('staffPendingApps');
Route::get('/staff/peer-review', 'ApplicationController@showPeerReview')
->name('peerReview');
Route::get('/staff/pending-interview', 'ApplicationController@showPendingInterview')
->name('pendingInterview');
Route::post('{id}/staff/vote', 'VoteController@vote')
->name('voteApplication');
@@ -161,6 +171,13 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
->name('savePosition');
Route::get('positions/edit/{position}', 'VacancyController@edit')
->name('editPosition');
Route::patch('positions/update/{position}', 'VacancyController@update')
->name('updatePosition');
Route::patch('positions/availability/{status}/{id}', 'VacancyController@updatePositionAvailability')
->name('updatePositionAvailability');
@@ -177,6 +194,9 @@ Route::group(['middleware' => ['auth', 'forcelogout']], function(){
Route::get('forms', 'FormController@index')
->name('showForms');
Route::get('forms/preview/{form}', 'FormController@preview')
->name('previewForm');
Route::get('devtools', 'DevToolsController@index')
->name('devTools');