Browse Source

Update 2016-11-25.20.59

pull/1/head
Nafies Luthfi 9 years ago
parent
commit
6c4527ae79
  1. 5
      app/Entities/Projects/Feature.php
  2. 26
      app/Entities/Users/Event.php
  3. 2
      app/Entities/Users/User.php
  4. 7
      app/Exceptions/Handler.php
  5. 171
      app/Http/Controllers/Api/EventsController.php
  6. 2
      app/Http/Controllers/Projects/ProjectsController.php
  7. 3
      app/Http/routes.php
  8. 29
      app/Http/routes/calendar.php
  9. 22
      app/Policies/EventPolicy.php
  10. 2
      app/Providers/AuthServiceProvider.php
  11. 3
      composer.json
  12. 591
      composer.lock
  13. 5
      config/app.php
  14. 17
      database/factories/ModelFactory.php
  15. 7316
      public/assets/css/app.css
  16. 1
      public/assets/css/app.css.map
  17. 7376
      public/assets/css/app.s.css
  18. 1
      public/assets/css/app.s.css.map
  19. 5
      public/assets/css/bootstrap-theme.min.css
  20. 5
      public/assets/css/bootstrap.min.css
  21. 4
      public/assets/css/font-awesome.min.css
  22. 353
      public/assets/css/sb-admin-2.css
  23. 10
      public/assets/js/bootstrap.min.js
  24. 2
      resources/assets/sass/_bootstrap.scss
  25. 1
      resources/views/features/partials/feature-show.blade.php
  26. 5
      resources/views/layouts/app.blade.php
  27. 1
      resources/views/layouts/partials/sidebar.blade.php
  28. 14
      resources/views/projects/features-export-excel.blade.php
  29. 71
      resources/views/projects/features-export-progress-excel.blade.php
  30. 1
      resources/views/projects/index.blade.php
  31. 313
      resources/views/users/calendar.blade.php
  32. 147
      tests/api/ApiEventsTest.php

5
app/Entities/Projects/Feature.php

@ -30,4 +30,9 @@ class Feature extends Model {
return $this->hasMany(Task::class)->orderBy('progress')->orderBy('position');
}
public function type()
{
return $this->type_id == 1 ? 'Project' : 'Additional';
}
}

26
app/Entities/Users/Event.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);
}
}

2
app/Entities/Users/User.php

@ -11,7 +11,7 @@ class User extends Authenticatable
use PresentableTrait, HasRoles;
protected $fillable = ['name', 'username', 'email', 'password'];
protected $hidden = ['password', 'remember_token'];
protected $hidden = ['password', 'remember_token', 'api_token'];
protected $presenter = UserPresenter::class;
public function setPasswordAttribute($value)

7
app/Exceptions/Handler.php

@ -45,6 +45,13 @@ class Handler extends ExceptionHandler
*/
public function render($request, Exception $e)
{
if ($e instanceof AuthorizationException) {
if ($request->isJson())
return response()->json(['error' => 'Forbidden Action.'], 403);
flash()->error('Invalid access');
return redirect()->home();
}
return parent::render($request, $e);
}
}

171
app/Http/Controllers/Api/EventsController.php

@ -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);
}
}

2
app/Http/Controllers/Projects/ProjectsController.php

@ -104,7 +104,7 @@ class ProjectsController extends Controller {
$features = $this->repo->getProjectFeatures($projectId, $featureType);
if ($exportType == 'excel') {
// return view('projects.features-export-excel', compact('project','features'));
return view('projects.features-export-excel', compact('project','features'));
\Excel::create(str_slug(trans('project.features') . '-' . $project->name), function($excel) use ($project, $features) {
$excel->sheet('testng', function($sheet) use ($project, $features) {
$sheet->loadView('projects.features-export-excel',compact('project','features'));

3
app/Http/routes.php

@ -10,4 +10,5 @@ require __DIR__ . '/routes/projects.php';
require __DIR__ . '/routes/payments.php';
require __DIR__ . '/routes/subscriptions.php';
require __DIR__ . '/routes/reports.php';
require __DIR__ . '/routes/options-vue.php';
require __DIR__ . '/routes/options-vue.php';
require __DIR__ . '/routes/calendar.php';

29
app/Http/routes/calendar.php

@ -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']);
// });

22
app/Policies/EventPolicy.php

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

2
app/Providers/AuthServiceProvider.php

@ -14,7 +14,7 @@ class AuthServiceProvider extends ServiceProvider
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
'App\Entities\Users\Event' => 'App\Policies\EventPolicy',
];
/**

3
composer.json

@ -13,7 +13,8 @@
"barryvdh/laravel-debugbar": "^2.0",
"backup-manager/laravel": "^1.0",
"maatwebsite/excel": "~2.1.0",
"barryvdh/laravel-dompdf": "^0.6.1"
"barryvdh/laravel-dompdf": "^0.6.1",
"spatie/laravel-fractal": "^2.0"
},
"require-dev": {
"fzaninotto/faker": "~1.4",

591
composer.lock
File diff suppressed because it is too large
View File

5
config/app.php

@ -159,11 +159,11 @@ return [
App\Providers\RouteServiceProvider::class,
BackupManager\Laravel\Laravel5ServiceProvider::class,
Barryvdh\Debugbar\ServiceProvider::class,
// Barryvdh\Debugbar\ServiceProvider::class,
Collective\Html\HtmlServiceProvider::class,
Laracasts\Flash\FlashServiceProvider::class,
Maatwebsite\Excel\ExcelServiceProvider::class,
Spatie\Fractal\FractalServiceProvider::class,
],
/*
@ -213,6 +213,7 @@ return [
'Carbon' => Carbon\Carbon::class,
'Debugbar' => Barryvdh\Debugbar\Facade::class,
'Excel' => Maatwebsite\Excel\Facades\Excel::class,
'Fractal' => Spatie\Fractal\FractalFacade::class,
'Form' => Collective\Html\FormFacade::class,
'FormField' => App\Services\Facades\FormField::class,
'Html' => Collective\Html\HtmlFacade::class,

17
database/factories/ModelFactory.php

@ -5,6 +5,7 @@ use App\Entities\Projects\Feature;
use App\Entities\Projects\Project;
use App\Entities\Projects\Task;
use App\Entities\Subscriptions\Subscription;
use App\Entities\Users\Event;
use App\Entities\Users\User;
/*
@ -22,9 +23,10 @@ $factory->define(User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'username' => $faker->username,
'email' => $faker->email,
'email' => $email = $faker->email,
'password' => 'member',
'remember_token' => str_random(10),
'api_token' => bcrypt($email),
];
});
@ -111,4 +113,17 @@ $factory->define(Task::class, function (Faker\Generator $faker) {
'route_name' => implode('.', $faker->words(3)),
'position' => rand(1,10),
];
});
$factory->define(Event::class, function (Faker\Generator $faker) {
return [
'user_id' => factory(User::class)->create()->id,
'project_id' => factory(Project::class)->create()->id,
'title' => $faker->words(rand(2,4), true),
'body' => $faker->sentence,
'start' => $faker->dateTimeBetween('-2 months', '-2 months')->format('Y-m-d H:i:s'),
'end' => $faker->dateTimeBetween('-2 months', '-2 months')->format('Y-m-d H:i:s'),
'is_allday' => rand(0,1),
];
});

7316
public/assets/css/app.css
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

7376
public/assets/css/app.s.css
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

5
public/assets/css/bootstrap-theme.min.css
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

4
public/assets/css/font-awesome.min.css
File diff suppressed because it is too large
View File

353
public/assets/css/sb-admin-2.css

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

2
resources/assets/sass/_bootstrap.scss

@ -46,7 +46,7 @@
@import "node_modules/bootstrap-sass/assets/stylesheets/bootstrap/close";
// Components w/ JavaScript
// @import "node_modules/bootstrap-sass/assets/stylesheets/bootstrap/modals";
@import "node_modules/bootstrap-sass/assets/stylesheets/bootstrap/modals";
// @import "node_modules/bootstrap-sass/assets/stylesheets/bootstrap/tooltip";
// @import "node_modules/bootstrap-sass/assets/stylesheets/bootstrap/popovers";
// @import "node_modules/bootstrap-sass/assets/stylesheets/bootstrap/carousel";

1
resources/views/features/partials/feature-show.blade.php

@ -3,6 +3,7 @@
<table class="table table-condensed">
<tbody>
<tr><th class="col-md-3">{{ trans('feature.name') }}</th><td class="col-md-9">{{ $feature->name }}</td></tr>
<tr><th>{{ trans('feature.type') }}</th><td>{{ $feature->type() }}</td></tr>
<tr><th>{{ trans('feature.price') }}</th><td>{{ formatRp($feature->price) }}</td></tr>
<tr><th>{{ trans('feature.tasks_count') }}</th><td>{{ $feature->tasks->count() }}</td></tr>
<tr><th>{{ trans('feature.progress') }}</th><td>{{ formatDecimal($feature->tasks->avg('progress')) }}%</td></tr>

5
resources/views/layouts/app.blade.php

@ -11,7 +11,7 @@
{{-- {!! Html::style('assets/css/plugins/metisMenu/metisMenu.min.css') !!} --}}
@yield('ext_css')
{!! Html::style('assets/css/app.css') !!}
{!! Html::style('assets/css/app.s.css') !!}
</head>
<body>
<div id="wrapper">
@ -42,6 +42,9 @@
$.ajaxSetup({
headers: {
'X-CSRF-Token': $('meta[name="x-csrf-token"]').attr('content')
},
beforeSend: function(xhr){
xhr.setRequestHeader('Authorization', 'Bearer ' + "{{ auth()->user()->api_token }}");
}
});
})();

1
resources/views/layouts/partials/sidebar.blade.php

@ -37,6 +37,7 @@
@can('see_reports')
<li>{!! html_link_to_route('reports.payments.yearly', 'Penghasilan', [], ['icon' => 'line-chart']) !!}</li>
<li>{!! html_link_to_route('reports.current-credits', 'Piutang', [], ['icon' => 'money']) !!}</li>
<li>{!! html_link_to_route('users.calendar', 'Calendar', [], ['icon' => 'calendar']) !!}</li>
{{--
<li>
{!! html_link_to_route('reports.payments.index', 'Laporan <span class="fa arrow"></span>', [], ['icon' => 'line-chart']) !!}

14
resources/views/projects/features-export-excel.blade.php

@ -11,6 +11,9 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{{ $project->name }}</title>
<style>
table {
border-collapse:collapse;
}
th, td {
padding: 5px;
}
@ -21,14 +24,13 @@
<thead>
<tr>
<td colspan="4" style="text-align:center">
<h1>{{ trans('project.features') }} {{ $project->name }}</h1>
<strong>{{ trans('project.features') }} {{ $project->name }}</strong>
</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 class="text-right">{{ trans('feature.price') }}</th>
<th>{{ trans('app.description') }}</th>
</tr>
</thead>
@ -39,8 +41,7 @@
<td>
{{ $feature->name }}
</td>
<td class="text-center">{{ $feature->progress = $feature->tasks->avg('progress')/100 }}</td>
{{-- <td class="text-right">{{ $feature->price }}</td> --}}
<td class="text-right">{{ $feature->price }}</td>
<td style="wrap-text: true;">{!! nl2br($feature->description) !!}</td>
</tr>
@ -61,8 +62,7 @@
<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 class="text-right">{{ $features->sum('price') }}</th>
<th></th>
</tr>
</tfoot>

71
resources/views/projects/features-export-progress-excel.blade.php

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

1
resources/views/projects/index.blade.php

@ -10,6 +10,7 @@
<div class="well well-sm text-right">
<div class="pull-left hidden-xs">{!! str_replace('/?', '?', $projects->appends(Request::except('page'))->render()) !!}</div>
{!! Form::open(['method'=>'get','class'=>'form-inline']) !!}
{!! Form::select('status', getProjectStatusesList(), Request::get('status'), ['class'=>'form-control','placeholder'=> '- Semua Project -']) !!}
{!! Form::text('q', Request::get('q'), ['class'=>'form-control index-search-field','placeholder'=>trans('project.search'),'style' => 'width:350px']) !!}
{!! Form::submit(trans('project.search'), ['class' => 'btn btn-info btn-sm']) !!}
{!! link_to_route('projects.index','Reset',[],['class' => 'btn btn-default btn-sm']) !!}

313
resources/views/users/calendar.blade.php

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

147
tests/api/ApiEventsTest.php

@ -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',
]);
}
}
Loading…
Cancel
Save