Implement availability schedules
This commit is contained in:
parent
6580fc1bde
commit
c218e44b4e
|
@ -5,6 +5,7 @@ namespace App\Console;
|
|||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
use App\Jobs\IncrementAvailabilityMinutesJob;
|
||||
use App\Jobs\UpdateAvailabilityWithSchedulesJob;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
|
@ -14,6 +15,7 @@ class Kernel extends ConsoleKernel
|
|||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
$schedule->job(new IncrementAvailabilityMinutesJob)->everyMinute();
|
||||
$schedule->job(new UpdateAvailabilityWithSchedulesJob)->everyThirtyMinutes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ScheduleSlots;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ScheduleSlotsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
return ScheduleSlots::select("day", "slot")
|
||||
->where('user', $request->user()->id)
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
ScheduleSlots::where('user', $request->user()->id)->delete();
|
||||
|
||||
$schedules = array_map(function ($schedule) {
|
||||
$schedule["user"] = auth()->id();
|
||||
return $schedule;
|
||||
}, $request->input('schedules'));
|
||||
|
||||
return ScheduleSlots::insert($schedules);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
use App\Models\ScheduleSlots;
|
||||
use App\Models\User;
|
||||
|
||||
class UpdateAvailabilityWithSchedulesJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
//Days starts from 0 in frontend
|
||||
$curr_day = now()->dayOfWeek-1;
|
||||
//There are 48 slots of 30 minutes, starting from 0 (00:00-00:30) to 47 (23:30-00:00)
|
||||
$curr_slot = now()->hour * 2 + (now()->minute >= 30);
|
||||
|
||||
$scheduled_users = ScheduleSlots::where([
|
||||
["day", "=", $curr_day],
|
||||
["slot", "=", $curr_slot]
|
||||
])->pluck("user");
|
||||
|
||||
User::whereIn("id", $scheduled_users)
|
||||
->where([
|
||||
["banned", "=", 0],
|
||||
["availability_manual_mode", "=", 0]
|
||||
])
|
||||
->update(['available' => 1]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ScheduleSlots extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'day',
|
||||
'slot'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the user that owns the phone.
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
protected static function defAttr($messages, $attribute){
|
||||
|
||||
if(isset($messages[$attribute])){
|
||||
return $messages[$attribute];
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
"user" => auth()->id(),
|
||||
];
|
||||
|
||||
return $attributes[$attribute];
|
||||
}
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($messages) {
|
||||
$messages->user = self::defAttr($messages, "user");
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('schedule_slots', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedTinyInteger('day');
|
||||
$table->unsignedTinyInteger('slot');
|
||||
$table->unsignedBigInteger('user')->unsigned();
|
||||
$table->foreign('user')->references('id')->on('users')->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('schedule_slots');
|
||||
}
|
||||
};
|
|
@ -3,6 +3,7 @@
|
|||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\AuthController;
|
||||
use App\Http\Controllers\UserController;
|
||||
use App\Http\Controllers\ScheduleSlotsController;
|
||||
use App\Http\Controllers\AvailabilityController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
@ -27,6 +28,9 @@ Route::middleware('auth:sanctum')->group( function () {
|
|||
|
||||
Route::get('/list', [UserController::class, 'index']);
|
||||
|
||||
Route::get('/schedules', [ScheduleSlotsController::class, 'index']);
|
||||
Route::post('/schedules', [ScheduleSlotsController::class, 'store']);
|
||||
|
||||
Route::get('/availability', [AvailabilityController::class, 'get']);
|
||||
Route::post('/availability', [AvailabilityController::class, 'updateAvailability']);
|
||||
Route::post('/manual_mode', [AvailabilityController::class, 'updateAvailabilityManualMode']);
|
||||
|
|
|
@ -11,22 +11,22 @@
|
|||
<td style="background-color: white;"></td>
|
||||
<ng-container *ngIf="orientation === 'portrait'">
|
||||
<ng-container *ngFor="let day of days; let i = index">
|
||||
<td class="day" (click)="selectDay(i)">{{ day.short|translate }}</td>
|
||||
<td class="day" (click)="selectEntireDay(i)">{{ day.short|translate }}</td>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="orientation === 'landscape'">
|
||||
<ng-container *ngFor="let hour of hours">
|
||||
<td class="hour" (click)="selectHour(hour)">{{ hour }}</td>
|
||||
<ng-container *ngFor="let slot of slots; odd as isOdd; even as isEven;">
|
||||
<td class="hour" (click)="selectEverySlotWithHour(slot)">{{ isEven ? (slot/2) : ((slot-1)/2) }}:{{ isEven ? "00" : "30" }}</td>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="scheduler_body" *ngIf="orientation === 'portrait'">
|
||||
<ng-container *ngFor="let hour of hours">
|
||||
<ng-container *ngFor="let slot of slots">
|
||||
<tr>
|
||||
<td class="hour" (click)="selectHour(hour)">{{ hour }}</td>
|
||||
<td class="hour" (click)="selectEverySlotWithHour(slot)">{{ slot }}</td>
|
||||
<ng-container *ngFor="let day of days; let i = index">
|
||||
<td class="hour-cell" [class.highlighted] = "isCellSelected(i, hour)" (mousedown)="mouseDownCell(i, hour)" (mouseup)="mouseUpCell()" (mouseover)="mouseOverCell(i, hour)"></td>
|
||||
<td class="hour-cell" [class.highlighted] = "isCellSelected(i, slot)" (mousedown)="mouseDownCell(i, slot)" (mouseup)="mouseUpCell()" (mouseover)="mouseOverCell(i, slot)"></td>
|
||||
</ng-container>
|
||||
</tr>
|
||||
</ng-container>
|
||||
|
@ -34,9 +34,9 @@
|
|||
<tbody id="scheduler_body" *ngIf="orientation === 'landscape'">
|
||||
<ng-container *ngFor="let day of days; let i = index">
|
||||
<tr>
|
||||
<td class="day" (click)="selectDay(i)">{{ day.short|translate }}</td>
|
||||
<ng-container *ngFor="let hour of hours">
|
||||
<td class="hour-cell" [class.highlighted] = "isCellSelected(i, hour)" (mousedown)="mouseDownCell(i, hour)" (mouseup)="mouseUpCell()" (mouseover)="mouseOverCell(i, hour)"></td>
|
||||
<td class="day" (click)="selectEntireDay(i)">{{ day.short|translate }}</td>
|
||||
<ng-container *ngFor="let slot of slots">
|
||||
<td class="hour-cell" [class.highlighted] = "isCellSelected(i, slot)" (mousedown)="mouseDownCell(i, slot)" (mouseup)="mouseUpCell()" (mouseover)="mouseOverCell(i, slot)"></td>
|
||||
</ng-container>
|
||||
</tr>
|
||||
</ng-container>
|
||||
|
|
|
@ -41,37 +41,12 @@ export class ModalAvailabilityScheduleComponent implements OnInit {
|
|||
short: 'sunday_short'
|
||||
}
|
||||
];
|
||||
public hours = [
|
||||
"0:00", "0:30",
|
||||
"1:00", "1:30",
|
||||
"2:00", "2:30",
|
||||
"3:00", "3:30",
|
||||
"4:00", "4:30",
|
||||
"5:00", "5:30",
|
||||
"6:00", "6:30",
|
||||
"7:00", "7:30",
|
||||
"8:00", "8:30",
|
||||
"9:00", "9:30",
|
||||
"10:00", "10:30",
|
||||
"11:00", "11:30",
|
||||
"12:00", "12:30",
|
||||
"13:00", "13:30",
|
||||
"14:00", "14:30",
|
||||
"15:00", "15:30",
|
||||
"16:00", "16:30",
|
||||
"17:00", "17:30",
|
||||
"18:00", "18:30",
|
||||
"19:00", "19:30",
|
||||
"20:00", "20:30",
|
||||
"21:00", "21:30",
|
||||
"22:00", "22:30",
|
||||
"23:00", "23:30",
|
||||
];
|
||||
public slots = Array(48).fill(0).map((x,i)=>i);
|
||||
|
||||
public selectedCells: any = [];
|
||||
|
||||
//Used for "select all"
|
||||
public selectedHours: string[] = [];
|
||||
public selectedSlots: number[] = [];
|
||||
public selectedDays: number[] = [];
|
||||
|
||||
public isSelecting = false;
|
||||
|
@ -91,13 +66,9 @@ export class ModalAvailabilityScheduleComponent implements OnInit {
|
|||
|
||||
ngOnInit(): void {
|
||||
this.orientation = window.innerHeight > window.innerWidth ? "portrait" : "landscape";
|
||||
if(localStorage.getItem('schedules') === null) {
|
||||
this.api.get("schedules").then((response: any) => {
|
||||
this.loadSchedules(response.schedules);
|
||||
});
|
||||
} else {
|
||||
this.loadSchedules(JSON.parse((localStorage.getItem('schedules') as string)));
|
||||
}
|
||||
this.api.get("schedules").then((response: any) => {
|
||||
this.loadSchedules(response);
|
||||
});
|
||||
}
|
||||
|
||||
saveChanges() {
|
||||
|
@ -105,79 +76,72 @@ export class ModalAvailabilityScheduleComponent implements OnInit {
|
|||
this.api.post("schedules", {
|
||||
schedules: this.selectedCells
|
||||
});
|
||||
localStorage.removeItem('schedules');
|
||||
this.bsModalRef.hide();
|
||||
}
|
||||
|
||||
saveChangesInLocal() {
|
||||
localStorage.setItem('schedules', JSON.stringify(this.selectedCells));
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(event: Event) {
|
||||
this.orientation = window.innerHeight > window.innerWidth ? "portrait" : "landscape";
|
||||
}
|
||||
|
||||
isCellSelected(day: number, hour: string) {
|
||||
return this.selectedCells.find((cell: any) => cell.day === day && cell.hour === hour);
|
||||
isCellSelected(day: number, slot: number) {
|
||||
return this.selectedCells.find((cell: any) => cell.day === day && cell.slot === slot);
|
||||
}
|
||||
|
||||
toggleCell(day: number, hour: string) {
|
||||
if(!this.isCellSelected(day, hour)) {
|
||||
toggleCell(day: number, slot: number) {
|
||||
if(!this.isCellSelected(day, slot)) {
|
||||
this.selectedCells.push({
|
||||
day, hour
|
||||
day, slot
|
||||
});
|
||||
} else {
|
||||
this.selectedCells = this.selectedCells.filter((cell: any) => cell.day !== day || cell.hour !== hour);
|
||||
this.selectedCells = this.selectedCells.filter((cell: any) => cell.day !== day || cell.slot !== slot);
|
||||
}
|
||||
this.saveChangesInLocal();
|
||||
}
|
||||
|
||||
selectHour(hour: string) {
|
||||
console.log("Hour selected", hour);
|
||||
if(this.selectedHours.includes(hour)) {
|
||||
selectEverySlotWithHour(slot: number) {
|
||||
console.log("Slot hour selected", slot);
|
||||
debugger;
|
||||
if(this.selectedSlots.includes(slot)) {
|
||||
this.days.forEach((day: any, i: number) => {
|
||||
this.selectedCells = this.selectedCells.filter((cell: any) => cell.day !== i || cell.hour !== hour);
|
||||
this.selectedCells = this.selectedCells.filter((cell: any) => cell.day !== i || cell.slot !== slot);
|
||||
});
|
||||
this.selectedHours = this.selectedHours.filter((h: string) => h !== hour);
|
||||
this.selectedSlots = this.selectedSlots.filter((h: number) => h !== slot);
|
||||
} else {
|
||||
this.days.forEach((day: any, i: number) => {
|
||||
if(!this.isCellSelected(i, hour)) {
|
||||
if(!this.isCellSelected(i, slot)) {
|
||||
this.selectedCells.push({
|
||||
day: i, hour
|
||||
day: i, slot
|
||||
});
|
||||
}
|
||||
});
|
||||
this.selectedHours.push(hour);
|
||||
this.selectedSlots.push(slot);
|
||||
}
|
||||
this.saveChangesInLocal();
|
||||
}
|
||||
|
||||
selectDay(day: number) {
|
||||
selectEntireDay(day: number) {
|
||||
console.log("Day selected", day);
|
||||
if(this.selectedDays.includes(day)) {
|
||||
this.hours.forEach((hour: string) => {
|
||||
this.selectedCells = this.selectedCells.filter((cell: any) => cell.day !== day || cell.hour !== hour);
|
||||
});
|
||||
for (let slot = 0; slot < 48; slot++) {
|
||||
this.selectedCells = this.selectedCells.filter((cell: any) => cell.day !== day || cell.slot !== slot);
|
||||
}
|
||||
this.selectedDays = this.selectedDays.filter((i: number) => i !== day);
|
||||
} else {
|
||||
this.hours.forEach((hour: string) => {
|
||||
if(!this.isCellSelected(day, hour)) {
|
||||
for (let slot = 0; slot < 48; slot++) {
|
||||
if(!this.isCellSelected(day, slot)) {
|
||||
this.selectedCells.push({
|
||||
day, hour
|
||||
day, slot
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
this.selectedDays.push(day);
|
||||
}
|
||||
this.saveChangesInLocal();
|
||||
}
|
||||
|
||||
mouseDownCell(day: number, hour: string) {
|
||||
mouseDownCell(day: number, slot: number) {
|
||||
this.isSelecting = true;
|
||||
console.log("Mouse down");
|
||||
console.log("Hour cell selected", day, hour);
|
||||
this.toggleCell(day, hour);
|
||||
console.log("Slot cell selected", day, slot);
|
||||
this.toggleCell(day, slot);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -186,11 +150,11 @@ export class ModalAvailabilityScheduleComponent implements OnInit {
|
|||
console.log("Mouse up");
|
||||
}
|
||||
|
||||
mouseOverCell(day: number, hour: string) {
|
||||
mouseOverCell(day: number, slot: number) {
|
||||
if (this.isSelecting) {
|
||||
console.log("Mouse over", day, hour);
|
||||
console.log("Hour cell selected", day, hour);
|
||||
this.toggleCell(day, hour);
|
||||
console.log("Mouse over", day, slot);
|
||||
console.log("Slot cell selected", day, slot);
|
||||
this.toggleCell(day, slot);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</button>
|
||||
<br>
|
||||
</ng-container>
|
||||
<button type="button" class="btn btn-lg" (click)="openScheduleModal()" *ngIf="false">
|
||||
<button type="button" class="btn btn-lg" (click)="openScheduleModal()">
|
||||
{{ 'list.update_schedules'|translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue