RSM-5 Made Vacancies easily linkable to Teams

This commit is contained in:
Miguel Nogueira 2020-10-09 22:27:36 +01:00
parent 6cc99d2ebe
commit 077ead9612
8 changed files with 200 additions and 34 deletions

View File

@ -2,11 +2,14 @@
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/database/factories" isTestSource="false" packagePrefix="Database\Factories\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="Tests\" />
<sourceFolder url="file://$MODULE_DIR$/database/seeders" isTestSource="false" packagePrefix="Database\Seeders\" />
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/app" isTestSource="false" packagePrefix="App\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/almasaeed2010/adminlte" />
<excludeFolder url="file://$MODULE_DIR$/vendor/asm89/stack-cors" />
<excludeFolder url="file://$MODULE_DIR$/vendor/awssat/discord-notification-channel" />
<excludeFolder url="file://$MODULE_DIR$/vendor/barryvdh/laravel-debugbar" />
<excludeFolder url="file://$MODULE_DIR$/vendor/brick/math" />
<excludeFolder url="file://$MODULE_DIR$/vendor/clue/stream-filter" />
@ -27,6 +30,7 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/filp/whoops" />
<excludeFolder url="file://$MODULE_DIR$/vendor/fruitcake/laravel-cors" />
<excludeFolder url="file://$MODULE_DIR$/vendor/fzaninotto/faker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/graham-campbell/result-type" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/guzzle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
@ -67,6 +71,7 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/phpspec/prophecy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-code-coverage" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-file-iterator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-invoker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-text-template" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-timer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-token-stream" />
@ -83,12 +88,16 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/ramsey/collection" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ramsey/uuid" />
<excludeFolder url="file://$MODULE_DIR$/vendor/scrivo/highlight.php" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/cli-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/code-unit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/complexity" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/diff" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/environment" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/exporter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/global-state" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/lines-of-code" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-enumerator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-reflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/recursion-context" />
@ -107,6 +116,8 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/finder" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-foundation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-kernel" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mime" />

View File

@ -142,9 +142,18 @@
<path value="$PROJECT_DIR$/vendor/league/mime-type-detection" />
<path value="$PROJECT_DIR$/vendor/mcamara/laravel-localization" />
<path value="$PROJECT_DIR$/vendor/mpociot/teamwork" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit" />
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/vendor/graham-campbell/result-type" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client-contracts" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/vendor/awssat/discord-notification-channel" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.2" />
<component name="PhpProjectSharedConfiguration" php_language_level="7.3" />
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings configuration_file_path="$PROJECT_DIR$/phpunit.xml" custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" use_configuration_file="true" />

View File

@ -16,6 +16,7 @@ use Mpociot\Teamwork\Exceptions\UserNotInTeamException;
use Mpociot\Teamwork\Facades\Teamwork;
use Mpociot\Teamwork\TeamInvite;
class TeamController extends Controller
{
/**
@ -82,7 +83,7 @@ class TeamController extends Controller
return view('dashboard.teams.edit-team')
->with('team', $team)
->with('users', User::all())
->with('vacancies', Vacancy::all());
->with('vacancies', Vacancy::with('teams')->get()->all());
}
/**
@ -115,7 +116,7 @@ class TeamController extends Controller
}
public function invite(SendInviteRequest $request, Team $team)
{
{
$user = User::findOrFail($request->user);
if (!$team->openJoin)
@ -126,29 +127,29 @@ class TeamController extends Controller
Teamwork::inviteToTeam($user, $team, function(TeamInvite $invite) use ($user) {
Mail::to($user)->send(new InviteToTeam($invite));
});
$request->session()->flash('success', 'Invite sent! They can now accept or deny it.');
}
else
else
{
$request->session()->flash('error', 'This user has already been invited.');
}
}
else
{
$request->session()->flash('error', 'You can\'t invite users to public teams.');
}
return redirect()->back();
return redirect()->back();
}
public function processInviteAction(Request $request, $action, $token)
{
switch($action)
{
case 'accept':
@ -168,7 +169,7 @@ class TeamController extends Controller
break;
case 'deny':
$invite = Teamwork::getInviteFromDenyToken($token);
if ($invite && $invite->user->is(Auth::user()))
@ -182,10 +183,10 @@ class TeamController extends Controller
}
break;
default:
$request->session()->flash('error', 'Sorry, but the invite URL you followed was malformed. Try asking for another invite, or submit a bug report.');
}
@ -209,4 +210,53 @@ class TeamController extends Controller
return redirect()->back();
}
// Since it's a separate form, we shouldn't use the same update method
public function assignVacancies(Request $request, Team $team)
{
// P.S. To future developers
// This method gave me a lot of trouble lol. It's hard to write code when you're half asleep.
// There may be an n+1 query in the view and I don't think there's a way to avoid that without writing a lot of extra code.
$requestVacancies = $request->assocVacancies;
$currentVacancies = $team->vacancies->pluck('id')->all();
if (is_null($requestVacancies))
{
foreach ($team->vacancies as $vacancy)
{
$team->vacancies()->detach($vacancy->id);
}
$request->session()->flash('success', 'Removed all vacancy associations.');
return redirect()->back();
}
$vacancyDiff = array_diff($requestVacancies, $currentVacancies);
$deselectedDiff = array_diff($currentVacancies, $requestVacancies);
if (!empty($vacancyDiff) || !empty($deselectedDiff))
{
foreach ($vacancyDiff as $selectedVacancy)
{
$team->vacancies()->attach($selectedVacancy);
}
foreach ($deselectedDiff as $deselectedVacancy)
{
$team->vacancies()->detach($deselectedVacancy);
}
}
else
{
$team->vacancies()->attach($requestVacancies);
}
$request->session()->flash('success', 'Assignments changed successfully.');
return redirect()->back();
}
}

View File

@ -80,4 +80,32 @@ class Vacancy extends Model
}
/**
* Check if the Modal is attached to the $checkingTeam Model
*
* @param Team $checkingTeam The mdoel you want to check against
* @return boolean Whether the models are attached
*/
public function hasTeam(Team $checkingTeam): bool
{
$myTeams = $this->teams;
if (empty($myTeams))
{
// no associated teams
return false;
}
foreach($myTeams as $team)
{
if ($team->id === $checkingTeam->id)
{
return true;
}
}
return false;
}
}

View File

@ -12,6 +12,7 @@
"ext-imagick": "*",
"ext-json": "*",
"arcanedev/log-viewer": "^8.0",
"awssat/discord-notification-channel": "^1.4",
"doctrine/dbal": "^2.10",
"fideloper/proxy": "^4.2",
"fruitcake/laravel-cors": "^1.0",

57
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": "db269f8f20690fd92cc9b51dbbca3fc5",
"content-hash": "b91b3c69e28100abbffdb9bb256025fd",
"packages": [
{
"name": "almasaeed2010/adminlte",
@ -225,6 +225,61 @@
],
"time": "2019-12-24T22:41:47+00:00"
},
{
"name": "awssat/discord-notification-channel",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/awssat/discord-notification-channel.git",
"reference": "514df4bb5db48f624658240d6b1f9b8ee468a46f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/awssat/discord-notification-channel/zipball/514df4bb5db48f624658240d6b1f9b8ee468a46f",
"reference": "514df4bb5db48f624658240d6b1f9b8ee468a46f",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.0|^7.0",
"illuminate/notifications": "~5.8.0|^6.0|^7.0|^8.0",
"laravel/slack-notification-channel": "^2.0",
"php": "^7.1.3"
},
"require-dev": {
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^7.0|^8.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Awssat\\Notifications\\DiscordChannelServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Awssat\\Notifications\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bader Almutairi",
"email": "phpfalcon@gmail.com"
}
],
"description": "Discord Notification Channel for laravel.",
"keywords": [
"discord",
"laravel",
"notifications"
],
"time": "2020-09-09T20:42:43+00:00"
},
{
"name": "bacon/bacon-qr-code",
"version": "2.0.2",

View File

@ -60,13 +60,13 @@
<tbody>
@foreach ($team->users as $teammate)
<tr>
<td>{{ $teammate->id }}</td>
<td><a target="_blank" href="{{ route('showSingleProfile', ['user' => $teammate->id]) }}">{{ $teammate->name }}</a></td>
<td>
@foreach ($teammate->roles as $teammate_role)
<span class="badge badge-secondary">{{ $teammate_role->name }}</span>
@endforeach
@ -123,21 +123,21 @@
</div>
<div class="card-body">
<form id="editTeam" method="POST" action="{{ route('teams.update', ['team' => $team->id]) }}">
@csrf
@method('PATCH')
<div class="form-group">
<label for="teamName">Team name</label>
<input type="text" class="form-control" id="teamName" value="{{ $team->name }}" disabled>
<label for="teamDescription">Team description</label>
<textarea class="form-control" rows="4" name="teamDescription" id="teamDescription">{{ $team->description }}</textarea>
<span class="text-left text-muted text-sm"><a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"><i class="fab fa-markdown"></i></a> Markdown supported</span>
<div class="controlbuttons mt-3">
<span rel="spanTxtTooltip" data-toggle="tooltip" title="This setting controls whether people can join the team freely." data-placement="top">
@ -147,11 +147,11 @@
<button onclick="$('#addUserModal').modal('show')" type="button" id="inviteUser" name="inviteUser" class="btn btn-success" {{ ($team->openJoin) ? 'disabled' : '' }}><i class="fas fa-user-plus"></i> Invite user</button>
</div>
</div>
</form>
</div>
<div class="card-footer">
@ -176,14 +176,18 @@
<div class="card-body">
<span class="text-muted"><i class="fas fa-info-circle"></i> The vacancies you select determine what applications your team members see. All applications under the vacancies you choose will be displayed.</span>
<form>
<select id="assocVacancies" name="assocVacancies" multiple="multiple">
<form method="POST" id="vacancyChangeForm" action="{{ route('assignVacancies', ['team' => $team->id]) }}">
@csrf
@method('PATCH')
<select id="assocVacancies" name="assocVacancies[]" multiple="multiple">
@foreach($vacancies as $vacancy)
<option value="{{ $vacancy->id }}">{{ $vacancy->vacancyName }}</option>
<!-- fixme: n+1 query here -->
<option {{ ($vacancy->hasTeam($team)) ? 'selected' : '' }} value="{{ $vacancy->id }}">{{ $vacancy->vacancyName }}</option>
@endforeach
@ -193,6 +197,12 @@
</div>
<div class="card-footer">
<button onclick="$('#vacancyChangeForm').submit()" type="button" class="btn btn-success"><i class="far fa-save"></i> Update Assignments</button>
</div>
</div>
</div>
@ -200,4 +210,4 @@
</div>
@stop
@stop

View File

@ -60,22 +60,24 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => [ 'l
->name('directory');
Route::resource('teams', TeamController::class);
Route::post('teams/{team}/invites/send', [TeamController::class, 'invite'])
Route::post('teams/{team}/invites/send', [TeamController::class, 'invite'])
->name('sendInvite');
Route::get('teams/{team}/switch', [TeamController::class, 'switchTeam'])
Route::get('teams/{team}/switch', [TeamController::class, 'switchTeam'])
->name('switchTeam');
Route::patch('teams/{team}/vacancies/update', [TeamController::class, 'assignVacancies'])
->name('assignVacancies');
Route::get('teams/invites/{action}/{token}', [TeamController::class, 'processInviteAction'])
->name('processInvite');
// WARNING: This is a resource, might not work under laravel 8.
Route::resource('teams', TeamController::class);
Route::group(['prefix' => '/applications'], function (){
@ -251,7 +253,7 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => [ 'l
Route::delete('forms/destroy/{form}', [FormController::class, 'destroy'])
->name('destroyForm');
Route::get('forms', [FormController::class, 'showFormBuilder'])
Route::get('forms', [FormController::class, 'index'])
->name('showForms');
Route::get('forms/preview/{form}', [FormController::class, 'preview'])