32 changed files with 9063 additions and 7448 deletions
-
5app/Entities/Projects/Feature.php
-
26app/Entities/Users/Event.php
-
2app/Entities/Users/User.php
-
7app/Exceptions/Handler.php
-
171app/Http/Controllers/Api/EventsController.php
-
2app/Http/Controllers/Projects/ProjectsController.php
-
3app/Http/routes.php
-
29app/Http/routes/calendar.php
-
22app/Policies/EventPolicy.php
-
2app/Providers/AuthServiceProvider.php
-
3composer.json
-
591composer.lock
-
5config/app.php
-
17database/factories/ModelFactory.php
-
7316public/assets/css/app.css
-
1public/assets/css/app.css.map
-
7376public/assets/css/app.s.css
-
1public/assets/css/app.s.css.map
-
5public/assets/css/bootstrap-theme.min.css
-
5public/assets/css/bootstrap.min.css
-
4public/assets/css/font-awesome.min.css
-
353public/assets/css/sb-admin-2.css
-
10public/assets/js/bootstrap.min.js
-
2resources/assets/sass/_bootstrap.scss
-
1resources/views/features/partials/feature-show.blade.php
-
5resources/views/layouts/app.blade.php
-
1resources/views/layouts/partials/sidebar.blade.php
-
14resources/views/projects/features-export-excel.blade.php
-
71resources/views/projects/features-export-progress-excel.blade.php
-
1resources/views/projects/index.blade.php
-
313resources/views/users/calendar.blade.php
-
147tests/api/ApiEventsTest.php
@ -0,0 +1,26 @@ |
|||
<?php |
|||
|
|||
namespace App\Entities\Users; |
|||
|
|||
use App\Entities\Findings\Finding; |
|||
use App\Entities\Projects\Project; |
|||
use App\Entities\Users\User; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
use Laracasts\Presenter\PresentableTrait; |
|||
|
|||
class Event extends Model |
|||
{ |
|||
protected $table = 'user_events'; |
|||
protected $guarded = ['id','created_at','updated_at']; |
|||
// protected $casts = ['is_allday' => 'boolean'];
|
|||
|
|||
public function user() |
|||
{ |
|||
return $this->belongsTo(User::class); |
|||
} |
|||
|
|||
public function project() |
|||
{ |
|||
return $this->belongsTo(Project::class); |
|||
} |
|||
} |
|||
@ -0,0 +1,171 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers\Api; |
|||
|
|||
use App\Entities\Users\Event; |
|||
use App\Http\Controllers\Controller; |
|||
use Illuminate\Http\Request; |
|||
|
|||
class EventsController extends Controller |
|||
{ |
|||
public function index(Request $request) |
|||
{ |
|||
$start = $request->get('start'); |
|||
$end = $request->get('end'); |
|||
$events = Event::where(function($query) use ($start, $end) { |
|||
if ($start && $end) |
|||
$query->whereBetween('start', [$start, $end]); |
|||
})->with('user')->get(); |
|||
|
|||
$response = fractal() |
|||
->collection($events) |
|||
->transformWith(function($event) { |
|||
$isOwnEvent = $event->user_id == auth()->id(); |
|||
return [ |
|||
'id' => $event->id, |
|||
'user' => $event->user->name, |
|||
'user_id' => $event->user_id, |
|||
'title' => $event->title, |
|||
'body' => $event->body, |
|||
'start' => $event->start, |
|||
'end' => $event->end, |
|||
'allDay' => $event->is_allday, |
|||
'editable' => true, |
|||
'color' => $isOwnEvent ? '' : '#B7B7B7', |
|||
]; |
|||
}) |
|||
->toArray(); |
|||
|
|||
return response()->json($response['data'], 200); |
|||
} |
|||
|
|||
public function store(Request $request) |
|||
{ |
|||
$this->validate($request, [ |
|||
'title' => 'required|string|max:60', |
|||
'body' => 'string|max:255', |
|||
'start' => 'required|date|date_format:Y-m-d H:i:s', |
|||
'end' => 'date|date_format:Y-m-d H:i:s', |
|||
'is_allday' => '', |
|||
]); |
|||
|
|||
$event = new Event; |
|||
$event->user_id = auth()->id(); |
|||
$event->title = $request->get('title'); |
|||
$event->body = $request->get('body'); |
|||
$event->start = $request->get('start'); |
|||
$event->end = $request->get('is_allday') == "true" ? null : $request->get('end'); |
|||
$event->is_allday = $request->get('is_allday') == "true" ? 1 : 0; |
|||
|
|||
$event->save(); |
|||
|
|||
$response = [ |
|||
'message' => trans('event.created') |
|||
] + fractal()->item($event) |
|||
->transformWith(function($event) { |
|||
return [ |
|||
'id' => $event->id, |
|||
'user' => $event->user->name, |
|||
'title' => $event->title, |
|||
'body' => $event->body, |
|||
'start' => $event->start, |
|||
'end' => $event->end, |
|||
'allDay' => $event->is_allday, |
|||
]; |
|||
}) |
|||
->toArray(); |
|||
|
|||
return response()->json($response, 201); |
|||
} |
|||
|
|||
public function show($id) |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
public function update(Request $request) |
|||
{ |
|||
$this->validate($request, [ |
|||
'id' => 'required|numeric|exists:user_events,id', |
|||
'title' => 'required|string|max:60', |
|||
'body' => 'string|max:255', |
|||
]); |
|||
|
|||
$event = Event::findOrFail($request->get('id')); |
|||
$this->authorize('update', $event); |
|||
|
|||
$event->title = $request->get('title'); |
|||
$event->body = $request->get('body'); |
|||
|
|||
$event->save(); |
|||
|
|||
$response = [ |
|||
'message' => trans('event.updated') |
|||
] + fractal()->item($event) |
|||
->transformWith(function($event) { |
|||
return [ |
|||
'id' => $event->id, |
|||
'user' => $event->user->name, |
|||
'title' => $event->title, |
|||
'body' => $event->body, |
|||
'start' => $event->start, |
|||
'end' => $event->end, |
|||
'allDay' => $event->is_allday, |
|||
]; |
|||
}) |
|||
->toArray(); |
|||
|
|||
return response()->json($response, 200); |
|||
} |
|||
|
|||
public function destroy(Request $request) |
|||
{ |
|||
$this->validate($request, [ |
|||
'id' => 'required|numeric|exists:user_events,id' |
|||
]); |
|||
|
|||
$event = Event::findOrFail($request->get('id')); |
|||
$this->authorize('delete', $event); |
|||
$event->delete(); |
|||
|
|||
return response()->json(['message' => trans('event.deleted')], 200); |
|||
} |
|||
|
|||
public function reschedule(Request $request) |
|||
{ |
|||
$this->validate($request, [ |
|||
'id' => 'required|numeric|exists:user_events,id', |
|||
'start' => 'required|date|date_format:Y-m-d H:i:s', |
|||
'end' => 'date|date_format:Y-m-d H:i:s', |
|||
]); |
|||
|
|||
$event = Event::findOrFail($request->get('id')); |
|||
$this->authorize('update', $event); |
|||
$event->start = $request->get('start'); |
|||
|
|||
if ($request->has('end')) { |
|||
$event->end = $request->get('end'); |
|||
$event->is_allday = false; |
|||
} |
|||
|
|||
$event->save(); |
|||
|
|||
$response = [ |
|||
'message' => trans('event.rescheduled') |
|||
] + fractal()->item($event) |
|||
->transformWith(function($event) { |
|||
return [ |
|||
'id' => $event->id, |
|||
'user' => $event->user->name, |
|||
'title' => $event->title, |
|||
'body' => $event->body, |
|||
'start' => $event->start, |
|||
'end' => $event->end, |
|||
'allDay' => $event->is_allday, |
|||
]; |
|||
}) |
|||
->toArray(); |
|||
|
|||
return response()->json($response, 200); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
Route::group(['middleware' => ['web','auth'], 'namespace' => 'Api'], function() { |
|||
/** |
|||
* Savety Calendar |
|||
*/ |
|||
Route::get('my-calendar', ['as' => 'users.calendar', 'uses' => function() { |
|||
return view('users.calendar'); |
|||
}]); |
|||
/** |
|||
* Savety Calendar |
|||
*/ |
|||
Route::get('get-events', ['as' => 'api.events.index', 'uses' => 'EventsController@index']); |
|||
Route::post('events', ['as' => 'api.events.store', 'uses' => 'EventsController@store']); |
|||
Route::patch('events/update', ['as' => 'api.events.update', 'uses' => 'EventsController@update']); |
|||
Route::patch('events/reschedule', ['as' => 'api.events.reschedule', 'uses' => 'EventsController@reschedule']); |
|||
Route::delete('events/delete', ['as' => 'api.events.destroy', 'uses' => 'EventsController@destroy']); |
|||
}); |
|||
|
|||
// Route::group(['middleware' => ['api','auth:api'], 'namespace' => 'Api'], function() {
|
|||
// /**
|
|||
// * Savety Calendar
|
|||
// */
|
|||
// Route::get('get-events', ['as' => 'api.events.index', 'uses' => 'EventsController@index']);
|
|||
// Route::post('events', ['as' => 'api.events.store', 'uses' => 'EventsController@store']);
|
|||
// Route::patch('events/update', ['as' => 'api.events.update', 'uses' => 'EventsController@update']);
|
|||
// Route::patch('events/reschedule', ['as' => 'api.events.reschedule', 'uses' => 'EventsController@reschedule']);
|
|||
// Route::delete('events/delete', ['as' => 'api.events.destroy', 'uses' => 'EventsController@destroy']);
|
|||
// });
|
|||
@ -0,0 +1,22 @@ |
|||
<?php |
|||
|
|||
namespace App\Policies; |
|||
|
|||
use App\Entities\Users\Event; |
|||
use App\Entities\Users\User; |
|||
use Illuminate\Auth\Access\HandlesAuthorization; |
|||
|
|||
class EventPolicy |
|||
{ |
|||
use HandlesAuthorization; |
|||
|
|||
public function update(User $user, Event $event) |
|||
{ |
|||
return $user->id == $event->user_id; |
|||
} |
|||
|
|||
public function delete(User $user, Event $event) |
|||
{ |
|||
return $user->id == $event->user_id; |
|||
} |
|||
} |
|||
591
composer.lock
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
7316
public/assets/css/app.css
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1
public/assets/css/app.css.map
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
7376
public/assets/css/app.s.css
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1
public/assets/css/app.s.css.map
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
5
public/assets/css/bootstrap-theme.min.css
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
5
public/assets/css/bootstrap.min.css
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
4
public/assets/css/font-awesome.min.css
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,353 @@ |
|||
/*! |
|||
* Start Bootstrap - SB Admin 2 Bootstrap Admin Theme (http://startbootstrap.com) |
|||
* Code licensed under the Apache License v2.0. |
|||
* For details, see http://www.apache.org/licenses/LICENSE-2.0. |
|||
*/ |
|||
|
|||
body { |
|||
background-color: #f8f8f8; |
|||
} |
|||
|
|||
#wrapper { |
|||
width: 100%; |
|||
} |
|||
|
|||
#page-wrapper { |
|||
padding: 0 15px; |
|||
min-height: 568px; |
|||
background-color: #fff; |
|||
} |
|||
|
|||
@media(min-width:768px) { |
|||
#page-wrapper { |
|||
position: inherit; |
|||
margin: 0 0 0 250px; |
|||
padding: 0 30px; |
|||
border-left: 1px solid #e7e7e7; |
|||
} |
|||
} |
|||
|
|||
.navbar-top-links { |
|||
margin-right: 0; |
|||
} |
|||
|
|||
.navbar-top-links li { |
|||
display: inline-block; |
|||
} |
|||
|
|||
.navbar-top-links li:last-child { |
|||
margin-right: 15px; |
|||
} |
|||
|
|||
.navbar-top-links li a { |
|||
padding: 15px; |
|||
min-height: 50px; |
|||
} |
|||
|
|||
.navbar-top-links .dropdown-menu li { |
|||
display: block; |
|||
} |
|||
|
|||
.navbar-top-links .dropdown-menu li:last-child { |
|||
margin-right: 0; |
|||
} |
|||
|
|||
.navbar-top-links .dropdown-menu li a { |
|||
padding: 3px 20px; |
|||
min-height: 0; |
|||
} |
|||
|
|||
.navbar-top-links .dropdown-menu li a div { |
|||
white-space: normal; |
|||
} |
|||
|
|||
.navbar-top-links .dropdown-messages, |
|||
.navbar-top-links .dropdown-tasks, |
|||
.navbar-top-links .dropdown-alerts { |
|||
width: 310px; |
|||
min-width: 0; |
|||
} |
|||
|
|||
.navbar-top-links .dropdown-messages { |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.navbar-top-links .dropdown-tasks { |
|||
margin-left: -59px; |
|||
} |
|||
|
|||
.navbar-top-links .dropdown-alerts { |
|||
margin-left: -123px; |
|||
} |
|||
|
|||
.navbar-top-links .dropdown-user { |
|||
right: 0; |
|||
left: auto; |
|||
} |
|||
|
|||
.sidebar .sidebar-nav.navbar-collapse { |
|||
padding-right: 0; |
|||
padding-left: 0; |
|||
} |
|||
|
|||
.sidebar .sidebar-search { |
|||
padding: 15px; |
|||
} |
|||
|
|||
.sidebar ul li { |
|||
border-bottom: 1px solid #e7e7e7; |
|||
} |
|||
|
|||
.sidebar ul li a.active { |
|||
background-color: #eee; |
|||
} |
|||
|
|||
.sidebar .arrow { |
|||
float: right; |
|||
} |
|||
|
|||
.sidebar .fa.arrow:before { |
|||
content: "\f104"; |
|||
} |
|||
|
|||
.sidebar .active>a>.fa.arrow:before { |
|||
content: "\f107"; |
|||
} |
|||
|
|||
.sidebar .nav-second-level li, |
|||
.sidebar .nav-third-level li { |
|||
border-bottom: 0!important; |
|||
} |
|||
|
|||
.sidebar .nav-second-level li a { |
|||
padding-left: 37px; |
|||
} |
|||
|
|||
.sidebar .nav-third-level li a { |
|||
padding-left: 52px; |
|||
} |
|||
|
|||
@media(min-width:768px) { |
|||
.sidebar { |
|||
z-index: 1; |
|||
position: absolute; |
|||
width: 250px; |
|||
margin-top: 101px; |
|||
} |
|||
|
|||
.navbar-top-links .dropdown-messages, |
|||
.navbar-top-links .dropdown-tasks, |
|||
.navbar-top-links .dropdown-alerts { |
|||
margin-left: auto; |
|||
} |
|||
} |
|||
|
|||
.btn-outline { |
|||
color: inherit; |
|||
background-color: transparent; |
|||
transition: all .5s; |
|||
} |
|||
|
|||
.btn-primary.btn-outline { |
|||
color: #428bca; |
|||
} |
|||
|
|||
.btn-success.btn-outline { |
|||
color: #5cb85c; |
|||
} |
|||
|
|||
.btn-info.btn-outline { |
|||
color: #5bc0de; |
|||
} |
|||
|
|||
.btn-warning.btn-outline { |
|||
color: #f0ad4e; |
|||
} |
|||
|
|||
.btn-danger.btn-outline { |
|||
color: #d9534f; |
|||
} |
|||
|
|||
.btn-primary.btn-outline:hover, |
|||
.btn-success.btn-outline:hover, |
|||
.btn-info.btn-outline:hover, |
|||
.btn-warning.btn-outline:hover, |
|||
.btn-danger.btn-outline:hover { |
|||
color: #fff; |
|||
} |
|||
|
|||
.chat { |
|||
margin: 0; |
|||
padding: 0; |
|||
list-style: none; |
|||
} |
|||
|
|||
.chat li { |
|||
margin-bottom: 10px; |
|||
padding-bottom: 5px; |
|||
border-bottom: 1px dotted #999; |
|||
} |
|||
|
|||
.chat li.left .chat-body { |
|||
margin-left: 60px; |
|||
} |
|||
|
|||
.chat li.right .chat-body { |
|||
margin-right: 60px; |
|||
} |
|||
|
|||
.chat li .chat-body p { |
|||
margin: 0; |
|||
} |
|||
|
|||
.panel .slidedown .glyphicon, |
|||
.chat .glyphicon { |
|||
margin-right: 5px; |
|||
} |
|||
|
|||
.chat-panel .panel-body { |
|||
height: 350px; |
|||
overflow-y: scroll; |
|||
} |
|||
|
|||
.flot-chart { |
|||
display: block; |
|||
height: 400px; |
|||
} |
|||
|
|||
.flot-chart-content { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
table.dataTable thead .sorting, |
|||
table.dataTable thead .sorting_asc, |
|||
table.dataTable thead .sorting_desc, |
|||
table.dataTable thead .sorting_asc_disabled, |
|||
table.dataTable thead .sorting_desc_disabled { |
|||
background: 0 0; |
|||
} |
|||
|
|||
table.dataTable thead .sorting_asc:after { |
|||
content: "\f0de"; |
|||
float: right; |
|||
font-family: fontawesome; |
|||
} |
|||
|
|||
table.dataTable thead .sorting_desc:after { |
|||
content: "\f0dd"; |
|||
float: right; |
|||
font-family: fontawesome; |
|||
} |
|||
|
|||
table.dataTable thead .sorting:after { |
|||
content: "\f0dc"; |
|||
float: right; |
|||
font-family: fontawesome; |
|||
color: rgba(50,50,50,.5); |
|||
} |
|||
|
|||
.btn-circle { |
|||
width: 30px; |
|||
height: 30px; |
|||
padding: 6px 0; |
|||
border-radius: 15px; |
|||
text-align: center; |
|||
font-size: 12px; |
|||
line-height: 1.428571429; |
|||
} |
|||
|
|||
.btn-circle.btn-lg { |
|||
width: 50px; |
|||
height: 50px; |
|||
padding: 10px 16px; |
|||
border-radius: 25px; |
|||
font-size: 18px; |
|||
line-height: 1.33; |
|||
} |
|||
|
|||
.btn-circle.btn-xl { |
|||
width: 70px; |
|||
height: 70px; |
|||
padding: 10px 16px; |
|||
border-radius: 35px; |
|||
font-size: 24px; |
|||
line-height: 1.33; |
|||
} |
|||
|
|||
.show-grid [class^=col-] { |
|||
padding-top: 10px; |
|||
padding-bottom: 10px; |
|||
border: 1px solid #ddd; |
|||
background-color: #eee!important; |
|||
} |
|||
|
|||
.show-grid { |
|||
margin: 15px 0; |
|||
} |
|||
|
|||
.huge { |
|||
font-size: 40px; |
|||
} |
|||
|
|||
.panel-green { |
|||
border-color: #5cb85c; |
|||
} |
|||
|
|||
.panel-green .panel-heading { |
|||
border-color: #5cb85c; |
|||
color: #fff; |
|||
background-color: #5cb85c; |
|||
} |
|||
|
|||
.panel-green a { |
|||
color: #5cb85c; |
|||
} |
|||
|
|||
.panel-green a:hover { |
|||
color: #3d8b3d; |
|||
} |
|||
|
|||
.panel-red { |
|||
border-color: #d9534f; |
|||
} |
|||
|
|||
.panel-red .panel-heading { |
|||
border-color: #d9534f; |
|||
color: #fff; |
|||
background-color: #d9534f; |
|||
} |
|||
|
|||
.panel-red a { |
|||
color: #d9534f; |
|||
} |
|||
|
|||
.panel-red a:hover { |
|||
color: #b52b27; |
|||
} |
|||
|
|||
.panel-yellow { |
|||
border-color: #f0ad4e; |
|||
} |
|||
|
|||
.panel-yellow .panel-heading { |
|||
border-color: #f0ad4e; |
|||
color: #fff; |
|||
background-color: #f0ad4e; |
|||
} |
|||
|
|||
.panel-yellow a { |
|||
color: #f0ad4e; |
|||
} |
|||
|
|||
.panel-yellow a:hover { |
|||
color: #df8a13; |
|||
} |
|||
|
|||
.chosen-container-multi .chosen-choices li.search-field input[type=text] { |
|||
height: 25px; |
|||
} |
|||
|
|||
.button-form-inline { |
|||
display: inline; |
|||
} |
|||
10
public/assets/js/bootstrap.min.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,71 @@ |
|||
<?php |
|||
// $filename = str_slug(trans('project.features') . '-' . $project->name) . '.xls';
|
|||
// header("Content-Disposition: attachment; filename=\"$filename\"");
|
|||
// header("Content-Type: application/vnd.ms-excel");
|
|||
?>
|
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
{{-- <meta http-equiv="X-UA-Compatible" content="IE=edge"> --}} |
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|||
<title>{{ $project->name }}</title> |
|||
<style> |
|||
th, td { |
|||
padding: 5px; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<table border="1" class="table table-condensed table-striped"> |
|||
<thead> |
|||
<tr> |
|||
<td colspan="4" style="text-align:center"> |
|||
<h1>{{ trans('project.features') }} {{ $project->name }}</h1> |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
<th>{{ trans('app.table_no') }}</th> |
|||
<th>{{ trans('feature.name') }}</th> |
|||
<th class="text-center">{{ trans('feature.progress') }}</th> |
|||
{{-- <th class="text-right">{{ trans('feature.price') }}</th> --}} |
|||
<th>{{ trans('app.description') }}</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody id="sort-features"> |
|||
@forelse($features as $key => $feature) |
|||
<tr> |
|||
<td>{{ 1 + $key }}</td> |
|||
<td> |
|||
{{ $feature->name }} |
|||
</td> |
|||
<td class="text-center">{{ $feature->progress = $feature->tasks->avg('progress')/100 }}</td> |
|||
{{-- <td class="text-right">{{ $feature->price }}</td> --}} |
|||
<td style="wrap-text: true;">{!! nl2br($feature->description) !!}</td> |
|||
</tr> |
|||
|
|||
@if ($feature->tasks->count()) |
|||
@foreach($feature->tasks as $task) |
|||
<tr> |
|||
<td></td> |
|||
<td>{{ $task->name }}</td> |
|||
<td></td> |
|||
<td style="wrap-text: true;">{!! nl2br($task->description) !!}</td> |
|||
</tr> |
|||
@endforeach |
|||
@endif |
|||
@empty |
|||
<tr><td colspan="7">{{ trans('feature.empty') }}</td></tr> |
|||
@endforelse |
|||
</tbody> |
|||
<tfoot> |
|||
<tr> |
|||
<th class="text-right" colspan="2">Total</th> |
|||
<th class="text-center">{{ $project->getFeatureOveralProgress() }} %</th> |
|||
{{-- <th class="text-right">{{ $features->sum('price') }}</th> --}} |
|||
<th></th> |
|||
</tr> |
|||
</tfoot> |
|||
</table> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,313 @@ |
|||
@extends('layouts.app') |
|||
|
|||
@section('title', 'User Calendar') |
|||
|
|||
@section('content') |
|||
|
|||
<div class=""> |
|||
<div class="row"> |
|||
<div class="col-md-12"> |
|||
<div class="x_panel"> |
|||
<div class="x_title"><h3>User Calendar <small>Click to add/edit events</small></h3></div> |
|||
<div class="x_content"> |
|||
<div id='calendar'></div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
|
|||
<!-- calendar modal --> |
|||
<div id="CalenderModalNew" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> |
|||
<div class="modal-dialog"> |
|||
<div class="modal-content"> |
|||
|
|||
<div class="modal-header"> |
|||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> |
|||
<h4 class="modal-title" id="myModalLabel">New Calendar Entry</h4> |
|||
</div> |
|||
<div class="modal-body"> |
|||
<div id="testmodal" style="padding: 5px 20px;"> |
|||
<form id="antoform" class="form-horizontal calender" role="form"> |
|||
<div class="form-group"> |
|||
<label class="col-sm-3 control-label">User</label> |
|||
<div class="col-sm-9"> |
|||
<span class="form-control" disabled>{{ auth()->user()->name }}</span> |
|||
</div> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-sm-3 control-label">Title</label> |
|||
<div class="col-sm-9"> |
|||
<input type="text" class="form-control" id="title" name="title"> |
|||
</div> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-sm-3 control-label">Description</label> |
|||
<div class="col-sm-9"> |
|||
<textarea class="form-control" style="height:55px;" id="descr" name="descr"></textarea> |
|||
</div> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-sm-3 control-label"></label> |
|||
<div class="col-sm-9"> |
|||
<label for="is_allday"><input id="is_allday" type="checkbox" name="is_allday"> All Day Event?</label> |
|||
</div> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
<div class="modal-footer"> |
|||
<button type="button" class="btn btn-default antoclose" data-dismiss="modal">Close</button> |
|||
<button type="button" class="btn btn-primary antosubmit">Save</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div id="CalenderModalEdit" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> |
|||
<div class="modal-dialog"> |
|||
<div class="modal-content"> |
|||
|
|||
<div class="modal-header"> |
|||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> |
|||
<h4 class="modal-title" id="myModalLabel2">Edit Calendar Entry</h4> |
|||
</div> |
|||
<form id="antoform2" class="form-horizontal calender" role="form"> |
|||
<div class="modal-body"> |
|||
<div id="testmodal2" style="padding: 5px 20px;"> |
|||
<input type="hidden" name="id2" id="id2"> |
|||
<div class="form-group"> |
|||
<label class="col-sm-3 control-label">User</label> |
|||
<div class="col-sm-9"> |
|||
<span class="form-control" disabled id="user2"></span> |
|||
</div> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-sm-3 control-label">Title</label> |
|||
<div class="col-sm-9"> |
|||
<input type="text" class="form-control" id="title2" name="title2"> |
|||
</div> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label class="col-sm-3 control-label">Description</label> |
|||
<div class="col-sm-9"> |
|||
<textarea class="form-control" style="height:55px;" id="descr2" name="descr2"></textarea> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
</div> |
|||
<div class="modal-footer"> |
|||
<button type="button" class="btn btn-danger pull-left antodelete2" title="Delete Event"><i class="fa fa-times"></i></button> |
|||
<button type="button" class="btn btn-default antoclose2" data-dismiss="modal">Close</button> |
|||
<button type="submit" class="btn btn-primary antosubmit2">Save changes</button> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div id="fc_create" data-toggle="modal" data-target="#CalenderModalNew"></div> |
|||
<div id="fc_edit" data-toggle="modal" data-target="#CalenderModalEdit"></div> |
|||
<!-- /calendar modal --> |
|||
|
|||
@endsection |
|||
|
|||
@section('ext_css') |
|||
{!! Html::style(url('assets/css/plugins/fullcalendar.min.css')) !!} |
|||
@endsection |
|||
|
|||
@section('ext_js') |
|||
{!! Html::script(url('assets/js/plugins/moment.min.js')) !!} |
|||
{!! Html::script(url('assets/js/plugins/fullcalendar.min.js')) !!} |
|||
@endsection |
|||
|
|||
@section('script') |
|||
<script> |
|||
(function() { |
|||
var date = new Date(), |
|||
d = date.getDate(), |
|||
m = date.getMonth(), |
|||
y = date.getFullYear(), |
|||
started, |
|||
categoryClass; |
|||
|
|||
var calendar = $('#calendar').fullCalendar({ |
|||
header: { |
|||
left: 'prev,next today', |
|||
center: 'title', |
|||
right: 'month,agendaWeek,agendaDay' |
|||
}, |
|||
height: 550, |
|||
selectable: true, |
|||
selectHelper: true, |
|||
droppable: false, |
|||
editable: false, |
|||
// eventLimit: true,
|
|||
slotLabelFormat: 'HH:mm', |
|||
slotDuration: '01:00:00', |
|||
events: { |
|||
url: "{{ route('api.events.index') }}", |
|||
type: "GET", |
|||
error: function() { |
|||
alert('there was an error while fetching events!'); |
|||
} |
|||
}, |
|||
eventRender: function(calEvent, element) { |
|||
// element.find('.fc-content').prepend('<strong style="display:block">' + calEvent.user + '</strong>');
|
|||
}, |
|||
select: function(start, end, allDay) { |
|||
$('#fc_create').click(); |
|||
|
|||
started = start; |
|||
ended = end; |
|||
|
|||
$(".antosubmit").on("click", function() { |
|||
var title = $("#title").val(); |
|||
var body = $("#descr").val(); |
|||
var is_allday = $("#is_allday").is(':checked'); |
|||
|
|||
if (title) { |
|||
$.ajax({ |
|||
url: "{{ route('api.events.store') }}", |
|||
method: "POST", |
|||
data: { title: title, body: body, start: started.format("YYYY-MM-DD HH:mm:ss"), end: ended.format("YYYY-MM-DD HH:mm:ss"), is_allday: is_allday }, |
|||
success: function(response){ |
|||
if(response.message == 'event.created') { |
|||
calendar.fullCalendar('renderEvent', { |
|||
id: response.data.id, |
|||
title: title, |
|||
body: body, |
|||
start: started.format("YYYY-MM-DD HH:mm"), |
|||
end: ended.format("YYYY-MM-DD HH:mm"), |
|||
user: "{{ auth()->user()->name }}", |
|||
user_id: "{{ auth()->id() }}", |
|||
allDay: is_allday, |
|||
editable: true |
|||
}, true); |
|||
} |
|||
}, |
|||
error: function(e){ |
|||
alert('Error processing your request: '+e.responseText); |
|||
} |
|||
}); |
|||
|
|||
} |
|||
|
|||
$('#title').val(''); |
|||
$('#descr').val(''); |
|||
|
|||
calendar.fullCalendar('unselect'); |
|||
|
|||
$('.antoclose').click(); |
|||
|
|||
return false; |
|||
}); |
|||
}, |
|||
eventClick: function(calEvent, jsEvent, view) { |
|||
|
|||
if (calEvent.editable) { |
|||
$('#user2').text(calEvent.user); |
|||
$('#title2').val(calEvent.title); |
|||
$('#descr2').val(calEvent.body); |
|||
$('#CalenderModalEdit').modal(); |
|||
} |
|||
else { |
|||
$('#user3').text(calEvent.user); |
|||
$('#title3').html(calEvent.title); |
|||
$('#descr3').html(calEvent.body); |
|||
$('#CalenderModalView').modal(); |
|||
} |
|||
|
|||
$(".antodelete2").off("click").on("click", function() { |
|||
var confirmBox = confirm('Delete this event?'); |
|||
|
|||
if (confirmBox) { |
|||
$.ajax({ |
|||
url: "{{ route('api.events.destroy') }}", |
|||
method: "DELETE", |
|||
beforeSend: function(xhr){ |
|||
xhr.setRequestHeader('Authorization', 'Bearer ' + "{{ auth()->user()->api_token }}"); |
|||
}, |
|||
data: { id: calEvent.id }, |
|||
success: function(response){ |
|||
if(response.message == 'event.deleted') |
|||
calendar.fullCalendar('removeEvents', calEvent.id); |
|||
console.log(calEvent); |
|||
}, |
|||
error: function(e){ |
|||
alert('Error processing your request: '+e.responseText); |
|||
} |
|||
}); |
|||
|
|||
} |
|||
|
|||
$('#CalenderModalEdit').modal('hide'); |
|||
$('#CalenderModalView').modal('hide'); |
|||
}); |
|||
|
|||
$("#antoform2").off("submit").on("submit", function() { |
|||
calEvent.title = $("#title2").val(); |
|||
calEvent.body = $("#descr2").val(); |
|||
|
|||
$.ajax({ |
|||
url: "{{ route('api.events.update') }}", |
|||
method: "PATCH", |
|||
data: { id: calEvent.id, title: calEvent.title, body: calEvent.body }, |
|||
success: function(response){ |
|||
if(response.message == 'event.updated') |
|||
$('#calendar').fullCalendar('updateEvent',calEvent); |
|||
console.log(calEvent); |
|||
}, |
|||
error: function(e){ |
|||
alert('Error processing your request: '+e.responseText); |
|||
} |
|||
}); |
|||
|
|||
$('#CalenderModalEdit').modal('hide'); |
|||
$('#CalenderModalView').modal('hide'); |
|||
|
|||
return false; |
|||
}); |
|||
|
|||
calendar.fullCalendar('unselect'); |
|||
}, |
|||
eventDrop: function(calEvent, delta, revertFunc) { |
|||
var start = calEvent.start.format('YYYY-MM-DD HH:mm:ss'); |
|||
var end = calEvent.end ? calEvent.end.format('YYYY-MM-DD HH:mm:ss') : null; |
|||
$.ajax({ |
|||
url: "{{ route('api.events.reschedule') }}", |
|||
method: "PATCH", |
|||
data: { id: calEvent.id, start: start, end: end }, |
|||
success: function(response){ |
|||
if(response.message != 'event.rescheduled') |
|||
revertFunc(); |
|||
}, |
|||
error: function(e){ |
|||
revertFunc(); |
|||
alert('Error processing your request: '+e.responseText); |
|||
} |
|||
}); |
|||
}, |
|||
eventResize: function(calEvent, delta, revertFunc) { |
|||
var start = calEvent.start.format('YYYY-MM-DD HH:mm:ss'); |
|||
var end = calEvent.end.format('YYYY-MM-DD HH:mm:ss'); |
|||
$.ajax({ |
|||
url: "{{ route('api.events.reschedule') }}", |
|||
method: "PATCH", |
|||
data: { id: calEvent.id, start: start, end: end }, |
|||
success: function(response){ |
|||
if(response.message != 'event.rescheduled') |
|||
revertFunc(); |
|||
}, |
|||
error: function(e){ |
|||
revertFunc(); |
|||
alert('Error processing your request: '+e.responseText); |
|||
} |
|||
}); |
|||
|
|||
} |
|||
}); |
|||
})(); |
|||
</script> |
|||
@endsection |
|||
@ -0,0 +1,147 @@ |
|||
<?php |
|||
|
|||
use App\Entities\Users\Event; |
|||
use App\Entities\Users\User; |
|||
use Illuminate\Foundation\Testing\DatabaseMigrations; |
|||
use Illuminate\Foundation\Testing\DatabaseTransactions; |
|||
use Illuminate\Foundation\Testing\WithoutMiddleware; |
|||
|
|||
class ApiEventsTest extends TestCase |
|||
{ |
|||
use DatabaseTransactions; |
|||
|
|||
/** @test */ |
|||
public function it_can_get_all_existing_events() |
|||
{ |
|||
$user = factory(User::class)->create(); |
|||
$events = factory(Event::class, 5)->create(['user_id' => $user->id]); |
|||
|
|||
$this->json('get', route('api.events.index'), [ |
|||
'Authorization' => 'Bearer ' . $user->api_token |
|||
]); |
|||
|
|||
$this->seeStatusCode(200); |
|||
|
|||
$this->seeJsonStructure([ |
|||
'*' => [ |
|||
'user', |
|||
'title', |
|||
'body', |
|||
'start', |
|||
] |
|||
]); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function user_can_create_new_event() |
|||
{ |
|||
$user = factory(User::class)->create(); |
|||
|
|||
$this->json('post', route('api.events.store'), [ |
|||
'title' => 'New Event Title', |
|||
'body' => 'New Event Body', |
|||
'start' => '2016-07-21 12:20:00', |
|||
], [ |
|||
'Authorization' => 'Bearer ' . $user->api_token |
|||
]); |
|||
|
|||
$this->seeStatusCode(201); |
|||
|
|||
$this->seeJson([ |
|||
'message' => trans('event.created'), |
|||
'user' => $user->name, |
|||
'title' => 'New Event Title', |
|||
'body' => 'New Event Body', |
|||
'start' => '2016-07-21 12:20:00', |
|||
'end' => null, |
|||
'allDay' => false, |
|||
]); |
|||
|
|||
$this->seeInDatabase('user_events', [ |
|||
'user_id' => $user->id, |
|||
'title' => 'New Event Title', |
|||
'body' => 'New Event Body', |
|||
'start' => '2016-07-21 12:20:00', |
|||
'end' => null, |
|||
'is_allday' => 0, |
|||
]); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function user_can_update_their_event() |
|||
{ |
|||
$user = factory(User::class)->create(); |
|||
$event = factory(Event::class)->create(['user_id' => $user->id]); |
|||
// dump($event->toArray());
|
|||
$this->json('patch', route('api.events.update'), [ |
|||
'id' => $event->id, |
|||
'title' => 'New Event Title', |
|||
'body' => 'New Event Body', |
|||
], [ |
|||
'Authorization' => 'Bearer ' . $user->api_token |
|||
]); |
|||
|
|||
$this->dump(); |
|||
|
|||
$this->seeStatusCode(200); |
|||
|
|||
$this->seeJson([ |
|||
'message' => trans('event.updated'), |
|||
'user' => $user->name, |
|||
'title' => 'New Event Title', |
|||
'body' => 'New Event Body', |
|||
]); |
|||
|
|||
$this->seeInDatabase('user_events', [ |
|||
'user_id' => $user->id, |
|||
'title' => 'New Event Title', |
|||
'body' => 'New Event Body', |
|||
]); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function user_can_delete_their_event() |
|||
{ |
|||
$user = factory(User::class)->create(); |
|||
$event = factory(Event::class)->create(['user_id' => $user->id]); |
|||
|
|||
$this->json('delete',route('api.events.destroy'), ['id' => $event->id], [ |
|||
'Authorization' => 'Bearer ' . $user->api_token |
|||
]); |
|||
|
|||
$this->seeStatusCode(200); |
|||
|
|||
$this->seeJson([ |
|||
'message' => trans('event.deleted'), |
|||
]); |
|||
} |
|||
|
|||
/** @test */ |
|||
public function user_can_reschedule_their_event() |
|||
{ |
|||
$user = factory(User::class)->create(); |
|||
$event = factory(Event::class)->create(['user_id' => $user->id, 'start' => '2016-11-17 12:00:00']); |
|||
|
|||
$this->json('patch', route('api.events.reschedule'), [ |
|||
'id' => $event->id, |
|||
'start' => '2016-11-07 13:00:00', |
|||
'start' => '2016-11-07 15:00:00', |
|||
], [ |
|||
'Authorization' => 'Bearer ' . $user->api_token |
|||
]); |
|||
|
|||
$this->dump(); |
|||
$this->seeStatusCode(200); |
|||
|
|||
$this->seeJson([ |
|||
'message' => trans('event.rescheduled'), |
|||
]); |
|||
|
|||
$this->seeInDatabase('user_events', [ |
|||
'id' => $event->id, |
|||
'user_id' => $user->id, |
|||
'start' => '2016-11-07 13:00:00', |
|||
'start' => '2016-11-07 15:00:00', |
|||
]); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue