feat: add loa requests
This commit adds a feature that allows users to request periods of inactivity from their managers. This is effectively known as a leave of absence. The commit also introduces new permissions and migrations, therefore, you'll need to adapt your database according to these changes.
This commit is contained in:
parent
f61a287c78
commit
e567094f40
@ -2,6 +2,8 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Exceptions\AbsenceNotActionableException;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@ -25,4 +27,99 @@ class Absence extends Model
|
||||
{
|
||||
return $this->belongsTo('App\User', 'requesterID', 'id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether this model can be setApproved(), setDeclined() or setCancelled()
|
||||
*
|
||||
* @param bool $toCancel Switch the check to cancellability check
|
||||
* @return bool
|
||||
*/
|
||||
public function isActionable(bool $toCancel = false): bool
|
||||
{
|
||||
if ($toCancel)
|
||||
{
|
||||
return in_array($this->getRawOriginal('status'), ['PENDING', 'APPROVED']);
|
||||
}
|
||||
|
||||
return $this->getRawOriginal('status') == 'PENDING';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the Absence's status as approved
|
||||
*
|
||||
* @return Absence
|
||||
* @throws AbsenceNotActionableException
|
||||
*/
|
||||
public function setApproved(): Absence
|
||||
{
|
||||
if ($this->isActionable())
|
||||
{
|
||||
return tap($this)->update([
|
||||
'status' => 'APPROVED'
|
||||
]);
|
||||
}
|
||||
|
||||
throw new AbsenceNotActionableException('This absence is not actionable!');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the absence's status as declined
|
||||
*
|
||||
* @return Absence
|
||||
* @throws AbsenceNotActionableException
|
||||
*/
|
||||
public function setDeclined(): Absence
|
||||
{
|
||||
if ($this->isActionable()) {
|
||||
return tap($this)->update([
|
||||
'status' => 'DECLINED'
|
||||
]);
|
||||
}
|
||||
|
||||
throw new AbsenceNotActionableException('This absence is not actionable!');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the absence's status as cancelled
|
||||
*
|
||||
* @return Absence
|
||||
* @throws AbsenceNotActionableException Thrown when the switch to this status would be invalid
|
||||
*/
|
||||
public function setCancelled(): Absence
|
||||
{
|
||||
if ($this->isActionable(true)) {
|
||||
return tap($this)->update([
|
||||
'status' => 'CANCELLED'
|
||||
]);
|
||||
}
|
||||
|
||||
throw new AbsenceNotActionableException('This absence is not actionable!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the absence's status as ended
|
||||
*
|
||||
* @return Absence
|
||||
*/
|
||||
public function setEnded(): Absence
|
||||
{
|
||||
return tap($this)->update([
|
||||
'status' => 'ENDED'
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
// Look out when retrieving this value;
|
||||
//If you need the unaltered version of it, either adapt to its formatting or call getRawOriginal()
|
||||
protected function status(): Attribute {
|
||||
return Attribute::make(
|
||||
get: fn($value) => ucfirst(strtolower($value))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
10
app/Exceptions/AbsenceNotActionableException.php
Normal file
10
app/Exceptions/AbsenceNotActionableException.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class AbsenceNotActionableException extends Exception
|
||||
{
|
||||
//
|
||||
}
|
@ -3,10 +3,12 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Absence;
|
||||
use App\Exceptions\AbsenceNotActionableException;
|
||||
use App\Http\Requests\StoreAbsenceRequest;
|
||||
use App\Http\Requests\UpdateAbsenceRequest;
|
||||
use App\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
@ -28,7 +30,7 @@ class AbsenceController extends Controller
|
||||
// Or we could adjust the query (using a model scope) to only return valid absences;
|
||||
// If there are any, refuse to store more, but this approach also works
|
||||
// A model scope that only returns cancelled, declined and ended absences could also be implemented for future use
|
||||
if (in_array($absence->status, ['PENDING', 'APPROVED']))
|
||||
if (in_array($absence->getRawOriginal('status'), ['PENDING', 'APPROVED']))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -38,31 +40,51 @@ class AbsenceController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
* Display a listing of absences.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('viewAny', Absence::class);
|
||||
|
||||
return view('dashboard.absences.index')
|
||||
->with('absences', Absence::all());
|
||||
// display for admin users
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
* Display a listing of absences belonging to the current user.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function showUserAbsences()
|
||||
{
|
||||
$this->authorize('viewOwn', Absence::class);
|
||||
|
||||
return view('dashboard.absences.own')
|
||||
->with('absences', Auth::user()->absences);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Show the form for creating a new absence request.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->authorize('create', Absence::class);
|
||||
|
||||
return view('dashboard.absences.create')
|
||||
->with('activeRequest', $this->hasActiveRequest(Auth::user()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
* Store a newly created request in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\StoreAbsenceRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
@ -77,62 +99,133 @@ class AbsenceController extends Controller
|
||||
->with('error', __('You already have an active request. Cancel it or let it expire first.'));
|
||||
}
|
||||
|
||||
Absence::create([
|
||||
|
||||
$absence = Absence::create([
|
||||
'requesterID' => Auth::user()->id,
|
||||
'start' => $request->start_date,
|
||||
'predicted_end' => $request->predicted_end,
|
||||
'available_assist' => $request->invalidAbsenceAgreement == 'on',
|
||||
'available_assist' => $request->available_assist == "on",
|
||||
'reason' => $request->reason,
|
||||
'status' => 'PENDING',
|
||||
]);
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->to(route('absences.show', ['absence' => $absence->id]))
|
||||
->with('success', 'Absence request submitted for approval. You will receive email confirmation shortly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
* Display the specified absence request.
|
||||
*
|
||||
* @param \App\Absence $absence
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Absence $absence)
|
||||
{
|
||||
//
|
||||
$this->authorize('view', $absence);
|
||||
|
||||
return view('dashboard.absences.view')
|
||||
->with([
|
||||
'absence' => $absence,
|
||||
'totalDays' => Carbon::parse($absence->start)->diffInDays($absence->predicted_end)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
* Approve the specified absence.
|
||||
*
|
||||
* @param \App\Absence $absence
|
||||
* @return \Illuminate\Http\Response
|
||||
* @param Absence $absence
|
||||
* @return RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function edit(Absence $absence)
|
||||
public function approveAbsence(Absence $absence): RedirectResponse
|
||||
{
|
||||
//
|
||||
$this->authorize('approve', $absence);
|
||||
|
||||
try
|
||||
{
|
||||
$absence->setApproved();
|
||||
}
|
||||
catch (AbsenceNotActionableException $notActionableException)
|
||||
{
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', $notActionableException->getMessage());
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('success', __('Absence request successfully approved. It will automatically transition to "Ended" on its predicted end date.'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
* Decline the specified absence.
|
||||
*
|
||||
* @param \App\Http\Requests\UpdateAbsenceRequest $request
|
||||
* @param \App\Absence $absence
|
||||
* @return \Illuminate\Http\Response
|
||||
* @param Absence $absence
|
||||
* @return RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function update(UpdateAbsenceRequest $request, Absence $absence)
|
||||
public function declineAbsence(Absence $absence): RedirectResponse
|
||||
{
|
||||
//
|
||||
$this->authorize('decline', $absence);
|
||||
|
||||
try
|
||||
{
|
||||
$absence->setDeclined();
|
||||
} catch (AbsenceNotActionableException $notActionableException)
|
||||
{
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', $notActionableException->getMessage());
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('success', __('Absence request successfully declined.'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cancel the specified absence.
|
||||
*
|
||||
* @param Absence $absence
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function cancelAbsence(Absence $absence): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$this->authorize('cancel', $absence);
|
||||
|
||||
try
|
||||
{
|
||||
$absence->setCancelled();
|
||||
}
|
||||
catch (AbsenceNotActionableException $notActionableException)
|
||||
{
|
||||
return redirect()
|
||||
->back()
|
||||
->with('error', $notActionableException->getMessage());
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('success', __('Absence request successfully cancelled.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Absence $absence
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Absence $absence)
|
||||
{
|
||||
//
|
||||
$this->authorize('delete', $absence);
|
||||
|
||||
if ($absence->delete()) {
|
||||
return redirect()
|
||||
->to(route('absences.index'))
|
||||
->with('success', __('Absence request deleted.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,10 +21,20 @@ class AbsencePolicy
|
||||
{
|
||||
if ($user->hasPermissionTo('admin.viewAllAbsences'))
|
||||
{
|
||||
return \Illuminate\Auth\Access\Response::allow();
|
||||
return true;
|
||||
}
|
||||
|
||||
return \Illuminate\Auth\Access\Response::deny('Forbidden');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function viewOwn(User $user): bool
|
||||
{
|
||||
if ($user->hasPermissionTo('reviewer.viewAbsence')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -36,7 +46,7 @@ class AbsencePolicy
|
||||
*/
|
||||
public function view(User $user, Absence $absence)
|
||||
{
|
||||
if ($user->hasPermissionTo('reviewer.viewAbsence') && $user->id == $absence->requesterID)
|
||||
if ($user->hasPermissionTo('reviewer.viewAbsence') && $user->is($absence->requester) || $user->hasPermissionTo('admin.manageAbsences'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -55,16 +65,49 @@ class AbsencePolicy
|
||||
return $user->hasPermissionTo('reviewer.requestAbsence');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
* Determine whether the user can approve the absence request
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\Absence $absence
|
||||
* @return \Illuminate\Auth\Access\Response|bool
|
||||
* @param User $user
|
||||
* @param Absence $absence
|
||||
* @return bool
|
||||
*/
|
||||
public function update(User $user, Absence $absence)
|
||||
public function approve(User $user, Absence $absence): bool
|
||||
{
|
||||
return $user->hasPermissionTo('admin.manageAbsences');
|
||||
if ($user->can('admin.manageAbsences') && $user->isNot($absence->requester))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function decline(User $user, Absence $absence): bool
|
||||
{
|
||||
if ($user->can('admin.manageAbsences') && $user->isNot($absence->requester))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can cancel the absence request
|
||||
*
|
||||
* @param User $user
|
||||
* @param Absence $absence
|
||||
* @return bool
|
||||
*/
|
||||
public function cancel(User $user, Absence $absence): bool {
|
||||
|
||||
if($user->is($absence->requester) && $user->can('reviewer.withdrawAbsence')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,7 +119,8 @@ class AbsencePolicy
|
||||
*/
|
||||
public function delete(User $user, Absence $absence)
|
||||
{
|
||||
return $user->hasPermissionTo('admin.manageAbsences') || $user->hasPermissionTo('reviewer.withdrawAbsence') && $user->id == $absence->requesterID;
|
||||
return $user->hasPermissionTo('admin.manageAbsences');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -21,11 +21,13 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Absence;
|
||||
use App\ApiKey;
|
||||
use App\Application;
|
||||
use App\Appointment;
|
||||
use App\Ban;
|
||||
use App\Form;
|
||||
use App\Policies\AbsencePolicy;
|
||||
use App\Policies\ApiKeyPolicy;
|
||||
use App\Policies\ApplicationPolicy;
|
||||
use App\Policies\AppointmentPolicy;
|
||||
@ -64,7 +66,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
Appointment::class => AppointmentPolicy::class,
|
||||
Team::class => TeamPolicy::class,
|
||||
TeamFile::class => TeamFilePolicy::class,
|
||||
ApiKey::class => ApiKeyPolicy::class
|
||||
Absence::class => AbsencePolicy::class
|
||||
];
|
||||
|
||||
/**
|
||||
|
0
composer.lock
generated
Normal file → Executable file
0
composer.lock
generated
Normal file → Executable file
@ -278,22 +278,22 @@ return [
|
||||
'can' => 'reviewer.requestAbsence',
|
||||
'submenu' => [
|
||||
[
|
||||
'text' => 'Request LOA',
|
||||
'icon' => 'far fa-clock',
|
||||
'text' => 'New request',
|
||||
'icon' => 'fas fa-plus',
|
||||
'can' => 'reviewer.requestAbsence',
|
||||
'url' => 'tba'
|
||||
'route' => 'absences.create'
|
||||
],
|
||||
[
|
||||
'text' => 'My LOA Requests',
|
||||
'text' => 'My requests',
|
||||
'icon' => 'fas fa-business-time',
|
||||
'can' => 'reviewer.viewAbsence',
|
||||
'url' => 'tba'
|
||||
'route' => 'showUserAbsences'
|
||||
],
|
||||
|
||||
],
|
||||
],
|
||||
[
|
||||
'text' => 'Absence Requests',
|
||||
'text' => 'Absence requests',
|
||||
'icon' => 'fas fa-address-card',
|
||||
'can' => 'admin.manageAbsences',
|
||||
'route' => 'absences.index'
|
||||
|
0
config/markdown.php
Normal file → Executable file
0
config/markdown.php
Normal file → Executable file
@ -9,7 +9,7 @@
|
||||
</button>
|
||||
</a>
|
||||
@else
|
||||
<button {{ ($disabled == true) ? 'disabled' : ''}} type="{{ $type }}" class="btn {{ !empty($size) ? 'btn-' . $size : '' }} btn-{{ $color }}" id="{{ $id }}">
|
||||
<button {{ ($disabled == true) ? 'disabled' : ''}} type="{{ $type }}" class="ml-2 btn {{ !empty($size) ? 'btn-' . $size : '' }} btn-{{ $color }}" id="{{ $id }}">
|
||||
@if (empty($icon))
|
||||
{{ $slot }}
|
||||
@else
|
||||
|
@ -106,7 +106,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<x-button id="btnCancelRequest" color="info" icon="fas fa-info-circle">
|
||||
<x-button link="{{ route('absences.index') }}" id="btnCancelRequest" color="info" icon="fas fa-info-circle">
|
||||
{{ __('Cancel request') }}
|
||||
</x-button>
|
||||
</div>
|
||||
@ -117,3 +117,7 @@
|
||||
|
||||
|
||||
@stop
|
||||
|
||||
@section('footer')
|
||||
@include('breadcrumbs.dashboard.footer')
|
||||
@stop
|
||||
|
@ -42,7 +42,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
@if (true)
|
||||
@if (!$absences->isEmpty())
|
||||
<table class="table table-borderless table-active">
|
||||
|
||||
<thead>
|
||||
@ -61,7 +61,7 @@
|
||||
<td>{{ $absence->requester->name }}</td>
|
||||
<td><span class="badge badge-warning"><i class="fas fa-exclamation-circle"></i> {{ __('None yet') }}</span></td>
|
||||
<td>
|
||||
@switch($absence->status)
|
||||
@switch($absence->getRawOriginal('status'))
|
||||
|
||||
@case('PENDING')
|
||||
<span class="badge badge-warning"><i class="fas fa-clock"></i> {{ __('Pending') }}</span>
|
||||
@ -113,3 +113,7 @@
|
||||
|
||||
|
||||
@stop
|
||||
|
||||
@section('footer')
|
||||
@include('breadcrumbs.dashboard.footer')
|
||||
@stop
|
||||
|
115
resources/views/dashboard/absences/own.blade.php
Normal file
115
resources/views/dashboard/absences/own.blade.php
Normal file
@ -0,0 +1,115 @@
|
||||
@extends('adminlte::page')
|
||||
|
||||
@section('title', config('app.name') . ' | ' . __('Member absence requests'))
|
||||
|
||||
@section('content_header')
|
||||
|
||||
<h4>{{__('Human Resources')}} / {{ __('Reviewer') }} / {{__('Absence management')}}</h4>
|
||||
|
||||
@stop
|
||||
|
||||
@section('js')
|
||||
|
||||
<x-global-errors></x-global-errors>
|
||||
|
||||
@stop
|
||||
|
||||
@section('content')
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
||||
<x-alert alert-type="info">
|
||||
<p class="text-bold"><i class="fas fa-info-circle"></i> {{ __('What is a leave of absence?') }}</p>
|
||||
|
||||
<p>{{ __('A leave of absence is a time period in which an employee takes personal time off, for a multitude of reasons. It\'s a prolonged, authorized absence form work and/or other duties, communicated in advance, usually via letter or via an HR system.') }}</p>
|
||||
|
||||
<p>{{ __('Here, you\'ll be able to view and approve leave requests from staff members. Notifications are sent out to ensure the right people know about this leave in advance. Staff members may ignore declined leave requests, however, their time off will be considered as a period of inactivity (no-show).') }}</p>
|
||||
</x-alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
<div class="card bg-gray-dark">
|
||||
|
||||
<div class="card-header bg-indigo">
|
||||
|
||||
<div class="card-title"><h4 class="text-bold">{{__('Leave of absence requests')}}</h4></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
@if (!$absences->isEmpty())
|
||||
<table class="table table-borderless table-active">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{__('Requesting user')}}</th>
|
||||
<th>{{__('Reviewing admin')}}</th>
|
||||
<th>{{ __('Status') }}</th>
|
||||
<th>{{ __('Request date') }}</th>
|
||||
<th>{{ __('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@foreach($absences as $absence)
|
||||
<tr>
|
||||
<td>{{ $absence->requester->name }}</td>
|
||||
<td><span class="badge badge-warning"><i class="fas fa-exclamation-circle"></i> {{ __('None yet') }}</span></td>
|
||||
<td>
|
||||
@switch($absence->getRawOriginal('status'))
|
||||
|
||||
@case('PENDING')
|
||||
<span class="badge badge-warning"><i class="fas fa-clock"></i> {{ __('Pending') }}</span>
|
||||
@break
|
||||
|
||||
@case('APPROVED')
|
||||
<span class="badge badge-success"><i class="far fa-thumbs-up"></i> {{ __('Approved') }}</span>
|
||||
@break
|
||||
|
||||
@case('DECLINED')
|
||||
<span class="badge badge-danger"><i class="far fa-thumbs-down"></i> {{ __('Declined') }}</span>
|
||||
@break
|
||||
|
||||
@case('CANCELLED')
|
||||
<span class="badge badge-secondary"><i class="fas fa-ban"></i> {{ __('Cancelled') }}</span>
|
||||
@break
|
||||
|
||||
@case('ENDED')
|
||||
<span class="badge badge-info"><i class="fas fa-history"></i> {{ __('Ended') }}</span>
|
||||
@break
|
||||
@endswitch
|
||||
</td>
|
||||
<td>{{ $absence->created_at }}</td>
|
||||
<td><a href="{{ route('absences.show', ['absence' => $absence->id]) }}" class="btn btn-warning btn-sm"><i class="fas fa-search"></i> {{ __('Review') }}</a></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="alert alert-warning">
|
||||
|
||||
<i class="fas fa-exclamation-triangle"></i><span> {{__('No requests')}}</span>
|
||||
<p>
|
||||
{{__('You haven\'t submitted any requests yet! Remember that you can only have one active request.')}}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<a href="{{ route('absences.create') }}"><button class="btn btn-success btn-sm"><i class="fas fa-plus-circle"></i> New request</button></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@stop
|
170
resources/views/dashboard/absences/view.blade.php
Executable file
170
resources/views/dashboard/absences/view.blade.php
Executable file
@ -0,0 +1,170 @@
|
||||
@extends('adminlte::page')
|
||||
|
||||
@section('title', config('app.name') . ' | ' . __('Member absence review'))
|
||||
|
||||
@section('content_header')
|
||||
|
||||
<h4>{{__('Human Resources')}} / {{ __('Staff') }} / {{__('View absence')}}</h4>
|
||||
|
||||
@stop
|
||||
|
||||
@section('js')
|
||||
|
||||
<x-global-errors></x-global-errors>
|
||||
|
||||
@stop
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
@csrf
|
||||
|
||||
<div class="card card-secondary">
|
||||
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">{{ __('Leave of absence') }}</h4>
|
||||
<div class="card-tools">
|
||||
|
||||
@can('admin.manageAbsences')
|
||||
@if (Auth::user()->is($absence->requester))
|
||||
<span rel="spanTxtTooltip" class="badge-warning badge" data-toggle="tooltip" data-placement="top" title="{{ __('While you have the necessary permissions to manage all absence requests, you may not approve nor deny your own requests, however, you may still delete them.') }}"><i class="fas fa-exclamation-triangle"></i> {{ __('Your request') }}</span>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@switch($absence->status)
|
||||
@case('Pending')
|
||||
<span rel="spanTxtTooltip" data-toggle="tooltip" data-placement="top" title="{{ __('Waiting review by an admin') }}" class="badge badge-primary"><i class="fas fa-hourglass"></i> {{ $absence->status }}</span>
|
||||
@break
|
||||
@case('Approved')
|
||||
<span rel="spanTxtTooltip" data-toggle="tooltip" data-placement="top" title="{{ __('Approved by an admin') }}" class="badge badge-success"><i class="fas fa-clipboard-check"></i> {{ $absence->status }}</span>
|
||||
@break
|
||||
@case('Declined')
|
||||
@case('Cancelled')
|
||||
<span rel="spanTxtTooltip" data-toggle="tooltip" data-placement="top" title="{{ __('Declined by an admin or withdrawn by the requester') }}" class="badge badge-danger"><i class="fas fa-ban"></i> {{ $absence->status }}</span>
|
||||
@break
|
||||
@case('Ended')
|
||||
<span rel="spanTxtTooltip" data-toggle="tooltip" data-placement="top" title="{{ __('This request reached its predicted end date') }}" class="badge badge-warning"><i class="fas fa-calendar-check"></i> {{ $absence->status }}</span>
|
||||
@break
|
||||
@default
|
||||
<span class="badge badge-danger"><i class="fas fa-bolt"></i> {{ __('Unavailable!') }}</span>
|
||||
|
||||
@endswitch
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<h3><i class="fas fa-info-circle"></i> {{ __('Request details') }}</h3>
|
||||
<hr>
|
||||
|
||||
<label for="submittedDate"><i class="fas fa-paper-plane"></i> {{ __('Submitted at') }}</label>
|
||||
<p id="submittedDate">{{ \Carbon\Carbon::parse($absence->created_at)->ago() }}</p>
|
||||
|
||||
<label for="timeframe"><i class="fas fa-calendar-alt"></i> {{__('Requested time period')}}</label>
|
||||
<p id="timeframe">{{ $absence->start }} — {{ $absence->predicted_end }} {{ __('(:totalDays days)', ['totalDays' => $totalDays]) }}</p>
|
||||
|
||||
<label for="available"><i class="fas fa-user-cog"></i> {{ __('Available to chat?') }}</label>
|
||||
@if($absence->available_assist == "1")
|
||||
<span id="available" class="badge badge-success"><i class="fas fa-check"></i> {{ __('Available') }}</span>
|
||||
@else
|
||||
<span id="available" class="badge badge-warning"><i class="fas fa-user-slash"></i> {{ __('Not available') }}</span>
|
||||
@endif
|
||||
|
||||
<p class="text-muted text-sm"><i class="fas fa-info-circle"></i> {{ __('This indicates whether the requesting user will be able to respond to emails, DMs, etc, during their absence.') }}</p>
|
||||
|
||||
<label for="reason"><i class="fas fa-clipboard"></i> {{ __('Request reason') }}</label>
|
||||
<input type="text" class="form-control" disabled value="{{ $absence->reason }}">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-footer text-center">
|
||||
@can('admin.manageAbsences')
|
||||
|
||||
@if(!Auth::user()->is($absence->requester) && $absence->isActionable())
|
||||
<form class="d-inline" name="approveRequestFrm" method="post" action="{{ route('approveAbsence', ['absence' => $absence->id]) }}">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<x-button id="approveRequestBtn" size="sm" type="submit" color="success" icon="fas fa-check-double">
|
||||
{{ __('Approve request') }}
|
||||
</x-button>
|
||||
</form>
|
||||
|
||||
<form class="d-inline" name="denyRequestFrm" method="post" action="{{ route('declineAbsence', ['absence' => $absence->id]) }}">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<x-button id="denyRequestBtn" size="sm" type="submit" color="danger" icon="fas fa-ban">
|
||||
{{ __('Deny request') }}
|
||||
</x-button>
|
||||
</form>
|
||||
|
||||
@endif
|
||||
|
||||
<form class="d-inline" name="deleteAbsence" method="post" action="{{ route('absences.destroy', ['absence' => $absence->id]) }}">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<x-button id="deleteRequestBtn" size="sm" type="submit" color="danger" icon="fas fa-trash">
|
||||
{{ __('Delete request') }}
|
||||
</x-button>
|
||||
</form>
|
||||
@endcan
|
||||
|
||||
@if (Auth::user()->is($absence->requester) && $absence->isActionable(true))
|
||||
@can('reviewer.withdrawAbsence')
|
||||
<form class="d-inline" name="cancelRequest" method="post" action="{{ route('cancelAbsence', ['absence' => $absence->id]) }}">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<x-button id="retractRequestBtn" size="sm" type="submit" color="warning" icon="fas fa-undo">
|
||||
{{ __('Retract request') }}
|
||||
</x-button>
|
||||
</form>
|
||||
@endcan
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col">
|
||||
|
||||
<div class="card card-widget widget-user">
|
||||
<div class="widget-user-header bg-secondary">
|
||||
<h3 class="widget-user-username">{{ $absence->requester->name }} </h3>
|
||||
<h5 class="widget-user-desc">{{ $absence->requester->email }}</h5>
|
||||
</div>
|
||||
<div class="widget-user-image">
|
||||
@if($absence->requester->profile->avatarPreference == 'gravatar')
|
||||
<img class="profile-user-img elevation-2 img-fluid img-circle" src="https://gravatar.com/avatar/{{md5($absence->requester->email)}}" alt="User profile picture">
|
||||
@else
|
||||
<img class="profile-user-img elevation-2 img-fluid img-circle" src="https://crafatar.com/avatars/{{$absence->requester->uuid}}" alt="User profile picture">
|
||||
@endif
|
||||
|
||||
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
@foreach ($absence->requester->roles as $role)
|
||||
<span class="badge badge-secondary mr-2">{{ucfirst($role->name)}}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col text-center">
|
||||
<x-button type="submit" id="backToAbsences" color="secondary" icon="fas fa-angle-double-left" link="{{ route('absences.index') }}">
|
||||
{{ __('Back to Absence list') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@stop
|
||||
|
||||
@section('footer')
|
||||
@include('breadcrumbs.dashboard.footer')
|
||||
@stop
|
@ -47,23 +47,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-4 offset-md-4">
|
||||
|
||||
<div class="card">
|
||||
|
||||
<div class="card-header">
|
||||
<div class="card-title"><h4><i class="fas fa-search"></i>{{__('Search users')}}</h4></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
|
@ -35,6 +35,7 @@
|
||||
<h3 class="widget-user-username">{{ $user->name }}</h3>
|
||||
<h5 class="widget-user-desc">{{ $user->profile->profileShortBio }}</h5>
|
||||
</div>
|
||||
|
||||
<div class="widget-user-image">
|
||||
@if($user->profile->avatarPreference == 'gravatar')
|
||||
<img class="profile-user-img elevation-2 img-fluid img-circle" src="https://gravatar.com/avatar/{{md5($user->email)}}" alt="User profile picture">
|
||||
|
@ -18,6 +18,8 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use App\Http\Controllers\AbsenceController;
|
||||
use App\Http\Controllers\ApplicationController;
|
||||
use App\Http\Controllers\AppointmentController;
|
||||
use App\Http\Controllers\Auth\LoginController;
|
||||
@ -245,7 +247,26 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo
|
||||
Route::patch('staff-members/terminate/{user}', [UserController::class, 'terminate'])
|
||||
->name('terminateStaffMember');
|
||||
|
||||
Route::resource('absences', \App\Http\Controllers\AbsenceController::class);
|
||||
|
||||
|
||||
Route::resource('absences', AbsenceController::class);
|
||||
|
||||
Route::controller(AbsenceController::class)->group(function () {
|
||||
|
||||
Route::get('my-absences', 'showUserAbsences')
|
||||
->name('showUserAbsences');
|
||||
|
||||
Route::patch('absences/{absence}/approve', 'approveAbsence')
|
||||
->name('approveAbsence');
|
||||
|
||||
Route::patch('absences/{absence}/decline', 'declineAbsence')
|
||||
->name('declineAbsence');
|
||||
|
||||
Route::patch('absences/{absence}/cancel', 'cancelAbsence')
|
||||
->name('cancelAbsence');
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'admin', 'middleware' => ['passwordredirect']], function () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user