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:
2022-02-24 00:56:46 +00:00
parent f61a287c78
commit e567094f40
16 changed files with 607 additions and 63 deletions

View 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

View File

@@ -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

View File

@@ -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

View 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

View 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 }} &mdash; {{ $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

View File

@@ -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">

View File

@@ -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">