Merge branch 'develop' into trunk
This commit is contained in:
commit
2d43f071e9
25 changed files with 1617 additions and 951 deletions
40
app/Enums/Planning/CellModel.php
Normal file
40
app/Enums/Planning/CellModel.php
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Planning;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\Site;
|
||||
use App\Models\Ticket;
|
||||
use Kwaadpepper\Enum\BaseEnumRoutable;
|
||||
|
||||
/**
|
||||
* Décrit à quel modèle est lié
|
||||
* la cellule
|
||||
*
|
||||
* @method static self ticket()
|
||||
* \App\Models\Ticket
|
||||
* @method static self site()
|
||||
* \App\Models\Site
|
||||
* @method static self project()
|
||||
* \App\Models\Project
|
||||
*/
|
||||
class CellModel extends BaseEnumRoutable
|
||||
{
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
'ticket' => (new Ticket())->getTable(),
|
||||
'site' => (new Site())->getTable(),
|
||||
'project' => (new Project())->getTable(),
|
||||
];
|
||||
}
|
||||
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'ticket' => trans('Ticket'),
|
||||
'site' => trans('Site'),
|
||||
'project' => trans('Projet')
|
||||
];
|
||||
}
|
||||
}
|
||||
43
app/Enums/Planning/CellType.php
Normal file
43
app/Enums/Planning/CellType.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Planning;
|
||||
|
||||
use Kwaadpepper\Enum\BaseEnumRoutable;
|
||||
|
||||
/**
|
||||
* Décrit le type d'activitée que représente
|
||||
* la cellule
|
||||
*
|
||||
* @method static self support()
|
||||
* @method static self rnd()
|
||||
* @method static self dev()
|
||||
* @method static self projects()
|
||||
* @method static self management()
|
||||
* @method static self abscence()
|
||||
*/
|
||||
class CellType extends BaseEnumRoutable
|
||||
{
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
'support' => '#c6e0b4',
|
||||
'rnd' => '#b4c6e7',
|
||||
'dev' => '#00b0f0',
|
||||
'projects' => '#ffe699',
|
||||
'management' => '#f4b084',
|
||||
'absence' => '#aeaaaa'
|
||||
];
|
||||
}
|
||||
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'support' => trans('Support'),
|
||||
'rnd' => trans('R&D'),
|
||||
'dev' => trans('Développement/Facturable'),
|
||||
'projects' => trans('Projets & Réunions'),
|
||||
'management' => trans('Organisation management'),
|
||||
'absence' => trans('Absence'),
|
||||
];
|
||||
}
|
||||
}
|
||||
36
app/Enums/Planning/TableRow.php
Normal file
36
app/Enums/Planning/TableRow.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Planning;
|
||||
|
||||
use Kwaadpepper\Enum\BaseEnumRoutable;
|
||||
|
||||
/**
|
||||
* Décrit à quel moment de la journée appartient la cellule
|
||||
*
|
||||
* @method static self matin1()
|
||||
* @method static self matin2()
|
||||
* @method static self apresmidi1()
|
||||
* @method static self apresmidi2()
|
||||
*/
|
||||
class TableRow extends BaseEnumRoutable
|
||||
{
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
'matin1' => 1,
|
||||
'matin2' => 2,
|
||||
'apresmidi1' => 3,
|
||||
'apresmidi2' => 4,
|
||||
];
|
||||
}
|
||||
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'matin1' => trans('Matin 1'),
|
||||
'matin2' => trans('Matin 2'),
|
||||
'apresmidi1' => trans('Après-midi 1'),
|
||||
'apresmidi2' => trans('Après-midi 2')
|
||||
];
|
||||
}
|
||||
}
|
||||
65
app/Http/Controllers/PlanningController.php
Normal file
65
app/Http/Controllers/PlanningController.php
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\Role;
|
||||
use App\Http\Requests\EraseCellContentRequest;
|
||||
use App\Http\Requests\SetCellContentRequest;
|
||||
use App\Models\User;
|
||||
use App\Models\WeekPlanning;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class PlanningController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$date = Carbon::now()->startOfWeek();
|
||||
if ($request->p) {
|
||||
$date = Carbon::createFromTimestamp($request->p)->startOfWeek();
|
||||
}
|
||||
$userIds = User::where('role', Role::developper())->pluck('id');
|
||||
$weeks = WeekPlanning::whereDate('start', $date)
|
||||
->with('user')->get();
|
||||
foreach ($userIds as $id) {
|
||||
// auto create missing planning for user
|
||||
if ($date >= Carbon::now() and !$weeks->contains('user.id', $id)) {
|
||||
$wp = new WeekPlanning([
|
||||
'start' => $date,
|
||||
'planning' => WeekPlanning::getWeekBlock($date),
|
||||
'user_id' => $id
|
||||
]);
|
||||
$wp->saveOrFail();
|
||||
$weeks->push($wp);
|
||||
}
|
||||
}
|
||||
return view('planning.index', compact('weeks', 'date'));
|
||||
}
|
||||
|
||||
public function setCellContent(SetCellContentRequest $request)
|
||||
{
|
||||
$wp = WeekPlanning::findOrFail($request->id);
|
||||
if (!isset($wp->planning[$request->day][$request->row])) {
|
||||
abort(422, trans('Ce jour et ce moment n\'existent pas.'));
|
||||
}
|
||||
$planning = $wp->planning;
|
||||
$planning[$request->day][$request->row] =
|
||||
array_merge(WeekPlanning::getCell(), array_intersect_key($request->all(), WeekPlanning::getCell()));
|
||||
$wp->planning = $planning;
|
||||
$wp->saveOrFail();
|
||||
return \response()->json(['status' => true]);
|
||||
}
|
||||
|
||||
public function eraseCellContent(EraseCellContentRequest $request)
|
||||
{
|
||||
$wp = WeekPlanning::findOrFail($request->id);
|
||||
if (!isset($wp->planning[$request->day][$request->row])) {
|
||||
abort(422, trans('Ce jour et ce moment n\'existent pas.'));
|
||||
}
|
||||
$planning = $wp->planning;
|
||||
$planning[$request->day][$request->row] = WeekPlanning::getCell();
|
||||
$wp->planning = $planning;
|
||||
$wp->saveOrFail();
|
||||
return \response()->json(['status' => true]);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
|
||||
use Kwaadpepper\CrudPolicies\Traits\CrudController;
|
||||
|
||||
|
|
@ -28,10 +30,13 @@ class ProjectController extends Controller
|
|||
return view('projects.index', $view->getData());
|
||||
}
|
||||
|
||||
public function show(Project $model)
|
||||
public function show(Request $request, Project $model)
|
||||
{
|
||||
/** @var \Illuminate\View\View */
|
||||
$view = $this->crudshow($model);
|
||||
$view = $this->crudshow($request, $model);
|
||||
if ($view instanceof JsonResponse) {
|
||||
return $view;
|
||||
}
|
||||
return view('projects.show', $view->getData());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,32 @@ class TicketController extends Controller
|
|||
|
||||
public function index(SortTickets $request)
|
||||
{
|
||||
// handle ajax
|
||||
if ($request->ajax()) {
|
||||
$tickets = Ticket::with('user');
|
||||
if ($request->s) {
|
||||
$tickets = $tickets->where('text', 'LIKE', "%$request->s%")
|
||||
->orWhere('id', $request->s)
|
||||
->orWhereHas('user', function ($q) use ($request) {
|
||||
$q->where('name', 'LIKE', "%$request->s%");
|
||||
});
|
||||
}
|
||||
/** @var \Illuminate/Pagination/LengthAwarePaginator $tickets */
|
||||
$tickets = $tickets->paginate();
|
||||
return \response()->json($tickets->getCollection()->map(function (Ticket $ticket) {
|
||||
return [
|
||||
'label' => sprintf(
|
||||
'Ticket %d (%s): %s',
|
||||
$ticket->id,
|
||||
$ticket->user->name,
|
||||
Str::limit(\preg_replace('/[[:cntrl:]]+/', '', $ticket->text), 35)
|
||||
),
|
||||
'value' => $ticket->id
|
||||
];
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/** @var \App\Models\User */
|
||||
$user = auth()->user();
|
||||
$tickets = Ticket::with('site')->has('site');
|
||||
|
|
@ -132,8 +158,19 @@ class TicketController extends Controller
|
|||
});
|
||||
}
|
||||
|
||||
public function show(Ticket $ticket)
|
||||
public function show(Request $request, Ticket $ticket)
|
||||
{
|
||||
if ($request->ajax()) {
|
||||
return \response()->json([
|
||||
'label' => sprintf(
|
||||
'Ticket %d (%s): %s',
|
||||
$ticket->id,
|
||||
$ticket->user->name,
|
||||
Str::limit(\preg_replace('/[[:cntrl:]]+/', '', $ticket->text), 35)
|
||||
),
|
||||
'value' => $ticket->id
|
||||
]);
|
||||
}
|
||||
$messages = $ticket->messages()->orderBy('created_at', 'DESC')->get();
|
||||
$isSubscribed = (bool)$ticket->subscriptions()->where('user_id', Auth::user()->id)->count();
|
||||
return view('ticket.show', compact('ticket', 'messages', 'isSubscribed'));
|
||||
|
|
|
|||
37
app/Http/Requests/EraseCellContentRequest.php
Normal file
37
app/Http/Requests/EraseCellContentRequest.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Enums\Planning\TableRow;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
|
||||
|
||||
class EraseCellContentRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->check() and $this->ajax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'id' => 'required|exists:week_plannings,id',
|
||||
'day' => 'required|numeric',
|
||||
'row' => [
|
||||
'required',
|
||||
new EnumIsValidRule(TableRow::class)
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
63
app/Http/Requests/SetCellContentRequest.php
Normal file
63
app/Http/Requests/SetCellContentRequest.php
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Enums\Planning\CellModel;
|
||||
use App\Enums\Planning\CellType;
|
||||
use App\Enums\Planning\TableRow;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
|
||||
|
||||
class SetCellContentRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
$this->merge([
|
||||
'model' => $this->model ?? null,
|
||||
'foreign' => $this->foreign ?? null,
|
||||
'text' => $this->text ?? ''
|
||||
]);
|
||||
return auth()->check() and $this->ajax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'id' => 'required|exists:week_plannings,id',
|
||||
'day' => 'required|numeric',
|
||||
'row' => [
|
||||
'required',
|
||||
new EnumIsValidRule(TableRow::class)
|
||||
],
|
||||
'type' => [
|
||||
'required',
|
||||
new EnumIsValidRule(CellType::class)
|
||||
],
|
||||
'cellModel' => \sprintf(
|
||||
'nullable',
|
||||
new EnumIsValidRule(CellModel::class)
|
||||
),
|
||||
'foreign' => 'required_with:cellModel|nullable|numeric',
|
||||
'text' => 'required|string|min:10|max:90'
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes()
|
||||
{
|
||||
return [
|
||||
'cellModel' => 'Type d\'élément lié',
|
||||
'foreign' => 'Élément lié',
|
||||
'text' => 'Description courte'
|
||||
];
|
||||
}
|
||||
}
|
||||
61
app/Models/WeekPlanning.php
Normal file
61
app/Models/WeekPlanning.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\Planning\TableRow;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class WeekPlanning extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $casts = [
|
||||
'start' => 'date',
|
||||
'planning' => 'array'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'start', 'planning', 'user_id'
|
||||
];
|
||||
|
||||
public static function getWeekBlock(Carbon $start): array
|
||||
{
|
||||
$start = $start->copy();
|
||||
$out = [];
|
||||
$daysOfWeek = 5;
|
||||
for ($i = 1; $i <= $daysOfWeek; $i++) {
|
||||
$out[$start->timestamp] = [];
|
||||
foreach (TableRow::toArray() as $tableRow) {
|
||||
$out[$start->timestamp][$tableRow->value] = \array_replace(
|
||||
self::getCell(),
|
||||
[
|
||||
'row' => $tableRow->value
|
||||
]
|
||||
);
|
||||
}
|
||||
$day = $start->addDay();
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function getCell(): array
|
||||
{
|
||||
return [
|
||||
/** @var \App\Enums\Planning\CellType */
|
||||
'type' => null,
|
||||
/** @var \App\Enums\Planning\TableRow */
|
||||
'row' => null,
|
||||
'foreign' => null,
|
||||
/** @var \App\Enums\Planning\CellModel */
|
||||
'cellModel' => null,
|
||||
'text' => ''
|
||||
];
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
"fideloper/proxy": "^4.4",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"kwaadpepper/crud-policies": "^0.1.19",
|
||||
"kwaadpepper/crud-policies": "^0.1.20",
|
||||
"kwaadpepper/enum": "^2.1",
|
||||
"kwaadpepper/exception-handler": "^0.0.3",
|
||||
"laravel-lang/lang": "~8.0",
|
||||
|
|
|
|||
1214
composer.lock
generated
1214
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -234,6 +234,10 @@ return [
|
|||
'Role' => App\Enums\Role::class,
|
||||
'FileType' => App\Enums\FileType::class,
|
||||
'LoadLevel' => App\Enums\LoadLevel::class,
|
||||
'CellModel' => App\Enums\Planning\CellModel::class,
|
||||
'CellType' => App\Enums\Planning\CellType::class,
|
||||
'TableRow' => App\Enums\Planning\TableRow::class,
|
||||
'Carbon' => 'Carbon\Carbon'
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
|||
76
database/factories/WeekPlanningFactory.php
Normal file
76
database/factories/WeekPlanningFactory.php
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\Planning\CellModel;
|
||||
use App\Enums\Planning\CellType;
|
||||
use App\Models\Project;
|
||||
use App\Models\Site;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\WeekPlanning;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use ReflectionFunction;
|
||||
|
||||
class WeekPlanningFactory extends Factory
|
||||
{
|
||||
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = WeekPlanning::class;
|
||||
|
||||
/** @var \Illuminate\Support\Carbon */
|
||||
private static $currentDate = null;
|
||||
|
||||
private static $user_id = null;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
$func = new ReflectionFunction(collect($this->states)->last());
|
||||
if (static::$user_id and $func->getStaticVariables()['state']['user_id'] != static::$user_id) {
|
||||
static::$currentDate = null;
|
||||
}
|
||||
if (!static::$currentDate) {
|
||||
static::$user_id = $func->getStaticVariables()['state']['user_id'];
|
||||
static::$currentDate = $func->getStaticVariables()['state']['start'];
|
||||
} else {
|
||||
static::$currentDate = static::$currentDate->endOfWeek()->addDay();
|
||||
}
|
||||
$wBlock = WeekPlanning::getWeekBlock(static::$currentDate);
|
||||
foreach ($wBlock as &$dayRows) {
|
||||
foreach ($dayRows as &$cell) {
|
||||
$cell['type'] = collect(CellType::toArray())->random()->value;
|
||||
if (rand(0, 3)) {
|
||||
$cellModel = collect(CellModel::toArray())->random();
|
||||
$cell['cellModel'] = $cellModel->value;
|
||||
switch ($cellModel) {
|
||||
case (new Ticket())->getTable():
|
||||
$cell['foreign'] = Ticket::select(['id'])
|
||||
->inRandomOrder()->first()->id;
|
||||
break;
|
||||
case (new Site())->getTable():
|
||||
$cell['foreign'] = Site::select(['id'])
|
||||
->inRandomOrder()->first()->id;
|
||||
break;
|
||||
case (new Project())->getTable():
|
||||
$cell['foreign'] = Project::select(['id'])
|
||||
->inRandomOrder()->first()->id;
|
||||
break;
|
||||
}
|
||||
$cell['text'] = $this->faker->text(rand(20, 50));
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
'start' => static::$currentDate,
|
||||
'planning' => $wBlock
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateWeekPlanningsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('week_plannings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->date('start');
|
||||
$table->text('planning');
|
||||
|
||||
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('week_plannings');
|
||||
}
|
||||
}
|
||||
29
database/seeders/WeekPlanningSeeder.php
Normal file
29
database/seeders/WeekPlanningSeeder.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Enums\Role;
|
||||
use App\Models\User;
|
||||
use App\Models\WeekPlanning;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class WeekPlanningSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
WeekPlanning::truncate();
|
||||
/** \App\Models\User $user */
|
||||
foreach (User::where('role', Role::developper())->get() as $user) {
|
||||
WeekPlanning::factory(13)->create([
|
||||
'start' => Carbon::now()->startOfWeek(),
|
||||
'user_id' => $user->id
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
package-lock.json
generated
18
package-lock.json
generated
|
|
@ -12,12 +12,13 @@
|
|||
"laravel-mix": "^6.0.16",
|
||||
"laravel-mix-polyfill": "^3.0.1",
|
||||
"lodash": "^4.17.19",
|
||||
"moment": "^2.29.1",
|
||||
"resolve-url-loader": "^3.1.0",
|
||||
"sass": "^1.15.2",
|
||||
"sass-loader": "^8.0.0",
|
||||
"vue": "^2.6.14",
|
||||
"vue-loader": "^15.9.7",
|
||||
"vue-select": "^3.11.2",
|
||||
"vue-select": "^3.13.0",
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
}
|
||||
},
|
||||
|
|
@ -6359,6 +6360,15 @@
|
|||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
|
@ -14750,6 +14760,12 @@
|
|||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
|
|
|||
|
|
@ -17,12 +17,13 @@
|
|||
"laravel-mix": "^6.0.16",
|
||||
"laravel-mix-polyfill": "^3.0.1",
|
||||
"lodash": "^4.17.19",
|
||||
"moment": "^2.29.1",
|
||||
"resolve-url-loader": "^3.1.0",
|
||||
"sass": "^1.15.2",
|
||||
"sass-loader": "^8.0.0",
|
||||
"vue": "^2.6.14",
|
||||
"vue-loader": "^15.9.7",
|
||||
"vue-select": "^3.11.2",
|
||||
"vue-select": "^3.13.0",
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
106
resources/js/app.js
vendored
106
resources/js/app.js
vendored
|
|
@ -1 +1,107 @@
|
|||
require('./bootstrap');
|
||||
window.Vue = require('vue/dist/vue.min.js');
|
||||
import moment from 'moment'
|
||||
import vSelect from "vue-select"
|
||||
|
||||
// v-select
|
||||
Vue.component('v-select', vSelect)
|
||||
|
||||
// Configure moment
|
||||
moment.locale('fr', {
|
||||
months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
|
||||
monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
|
||||
monthsParseExact : true,
|
||||
weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'),
|
||||
weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'),
|
||||
weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'),
|
||||
weekdaysParseExact : true,
|
||||
longDateFormat : {
|
||||
LT : 'HH:mm',
|
||||
LTS : 'HH:mm:ss',
|
||||
L : 'DD/MM/YYYY',
|
||||
LL : 'D MMMM YYYY',
|
||||
LLL : 'D MMMM YYYY HH:mm',
|
||||
LLLL : 'dddd D MMMM YYYY HH:mm'
|
||||
},
|
||||
calendar : {
|
||||
sameDay : '[Aujourd’hui à] LT',
|
||||
nextDay : '[Demain à] LT',
|
||||
nextWeek : 'dddd [à] LT',
|
||||
lastDay : '[Hier à] LT',
|
||||
lastWeek : 'dddd [dernier à] LT',
|
||||
sameElse : 'L'
|
||||
},
|
||||
relativeTime : {
|
||||
future : 'dans %s',
|
||||
past : 'il y a %s',
|
||||
s : 'quelques secondes',
|
||||
m : 'une minute',
|
||||
mm : '%d minutes',
|
||||
h : 'une heure',
|
||||
hh : '%d heures',
|
||||
d : 'un jour',
|
||||
dd : '%d jours',
|
||||
M : 'un mois',
|
||||
MM : '%d mois',
|
||||
y : 'un an',
|
||||
yy : '%d ans'
|
||||
},
|
||||
dayOfMonthOrdinalParse : /\d{1,2}(er|e)/,
|
||||
ordinal : function (number) {
|
||||
return number + (number === 1 ? 'er' : 'e');
|
||||
},
|
||||
meridiemParse : /PD|MD/,
|
||||
isPM : function (input) {
|
||||
return input.charAt(0) === 'M';
|
||||
},
|
||||
// In case the meridiem units are not separated around 12, then implement
|
||||
// this function (look at locale/id.js for an example).
|
||||
// meridiemHour : function (hour, meridiem) {
|
||||
// return /* 0-23 hour, given meridiem token and hour 1-12 */ ;
|
||||
// },
|
||||
meridiem : function (hours, minutes, isLower) {
|
||||
return hours < 12 ? 'PD' : 'MD';
|
||||
},
|
||||
week : {
|
||||
dow : 1, // Monday is the first day of the week.
|
||||
doy : 4 // Used to determine first week of the year.
|
||||
}
|
||||
});
|
||||
|
||||
moment.locale('fr');
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
// Register modules
|
||||
const modules = require.context("./modules", true, /\.js$/i);
|
||||
modules.keys().map(key => Vue.mixin(modules(key).default));
|
||||
|
||||
// Register components
|
||||
const vues = require.context("./components", true, /\.vue$/i);
|
||||
vues.keys().map(key =>
|
||||
Vue.component(
|
||||
key
|
||||
.split("/")
|
||||
.pop()
|
||||
.split(".")[0]
|
||||
.replace(/\.?([A-Z])/g, function (x, y) {
|
||||
return "-" + y.toLowerCase();
|
||||
})
|
||||
.replace(/^-/, ""),
|
||||
vues(key).default
|
||||
)
|
||||
);
|
||||
|
||||
// Register vue function
|
||||
Vue.filter('str_limit', function (value, size) {
|
||||
if (!value) return '';
|
||||
value = value.toString();
|
||||
if (value.length <= size) {
|
||||
return value;
|
||||
}
|
||||
return value.substr(0, size) + '...';
|
||||
});
|
||||
|
||||
for (let el of document.getElementsByClassName('vueComponent')) {
|
||||
new Vue({ el: el });
|
||||
}
|
||||
|
||||
|
|
|
|||
1
resources/js/bootstrap.js
vendored
1
resources/js/bootstrap.js
vendored
|
|
@ -15,6 +15,7 @@ window._ = require('lodash');
|
|||
window.axios = require('axios');
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
|
|
|
|||
505
resources/js/components/PlanningComponent.vue
Normal file
505
resources/js/components/PlanningComponent.vue
Normal file
|
|
@ -0,0 +1,505 @@
|
|||
<template>
|
||||
<div class="planning">
|
||||
<table v-if="currentWeek" class="table table-bordered align-middle text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2">
|
||||
<b>{{ currentWeek.user.name }}</b>
|
||||
</th>
|
||||
<th :colspan="daysOfWeek">
|
||||
<span class="fs-5">S{{ moment(currentWeek.start).week() }}</span>
|
||||
<span class="float-end me-5 mt-1">{{ moment(currentWeek.start).format('MMMM YYYY') }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td v-for="(day, timestamp) in currentWeek.planning"
|
||||
:key="timestamp">
|
||||
<b>{{ moment.unix(timestamp).format("dddd DD") }}</b>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="row in rows" :key="row.value">
|
||||
<td><b>{{ row.label }}</b></td>
|
||||
<template v-for="(day, timestamp) in currentWeek.planning">
|
||||
<td :key="timestamp" class="dynCell" :style="'background-color:' + day[row.value]['type'] + ';'">
|
||||
<b v-if="day[row.value]['cellModel']">
|
||||
<a target="_blank" class="text-light bg-dark"
|
||||
:href="getCellObjectInfo(day[row.value]).url">{{ getCellObjectInfo(day[row.value]).text }}</a>
|
||||
</b>
|
||||
<span v-else-if="!day[row.value]['text'].length">Rien</span><br>
|
||||
{{ day[row.value]["text"] | str_limit(25) }}
|
||||
<button class="btn btn-clear" @click="eraseCell(timestamp, row)">⌫</button>
|
||||
<button class="btn btn-edit" type="button" data-bs-toggle="modal"
|
||||
:data-bs-target="'#editModal'+currentWeek.id" @click="setModalData(timestamp, row)">✎</button>
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Modal -->
|
||||
<div v-if="currentWeek" class="modal fade" :id="'editModal'+currentWeek.id" tabindex="-1" :aria-labelledby="'modalLabel'+currentWeek.id" aria-hidden="true" >
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" :id="'modalLabel'+currentWeek.id" ref="modalLabel">Modal title</h5>
|
||||
<button ref="close" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" ></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div v-if="error" class="alert alert-danger" role="alert">
|
||||
{{ error }}
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div class="form-group mb-3">
|
||||
<label :for="'type'+currentWeek.id" class="col-form-label text-md-right ms-5">Categorie</label>
|
||||
<select :id="'type'+currentWeek.id" ref="type" class="form-select" aria-label="Sélectionnez une catégorie">
|
||||
<option value="" :selected="!currentType">Sélectionnez une catégorie</option>
|
||||
<option v-for="type in types" :key="type.value" :value="type.value"
|
||||
:selected="type.value === currentType"
|
||||
:style="'background-color:'+ type.value+';'">{{ type.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label :for="'morph'+currentWeek.id" class="col-form-label text-md-right ms-5">Type d'élément lié</label>
|
||||
<select :id="'morph'+currentWeek.id" ref="morph" class="form-select" @change="selectChange" aria-label="Sélectionnez un élément à lier">
|
||||
<option value="" :selected="!currentModel">Sélectionnez un élément à lier</option>
|
||||
<option v-for="model in models" :key="model.value" :selected="model.value === currentModel" :value="model.value">{{ model.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mb-3" v-if="dDisplay">
|
||||
<label :for="'select'+currentWeek.id" class="col-form-label text-md-right ms-5">Élément lié</label>
|
||||
<v-select
|
||||
:id="'select'+currentWeek.id"
|
||||
ref="select"
|
||||
:loading="loading"
|
||||
v-model="value"
|
||||
:clearable="false"
|
||||
label="label"
|
||||
:options='data'
|
||||
@search="search"
|
||||
:disabled="dReadonly||dDisabled"
|
||||
:filterable="true"
|
||||
:searchable="true">
|
||||
<li slot="list-footer" class="pagination">
|
||||
<button
|
||||
@click="$event.preventDefault(); page -= 1"
|
||||
:disabled="!hasPrevPage"
|
||||
class="btn btn-sm btn-primary mt-2 mb-2 ms-2 me-2">Précédent</button>
|
||||
<button
|
||||
@click="$event.preventDefault(); page += 1"
|
||||
:disabled="!hasNextPage"
|
||||
class="btn btn-sm btn-primary mt-2 mb-2">Suivant</button>
|
||||
</li>
|
||||
</v-select>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label :for="'text'+currentWeek.id" class="col-form-label text-md-right ms-5">Description courte</label>
|
||||
<textarea ref="text" class="form-control" name="text"
|
||||
:id="'text'+currentWeek.id" rows="2" placeholder="" @keydown="limitLines($event)" ></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-8 text-center">
|
||||
<button class="btn btn-primary me-3" @click="saveCell()">Valider</button>
|
||||
<button class="btn btn-danger" @click="$refs.close.click()">Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
mounted() {
|
||||
this.models = this.dbModels;
|
||||
this.types = this.dbTypes;
|
||||
this.rows = this.dbRows;
|
||||
this.$set(this, 'currentWeek', this.dbCurrentWeek);
|
||||
this.search(false)
|
||||
},
|
||||
props: ["dbModels", "dbTypes", "dbRows", "dbCurrentWeek"],
|
||||
data() {
|
||||
return {
|
||||
textLimit: 90,
|
||||
daysOfWeek: 5,
|
||||
types: [],
|
||||
rows: [],
|
||||
models: [],
|
||||
currentWeek: null,
|
||||
currentTimestamp: null,
|
||||
currentRow: null,
|
||||
currentModel: null,
|
||||
currentType: null,
|
||||
error: false,
|
||||
|
||||
// v-select
|
||||
dDisplay : false,
|
||||
dDisabled : false,
|
||||
dReadonly : false,
|
||||
value: {
|
||||
label: null,
|
||||
value: null
|
||||
},
|
||||
loading: false,
|
||||
searchText: '',
|
||||
data: [],
|
||||
page: 1,
|
||||
perPage: 15,
|
||||
hasNextPage: true,
|
||||
hasPrevPage: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
dbRows() {
|
||||
this.rows = this.dbRows;
|
||||
},
|
||||
dbTypes() {
|
||||
this.types = this.dbTypes;
|
||||
},
|
||||
dbModels() {
|
||||
this.models = this.dbModels;
|
||||
},
|
||||
dbCurrentWeek() {
|
||||
this.currentWeek = this.dbCurrentWeek;
|
||||
},
|
||||
page() {
|
||||
if(this.page < 1) {
|
||||
this.page = 1
|
||||
}
|
||||
if(this.page > 1) {
|
||||
this.hasPrevPage = true
|
||||
} else {
|
||||
this.hasPrevPage = false
|
||||
}
|
||||
this.search()
|
||||
},
|
||||
data() {
|
||||
if(this.data.length == this.perPage) {
|
||||
this.hasNextPage = true
|
||||
} else {
|
||||
this.hasNextPage = false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
eraseCell(timestamp, row) {
|
||||
this.setModalData(timestamp, row)
|
||||
const data = {
|
||||
'id': this.currentWeek.id,
|
||||
'day': this.currentTimestamp,
|
||||
'row': this.currentRow.value,
|
||||
}
|
||||
axios.post(this.route('planning.eraseCellContent'), data)
|
||||
.then(response => {
|
||||
if(response.data.status) {
|
||||
this.$set(this.currentWeek.planning[this.currentTimestamp], this.currentRow.value, {
|
||||
'type': '',
|
||||
'foreign': null,
|
||||
'cellModel': null,
|
||||
'text': '',
|
||||
})
|
||||
} else {
|
||||
console.error('Unknown response')
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if(error.response.status === 429) {
|
||||
location.reload()
|
||||
}
|
||||
console.error(error)
|
||||
})
|
||||
},
|
||||
saveCell() {
|
||||
this.error = false
|
||||
const data = {
|
||||
'id': this.currentWeek.id,
|
||||
'day': this.currentTimestamp,
|
||||
'type': this.$refs.type.value,
|
||||
'row': this.currentRow.value,
|
||||
'cellModel': this.$refs.morph.value,
|
||||
'foreign': this.value.value,
|
||||
'text': this.$refs.text.value,
|
||||
}
|
||||
axios.post(this.route('planning.setCellContent'), data)
|
||||
.then(response => {
|
||||
if(response.data.status) {
|
||||
this.$set(this.currentWeek.planning[this.currentTimestamp], this.currentRow.value, {
|
||||
'type': data['type'],
|
||||
'foreign': data['foreign'],
|
||||
'cellModel': data['cellModel'],
|
||||
'text': data['text'],
|
||||
})
|
||||
this.$refs.close.click()
|
||||
} else {
|
||||
this.error = 'Réponse inconnue';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if(error.response.status === 429) {
|
||||
location.reload()
|
||||
}
|
||||
if(error.response.status === 422) {
|
||||
if(error.response.data.errors) {
|
||||
this.error = ''
|
||||
for(let id in error.response.data.errors) {
|
||||
let field = error.response.data.errors[id]
|
||||
this.error += field.join('\n') + '\n'
|
||||
}
|
||||
return
|
||||
} else if(error.response.data.message) {
|
||||
this.error = error.response.data.message
|
||||
return
|
||||
}
|
||||
}
|
||||
this.error = error
|
||||
})
|
||||
},
|
||||
selectChange() {
|
||||
this.value = {
|
||||
label: null,
|
||||
value: null
|
||||
}
|
||||
switch(this.$refs.morph.value) {
|
||||
case 'tickets':
|
||||
case 'projects':
|
||||
case 'sites':
|
||||
this.dDisplay = true
|
||||
this.search()
|
||||
break
|
||||
default: this.dDisplay = false
|
||||
}
|
||||
},
|
||||
search (search = null) {
|
||||
this.$nextTick(() => {
|
||||
this.error = null
|
||||
let route;
|
||||
switch(this.$refs.morph.value) {
|
||||
case 'tickets':
|
||||
route = this.route('ticket.index')
|
||||
break
|
||||
case 'projects':
|
||||
route = this.route('projects.index')
|
||||
break
|
||||
case 'sites':
|
||||
route = this.route('sites.index')
|
||||
break
|
||||
default: return
|
||||
}
|
||||
if(search !== false && search !== null) {
|
||||
this.searchText = search
|
||||
}
|
||||
let init = search === false ? true : false
|
||||
route += '?page='+this.page
|
||||
route += '&s='+this.searchText
|
||||
this.loading = true
|
||||
axios.get(route)
|
||||
.then(response => {
|
||||
this.data = response.data
|
||||
})
|
||||
.catch(error => {
|
||||
if(error.response.status === 429) {
|
||||
location.reload()
|
||||
}
|
||||
this.error = error
|
||||
}).then(() => {
|
||||
this.loading = false
|
||||
})
|
||||
});
|
||||
},
|
||||
getModel(cellModelValue) {
|
||||
for (let model of this.models) {
|
||||
if (model.value === cellModelValue) {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
throw new Error("Unknown cell model value " + cellModelValue);
|
||||
},
|
||||
getCellObjectInfo(cellData) {
|
||||
for(let model of this.models) {
|
||||
if(cellData['cellModel'] === model.value && model.value == 'tickets') {
|
||||
return {
|
||||
'url': this.route('ticket.show', { TICKET: cellData['foreign']}),
|
||||
'text': this.getModel(cellData["cellModel"]).label
|
||||
}
|
||||
}
|
||||
if(cellData['cellModel'] === model.value && model.value == 'sites') {
|
||||
return {
|
||||
'url': this.route('sites.show', { SITE: cellData['foreign']}),
|
||||
'text': this.getModel(cellData["cellModel"]).label
|
||||
}
|
||||
}
|
||||
if(cellData['cellModel'] === model.value && model.value == 'projects') {
|
||||
return {
|
||||
'url': this.route('projects.show', { PROJECT: cellData['foreign']}),
|
||||
'text': this.getModel(cellData["cellModel"]).label
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
setModalData(timestamp, row) {
|
||||
this.currentTimestamp = timestamp
|
||||
this.currentRow = row
|
||||
this.currentType = this.currentWeek.planning[this.currentTimestamp][this.currentRow.value].type
|
||||
this.currentModel = this.currentWeek.planning[this.currentTimestamp][this.currentRow.value].cellModel
|
||||
let cellData = this.currentWeek.planning[timestamp][row.value]
|
||||
this.$refs.modalLabel.innerHTML = 'Jour : <u>' +
|
||||
this.moment.unix(timestamp).format("dddd, D MMMM YYYY")+ '</u>' +
|
||||
' <b>' + row.label + '<b/>';
|
||||
this.$refs.text.value = cellData.text
|
||||
this.$nextTick(() => {
|
||||
let route = null
|
||||
this.value = {
|
||||
label: null,
|
||||
value: null
|
||||
}
|
||||
switch(this.currentModel) {
|
||||
case 'tickets':
|
||||
this.dDisplay = true
|
||||
route = this.route('ticket.show', {
|
||||
TICKET: this.currentWeek.planning[this.currentTimestamp][this.currentRow.value].foreign
|
||||
})
|
||||
break
|
||||
case 'projects':
|
||||
this.dDisplay = true
|
||||
route = this.route('projects.show', {
|
||||
PROJECT: this.currentWeek.planning[this.currentTimestamp][this.currentRow.value].foreign
|
||||
})
|
||||
break
|
||||
case 'sites':
|
||||
this.dDisplay = true
|
||||
route = this.route('sites.show', {
|
||||
SITE: this.currentWeek.planning[this.currentTimestamp][this.currentRow.value].foreign
|
||||
})
|
||||
break
|
||||
default: this.dDisplay = false
|
||||
}
|
||||
if(route) {
|
||||
axios.get(route).then(response => {
|
||||
this.value = response.data
|
||||
}).catch(error => {
|
||||
if(error.response.status === 429) {
|
||||
location.reload()
|
||||
}
|
||||
this.error = error
|
||||
}).then(() => {
|
||||
this.loading = false
|
||||
})
|
||||
this.search()
|
||||
}
|
||||
})
|
||||
},
|
||||
limitLines(e) {
|
||||
let keynum, lines = this.$refs.text.value.split('\n').length;
|
||||
// IE
|
||||
if(window.event) {
|
||||
keynum = e.keyCode;
|
||||
// Netscape/Firefox/Opera
|
||||
} else if(e.which) {
|
||||
keynum = e.which;
|
||||
}
|
||||
if(keynum != 8 && this.$refs.text.value.length >= this.textLimit) {
|
||||
e.preventDefault()
|
||||
return false;
|
||||
}
|
||||
if(keynum == 13 && lines == this.$refs.text.rows) {
|
||||
e.preventDefault()
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "vue-select/src/scss/vue-select.scss";
|
||||
.planning {
|
||||
overflow: auto;
|
||||
|
||||
td {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
#text {
|
||||
height: 5.5rem;
|
||||
padding: 1rem 2rem;
|
||||
resize: none;
|
||||
width: 37rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.dynCell {
|
||||
position: relative;
|
||||
&:hover .btn {
|
||||
opacity: 1;
|
||||
}
|
||||
$buttonBgColor: #4d4d4d;
|
||||
|
||||
// Reset button Style to nice one
|
||||
button {
|
||||
display: inline;
|
||||
border: none;
|
||||
padding: 0.6rem 3rem;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
background: $buttonBgColor;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
transition: background 250ms ease-in-out, transform 150ms ease;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
button:disabled,
|
||||
button[disabled] {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
button:focus {
|
||||
background: darken($buttonBgColor, 10%);
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: 1px solid #fff;
|
||||
outline-offset: -4px;
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
.btn {
|
||||
position: absolute;
|
||||
top: 0.1rem;
|
||||
right: 0.1rem;
|
||||
padding: 0;
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
margin: 0;
|
||||
background-color: $buttonBgColor;
|
||||
opacity: 0;
|
||||
transition: 200ms opacity;
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: darken($buttonBgColor, 10%);
|
||||
}
|
||||
&.btn-clear {
|
||||
color: darken($color: #fff, $amount: 5);
|
||||
}
|
||||
&:nth-last-child(1) {
|
||||
right: 2.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
68
resources/js/components/PlanningsComponent.vue
Normal file
68
resources/js/components/PlanningsComponent.vue
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div class="plannings">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-1 text-center">
|
||||
<button class="sticky-top btn btn-primary mt-5" @click="goToPlanning(false)">←</button>
|
||||
</div>
|
||||
<div class="col-10" v-if="weeks.length">
|
||||
<template v-for="week in weeks">
|
||||
<planning-component
|
||||
:key="week.id"
|
||||
:db-current-week='week'
|
||||
:db-models='models'
|
||||
:db-types='types'
|
||||
:db-rows='rows'></planning-component>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="col-10">
|
||||
<h3>Pas de planning à afficher</h3>
|
||||
</div>
|
||||
<div class="col-1 text-center">
|
||||
<button class="sticky-top btn btn-primary mt-5" @click="goToPlanning(true)">→</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
mounted() {
|
||||
this.weeks = JSON.parse(this.dbWeeks);
|
||||
this.models = JSON.parse(this.dbModels);
|
||||
this.types = JSON.parse(this.dbTypes);
|
||||
this.rows = JSON.parse(this.dbRows);
|
||||
this.currentDate = JSON.parse(this.browseDate);
|
||||
},
|
||||
props: ["browseDate", "dbWeeks", "dbModels", "dbTypes", "dbRows"],
|
||||
data() {
|
||||
return {
|
||||
currentDate: null,
|
||||
types: [],
|
||||
rows: [],
|
||||
models: [],
|
||||
weeks: []
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
methods: {
|
||||
goToPlanning(forward) {
|
||||
let date = this.moment(this.currentDate).startOf('week').subtract(1, 'day');
|
||||
if(forward) {
|
||||
date = this.moment(this.currentDate).endOf('week').add(1, 'day')
|
||||
}
|
||||
let url = new URL(this.route('planning.index'))
|
||||
location = url.origin+ url.pathname + '?p='+date.unix()
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "vue-select/src/scss/vue-select.scss";
|
||||
.plannings {
|
||||
.sticky-top {
|
||||
top: 20rem !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
45
resources/js/modules/route.js
vendored
Normal file
45
resources/js/modules/route.js
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
export default {
|
||||
methods: {
|
||||
route(routeName, parameters = []) {
|
||||
let route = Object.keys(window._routes).indexOf(routeName) !== -1 ?
|
||||
window._routes[routeName] : null;
|
||||
if (!route) {
|
||||
return route;
|
||||
}
|
||||
// copy parameters
|
||||
let iParameters = Object.assign({}, parameters);
|
||||
let origin = window.location.origin;
|
||||
let pathname = route.replace(origin, '');
|
||||
let hash = window.location.hash;
|
||||
|
||||
// replace in path name
|
||||
for (let paramName in iParameters) {
|
||||
let paramValue = parameters[paramName];
|
||||
if (pathname.indexOf(paramName) !== -1) {
|
||||
pathname = pathname.replace(paramName, paramValue);
|
||||
}
|
||||
delete parameters[paramName];
|
||||
}
|
||||
|
||||
// search, dont copy if target url is different than actual
|
||||
let search = pathname == window.location.pathname ? window.location.search : ''
|
||||
|
||||
for (let paramName in parameters) {
|
||||
let paramValue = parameters[paramName];
|
||||
if (search.indexOf(paramName + "=") >= 0) {
|
||||
prefix = search.substring(0, search.indexOf(paramName + "="));
|
||||
suffix = search.substring(search.indexOf(paramName + "="));
|
||||
suffix = suffix.substring(suffix.indexOf("=") + 1);
|
||||
suffix = (suffix.indexOf("&") >= 0) ? suffix.substring(suffix.indexOf("&")) : '';
|
||||
search = prefix + paramName + "=" + paramValue + suffix;
|
||||
} else if (search.indexOf("?") < 0) {
|
||||
search += "?" + paramName + "=" + paramValue
|
||||
} else {
|
||||
search += "&" + paramName + "=" + paramValue
|
||||
}
|
||||
}
|
||||
return origin + pathname + search + hash;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>Support LDCWEB</title>
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
|
|
|
|||
41
resources/views/planning/index.blade.php
Normal file
41
resources/views/planning/index.blade.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
@extends('layout.app')
|
||||
|
||||
@section('content')
|
||||
<div id="planning" class="container container-xxl vueComponent">
|
||||
<div class="row mb-3 sticky-top bg-white">
|
||||
<div class="col-2">
|
||||
<h1><a href="{{ route('planning.index') }}">{{__('Planning') }}</a></h1>
|
||||
</div>
|
||||
<div class="col pt-2">
|
||||
<h4 class="d-inline me-3">Légende</h4>
|
||||
<div class="d-inline">
|
||||
@foreach(CellType::toArray() as $type)
|
||||
<span style="background-color: {{ $type->value }}; border: 1px solid black;">{{ $type->label }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<plannings-component
|
||||
browse-date='@json($date)'
|
||||
db-weeks='@json($weeks)'
|
||||
db-models='@json(CellModel::toArray())'
|
||||
db-types='@json(CellType::toArray())'
|
||||
db-rows='@json(TableRow::toArray())'></plannings-component>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scriptsConstants')
|
||||
<script>
|
||||
window._routes = {
|
||||
"planning.index": "{{ route('planning.index') }}",
|
||||
"ticket.show": "{{ route('ticket.show', 'TICKET') }}",
|
||||
"projects.show": "{{ route('projects.show', 'PROJECT') }}",
|
||||
"sites.show": "{{ route('sites.show', 'SITE') }}",
|
||||
"ticket.index": "{{ route('ticket.index') }}",
|
||||
"projects.index": "{{ route('projects.index') }}",
|
||||
"sites.index": "{{ route('sites.index') }}",
|
||||
"planning.setCellContent": "{{ route('planning.setCellContent') }}",
|
||||
"planning.eraseCellContent": "{{ route('planning.eraseCellContent') }}"
|
||||
};
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
use App\Http\Controllers\FileController;
|
||||
use App\Http\Controllers\HomeController;
|
||||
use App\Http\Controllers\LogController;
|
||||
use App\Http\Controllers\PlanningController;
|
||||
use App\Http\Controllers\ProjectController;
|
||||
use App\Http\Controllers\SiteController;
|
||||
use App\Http\Controllers\TicketController;
|
||||
|
|
@ -61,16 +62,24 @@ Route::middleware(['auth'])->group(function () {
|
|||
->middleware('can:reopen,ticket')
|
||||
->name('reopen');
|
||||
Route::post('/{ticket}/status', [TicketController::class, 'changeStatus'])
|
||||
->middleware('can:changeStatus,ticket')
|
||||
->name('changeStatus');
|
||||
->middleware('can:changeStatus,ticket')
|
||||
->name('changeStatus');
|
||||
Route::post('/{ticket}/subscribe', [TicketController::class, 'subscribe'])
|
||||
->middleware('can:suscribeToTicket,ticket')
|
||||
->name('subscribe');
|
||||
->middleware('can:suscribeToTicket,ticket')
|
||||
->name('subscribe');
|
||||
Route::put('/{ticket}/archive', [TicketController::class, 'setToArchive'])
|
||||
->middleware('can:changeStatus,ticket')
|
||||
->name('archive');
|
||||
->middleware('can:changeStatus,ticket')
|
||||
->name('archive');
|
||||
Route::put('/{ticket}/message/create', [TicketController::class, 'createMessage'])
|
||||
->middleware('can:createMessage,ticket')
|
||||
->name('message.create');
|
||||
->middleware('can:createMessage,ticket')
|
||||
->name('message.create');
|
||||
});
|
||||
|
||||
Route::prefix('/planning')->name('planning.')->group(function () {
|
||||
Route::get('/', [PlanningController::class, 'index'])->name('index');
|
||||
Route::post('/setCellContent', [PlanningController::class, 'setCellContent'])
|
||||
->name('setCellContent');
|
||||
Route::post('/eraseCellContent', [PlanningController::class, 'eraseCellContent'])
|
||||
->name('eraseCellContent');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue