mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-31 11:14:37 -04:00
Merge pull request #5691 from beganovich/v5-1204-tasks
(v5) Client portal: Tasks
This commit is contained in:
commit
9dc418ec5b
34
app/Http/Controllers/ClientPortal/TaskController.php
Normal file
34
app/Http/Controllers/ClientPortal/TaskController.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\ClientPortal;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\ClientPortal\Tasks\ShowTasksRequest;
|
||||||
|
|
||||||
|
class TaskController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Show the tasks in the client portal.
|
||||||
|
*
|
||||||
|
* @param ShowTasksRequest $request
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function index(ShowTasksRequest $request)
|
||||||
|
{
|
||||||
|
\Carbon\Carbon::setLocale(
|
||||||
|
auth('contact')->user()->preferredLocale()
|
||||||
|
);
|
||||||
|
|
||||||
|
return render('tasks.index');
|
||||||
|
}
|
||||||
|
}
|
39
app/Http/Livewire/TasksTable.php
Normal file
39
app/Http/Livewire/TasksTable.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Task;
|
||||||
|
use App\Utils\Traits\WithSorting;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
class TasksTable extends Component
|
||||||
|
{
|
||||||
|
use WithSorting;
|
||||||
|
use WithPagination;
|
||||||
|
|
||||||
|
public $per_page = 10;
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$query = Task::query()
|
||||||
|
->where('client_id', auth('contact')->user()->client->id)
|
||||||
|
->whereNotNull('invoice_id')
|
||||||
|
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||||
|
->paginate($this->per_page);
|
||||||
|
|
||||||
|
return render('components.livewire.tasks-table', [
|
||||||
|
'tasks' => $query,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
30
app/Http/Requests/ClientPortal/Tasks/ShowTasksRequest.php
Normal file
30
app/Http/Requests/ClientPortal/Tasks/ShowTasksRequest.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\ClientPortal\Tasks;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ShowTasksRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return (bool)auth()->user('contact')->client->getSetting('enable_client_portal_tasks');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -82,9 +82,7 @@ class PortalComposer
|
|||||||
$data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar'];
|
$data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar'];
|
||||||
|
|
||||||
if (auth()->user('contact')->client->getSetting('enable_client_portal_tasks')) {
|
if (auth()->user('contact')->client->getSetting('enable_client_portal_tasks')) {
|
||||||
$data[] = ['title' => ctrans('texts.tasks'), 'url' => 'client.dashboard', 'icon' => 'clock'];
|
$data[] = ['title' => ctrans('texts.tasks'), 'url' => 'client.tasks.index', 'icon' => 'clock'];
|
||||||
|
|
||||||
// TODO: Update when 'tasks' module is available in client portal.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="mr-2 text-sm hidden md:block">{{ ctrans('texts.per_page') }}</span>
|
||||||
|
<select wire:model="per_page" class="form-select py-1 text-sm">
|
||||||
|
<option>5</option>
|
||||||
|
<option selected>10</option>
|
||||||
|
<option>15</option>
|
||||||
|
<option>20</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||||
|
<div class="align-middle inline-block min-w-full overflow-hidden rounded">
|
||||||
|
<table class="min-w-full shadow rounded border border-gray-200 mt-4 credits-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||||
|
<span role="button" wire:click="sortBy('description')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.description') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<span role="button" wire:click="sortBy('status_id')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.status') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<span role="button" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.duration') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($tasks as $task)
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ \Illuminate\Support\Str::limit($task->description, 80) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ optional($task->status)->name }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ \Carbon\CarbonInterval::seconds($task->calcDuration())->cascade()->forHumans() }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500" colspan="100%">
|
||||||
|
{{ ctrans('texts.no_results') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center md:justify-between mt-6 mb-6">
|
||||||
|
@if($tasks->total() > 0)
|
||||||
|
<span class="text-gray-700 text-sm hidden md:block">
|
||||||
|
{{ ctrans('texts.showing_x_of', ['first' => $tasks->firstItem(), 'last' => $tasks->lastItem(), 'total' => $tasks->total()]) }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
{{ $tasks->links('portal/ninja2020/vendor/pagination') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
8
resources/views/portal/ninja2020/tasks/index.blade.php
Normal file
8
resources/views/portal/ninja2020/tasks/index.blade.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.tasks'))
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="flex flex-col">
|
||||||
|
@livewire('tasks-table')
|
||||||
|
</div>
|
||||||
|
@endsection
|
@ -54,7 +54,7 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence
|
|||||||
Route::get('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@verify')->name('payment_methods.verification');
|
Route::get('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@verify')->name('payment_methods.verification');
|
||||||
Route::post('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@processVerification');
|
Route::post('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@processVerification');
|
||||||
|
|
||||||
Route::resource('payment_methods', 'ClientPortal\PaymentMethodController')->except(['edit', 'update']);
|
Route::resource('payment_methods', 'ClientPortal\PaymentMethodController')->except(['edit', 'update']);
|
||||||
|
|
||||||
Route::match(['GET', 'POST'], 'quotes/approve', 'ClientPortal\QuoteController@bulk')->name('quotes.bulk');
|
Route::match(['GET', 'POST'], 'quotes/approve', 'ClientPortal\QuoteController@bulk')->name('quotes.bulk');
|
||||||
Route::get('quotes', 'ClientPortal\QuoteController@index')->name('quotes.index')->middleware('portal_enabled');
|
Route::get('quotes', 'ClientPortal\QuoteController@index')->name('quotes.index')->middleware('portal_enabled');
|
||||||
@ -76,6 +76,8 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence
|
|||||||
|
|
||||||
Route::resource('subscriptions', 'ClientPortal\SubscriptionController')->middleware('portal_enabled')->only(['index']);
|
Route::resource('subscriptions', 'ClientPortal\SubscriptionController')->middleware('portal_enabled')->only(['index']);
|
||||||
|
|
||||||
|
Route::resource('tasks', 'ClientPortal\TaskController')->only(['index']);
|
||||||
|
|
||||||
Route::post('upload', 'ClientPortal\UploadController')->name('upload.store');
|
Route::post('upload', 'ClientPortal\UploadController')->name('upload.store');
|
||||||
Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout');
|
Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout');
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user