bug - 14 - Fix create modal on calendar cell + add remove option for existing

This commit is contained in:
myrmidex 2026-01-04 14:52:46 +01:00
parent d57af05974
commit 7412316746
3 changed files with 393 additions and 9 deletions

View file

@ -2,7 +2,11 @@
namespace App\Livewire\Schedule; namespace App\Livewire\Schedule;
use App\Models\Dish;
use App\Models\Schedule;
use App\Models\ScheduledUserDish;
use App\Models\User; use App\Models\User;
use App\Models\UserDish;
use Carbon\Carbon; use Carbon\Carbon;
use DishPlanner\Schedule\Services\ScheduleCalendarService; use DishPlanner\Schedule\Services\ScheduleCalendarService;
use DishPlanner\ScheduledUserDish\Actions\DeleteScheduledUserDishForDateAction; use DishPlanner\ScheduledUserDish\Actions\DeleteScheduledUserDishForDateAction;
@ -21,6 +25,21 @@ class ScheduleCalendar extends Component
public $regenerateDate = null; public $regenerateDate = null;
public $regenerateUserId = null; public $regenerateUserId = null;
// Edit dish modal
public $showEditDishModal = false;
public $editDate = null;
public $editUserId = null;
public $selectedDishId = null;
public $availableDishes = [];
// Add dish modal
public $showAddDishModal = false;
public $addDate = null;
public $addUserId = null;
public $addSelectedDishId = null;
public $addAvailableUsers = [];
public $addAvailableDishes = [];
public function mount(): void public function mount(): void
{ {
$this->currentMonth = now()->month; $this->currentMonth = now()->month;
@ -144,6 +163,233 @@ public function cancel(): void
$this->showRegenerateModal = false; $this->showRegenerateModal = false;
$this->regenerateDate = null; $this->regenerateDate = null;
$this->regenerateUserId = null; $this->regenerateUserId = null;
$this->showEditDishModal = false;
$this->editDate = null;
$this->editUserId = null;
$this->selectedDishId = null;
$this->availableDishes = [];
$this->showAddDishModal = false;
$this->addDate = null;
$this->addUserId = null;
$this->addSelectedDishId = null;
$this->addAvailableUsers = [];
$this->addAvailableDishes = [];
}
public function removeDish($date, $userId): void
{
try {
if (!$this->authorizeUser($userId)) {
session()->flash('error', 'Unauthorized action.');
return;
}
$schedule = Schedule::where('planner_id', auth()->id())
->where('date', $date)
->first();
if ($schedule) {
ScheduledUserDish::where('schedule_id', $schedule->id)
->where('user_id', $userId)
->delete();
}
$this->loadCalendar();
session()->flash('success', 'Dish removed successfully!');
} catch (Exception $e) {
Log::error('Remove dish failed', ['exception' => $e, 'date' => $date, 'userId' => $userId]);
session()->flash('error', 'Unable to remove dish. Please try again.');
}
}
public function openAddDishModal($date): void
{
$this->addDate = $date;
// Load all users for this planner
$this->addAvailableUsers = User::where('planner_id', auth()->id())
->orderBy('name')
->get();
$this->addAvailableDishes = [];
$this->addUserId = null;
$this->addSelectedDishId = null;
$this->showAddDishModal = true;
}
public function updatedAddUserId($value): void
{
if ($value) {
// Load dishes available for selected user
$this->addAvailableDishes = Dish::whereHas('users', function ($query) use ($value) {
$query->where('users.id', $value);
})->orderBy('name')->get();
} else {
$this->addAvailableDishes = [];
}
$this->addSelectedDishId = null;
}
public function saveAddDish(): void
{
try {
if (!$this->addUserId) {
session()->flash('error', 'Please select a user.');
return;
}
if (!$this->authorizeUser($this->addUserId)) {
session()->flash('error', 'Unauthorized action.');
return;
}
if (!$this->addSelectedDishId) {
session()->flash('error', 'Please select a dish.');
return;
}
// Find or create the schedule for this date
$schedule = Schedule::firstOrCreate(
[
'planner_id' => auth()->id(),
'date' => $this->addDate,
],
['is_skipped' => false]
);
// Check if user already has a dish scheduled for this date
$existing = ScheduledUserDish::where('schedule_id', $schedule->id)
->where('user_id', $this->addUserId)
->first();
if ($existing) {
session()->flash('error', 'This user already has a dish scheduled for this date. Use Edit instead.');
return;
}
// Find the UserDish for this user and dish
$userDish = UserDish::where('user_id', $this->addUserId)
->where('dish_id', $this->addSelectedDishId)
->first();
if (!$userDish) {
session()->flash('error', 'This dish is not assigned to this user.');
return;
}
// Create the scheduled user dish
ScheduledUserDish::create([
'schedule_id' => $schedule->id,
'user_id' => $this->addUserId,
'user_dish_id' => $userDish->id,
'is_skipped' => false,
]);
$this->showAddDishModal = false;
$this->addDate = null;
$this->addUserId = null;
$this->addSelectedDishId = null;
$this->addAvailableUsers = [];
$this->addAvailableDishes = [];
$this->loadCalendar();
session()->flash('success', 'Dish added successfully!');
} catch (Exception $e) {
Log::error('Add dish failed', ['exception' => $e]);
session()->flash('error', 'Unable to add dish. Please try again.');
}
}
public function editDish($date, $userId): void
{
if (!$this->authorizeUser($userId)) {
session()->flash('error', 'Unauthorized action.');
return;
}
$this->editDate = $date;
$this->editUserId = $userId;
// Load dishes available for this user (via UserDish pivot)
$this->availableDishes = Dish::whereHas('users', function ($query) use ($userId) {
$query->where('users.id', $userId);
})->orderBy('name')->get();
// Get currently selected dish for this date/user if exists
$schedule = Schedule::where('planner_id', auth()->id())
->where('date', $date)
->first();
if ($schedule) {
$scheduledUserDish = ScheduledUserDish::where('schedule_id', $schedule->id)
->where('user_id', $userId)
->first();
if ($scheduledUserDish && $scheduledUserDish->userDish) {
$this->selectedDishId = $scheduledUserDish->userDish->dish_id;
}
}
$this->showEditDishModal = true;
}
public function saveDish(): void
{
try {
if (!$this->authorizeUser($this->editUserId)) {
session()->flash('error', 'Unauthorized action.');
return;
}
if (!$this->selectedDishId) {
session()->flash('error', 'Please select a dish.');
return;
}
// Find or create the schedule for this date
$schedule = Schedule::firstOrCreate(
[
'planner_id' => auth()->id(),
'date' => $this->editDate,
],
['is_skipped' => false]
);
// Find the UserDish for this user and dish
$userDish = UserDish::where('user_id', $this->editUserId)
->where('dish_id', $this->selectedDishId)
->first();
if (!$userDish) {
session()->flash('error', 'This dish is not assigned to this user.');
return;
}
// Update or create the scheduled user dish
ScheduledUserDish::updateOrCreate(
[
'schedule_id' => $schedule->id,
'user_id' => $this->editUserId,
],
[
'user_dish_id' => $userDish->id,
'is_skipped' => false,
]
);
$this->showEditDishModal = false;
$this->editDate = null;
$this->editUserId = null;
$this->selectedDishId = null;
$this->availableDishes = [];
$this->loadCalendar();
session()->flash('success', 'Dish updated successfully!');
} catch (Exception $e) {
Log::error('Save dish failed', ['exception' => $e]);
session()->flash('error', 'Unable to save dish. Please try again.');
}
} }
public function getMonthNameProperty(): string public function getMonthNameProperty(): string

View file

@ -29,7 +29,7 @@ public function up(): void
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('user_dish_id')->references('id')->on('user_dishes')->onDelete('cascade'); $table->foreign('user_dish_id')->references('id')->on('user_dishes')->onDelete('cascade');
$table->unique(['schedule_id', 'user_dish_id']); $table->unique(['schedule_id', 'user_id']);
$table->index('user_dish_id'); $table->index('user_dish_id');
}); });
} }

View file

@ -48,9 +48,15 @@ class="px-4 py-2 bg-gray-700 text-accent-blue rounded hover:bg-gray-600 transiti
{{ $dayData['isToday'] ? 'border-2 border-accent-blue' : 'border-gray-600' }}"> {{ $dayData['isToday'] ? 'border-2 border-accent-blue' : 'border-gray-600' }}">
@if($dayData['day']) @if($dayData['day'])
<!-- Day number --> <!-- Day number and add button -->
<div class="font-bold mb-2 {{ $dayData['isToday'] ? 'text-accent-blue' : 'text-gray-100' }}"> <div class="flex justify-between items-center mb-2">
<span class="font-bold {{ $dayData['isToday'] ? 'text-accent-blue' : 'text-gray-100' }}">
{{ $dayData['day'] }} {{ $dayData['day'] }}
</span>
<button wire:click="openAddDishModal('{{ $dayData['date']->format('Y-m-d') }}')"
class="w-5 h-5 bg-gray-600 hover:bg-primary text-gray-300 hover:text-white rounded flex items-center justify-center text-sm transition-colors duration-200">
+
</button>
</div> </div>
<!-- Scheduled dishes --> <!-- Scheduled dishes -->
@ -76,14 +82,22 @@ class="text-white hover:text-gray-300">
@click.away="showActions = false" @click.away="showActions = false"
x-cloak x-cloak
class="absolute bg-gray-700 border border-secondary rounded mt-4 ml-4 shadow-lg z-10"> class="absolute bg-gray-700 border border-secondary rounded mt-4 ml-4 shadow-lg z-10">
<button wire:click="editDish('{{ $dayData['date']->format('Y-m-d') }}', {{ $scheduled->user->id }})"
class="block w-full text-left px-3 py-1 text-xs hover:bg-gray-600 text-white">
Edit
</button>
<button wire:click="regenerateForUserDate('{{ $dayData['date']->format('Y-m-d') }}', {{ $scheduled->user->id }})" <button wire:click="regenerateForUserDate('{{ $dayData['date']->format('Y-m-d') }}', {{ $scheduled->user->id }})"
class="block w-full text-left px-3 py-1 text-xs hover:bg-gray-600 text-accent-blue"> class="block w-full text-left px-3 py-1 text-xs hover:bg-gray-600 text-accent-blue">
Regenerate Regenerate
</button> </button>
<button wire:click="skipDay('{{ $dayData['date']->format('Y-m-d') }}', {{ $scheduled->user->id }})" <button wire:click="skipDay('{{ $dayData['date']->format('Y-m-d') }}', {{ $scheduled->user->id }})"
class="block w-full text-left px-3 py-1 text-xs hover:bg-gray-600 text-danger"> class="block w-full text-left px-3 py-1 text-xs hover:bg-gray-600 text-warning">
Skip Skip
</button> </button>
<button wire:click="removeDish('{{ $dayData['date']->format('Y-m-d') }}', {{ $scheduled->user->id }})"
class="block w-full text-left px-3 py-1 text-xs hover:bg-gray-600 text-danger">
Remove
</button>
</div> </div>
</div> </div>
</div> </div>
@ -113,6 +127,10 @@ class="block w-full text-left px-3 py-1 text-xs hover:bg-gray-600 text-danger">
<span class="text-xs ml-2">(Today)</span> <span class="text-xs ml-2">(Today)</span>
@endif @endif
</div> </div>
<button wire:click="openAddDishModal('{{ $dayData['date']->format('Y-m-d') }}')"
class="w-7 h-7 bg-gray-600 hover:bg-primary text-gray-300 hover:text-white rounded flex items-center justify-center text-lg transition-colors duration-200">
+
</button>
</div> </div>
<!-- Scheduled dishes --> <!-- Scheduled dishes -->
@ -141,14 +159,22 @@ class="text-white hover:text-gray-300 p-1">
@click.away="showActions = false" @click.away="showActions = false"
x-cloak x-cloak
class="absolute right-4 bg-gray-700 border border-secondary rounded shadow-lg z-10"> class="absolute right-4 bg-gray-700 border border-secondary rounded shadow-lg z-10">
<button wire:click="editDish('{{ $dayData['date']->format('Y-m-d') }}', {{ $scheduled->user->id }})"
class="block w-full text-left px-4 py-2 text-sm hover:bg-gray-600 text-white">
Edit
</button>
<button wire:click="regenerateForUserDate('{{ $dayData['date']->format('Y-m-d') }}', {{ $scheduled->user->id }})" <button wire:click="regenerateForUserDate('{{ $dayData['date']->format('Y-m-d') }}', {{ $scheduled->user->id }})"
class="block w-full text-left px-4 py-2 text-sm hover:bg-gray-600 text-accent-blue"> class="block w-full text-left px-4 py-2 text-sm hover:bg-gray-600 text-accent-blue">
Regenerate Regenerate
</button> </button>
<button wire:click="skipDay('{{ $dayData['date']->format('Y-m-d') }}', {{ $scheduled->user->id }})" <button wire:click="skipDay('{{ $dayData['date']->format('Y-m-d') }}', {{ $scheduled->user->id }})"
class="block w-full text-left px-4 py-2 text-sm hover:bg-gray-600 text-danger"> class="block w-full text-left px-4 py-2 text-sm hover:bg-gray-600 text-warning">
Skip Skip
</button> </button>
<button wire:click="removeDish('{{ $dayData['date']->format('Y-m-d') }}', {{ $scheduled->user->id }})"
class="block w-full text-left px-4 py-2 text-sm hover:bg-gray-600 text-danger">
Remove
</button>
</div> </div>
</div> </div>
</div> </div>
@ -186,6 +212,118 @@ class="px-4 py-2 bg-warning text-white rounded hover:bg-yellow-600 transition-co
</div> </div>
@endif @endif
<!-- Edit Dish Modal -->
@if($showEditDishModal)
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-gray-600 border-2 border-secondary rounded-lg p-6 w-full max-w-md mx-4">
<h2 class="text-xl text-accent-blue mb-4">Edit Dish</h2>
<p class="text-gray-300 text-sm mb-4">
Choose a dish for <strong>{{ \App\Models\User::find($editUserId)?->name }}</strong> on {{ \Carbon\Carbon::parse($editDate)->format('M j, Y') }}
</p>
@if(count($availableDishes) > 0)
<div class="mb-6">
<label class="block text-sm font-medium mb-2">Dish</label>
<select wire:model="selectedDishId"
class="w-full p-2 border rounded bg-gray-700 border-secondary text-gray-100 focus:bg-gray-900 focus:outline-none focus:border-accent-blue">
<option value="">Select a dish...</option>
@foreach($availableDishes as $dish)
<option value="{{ $dish->id }}">{{ $dish->name }}</option>
@endforeach
</select>
</div>
@else
<div class="mb-6">
<p class="text-gray-400 text-sm italic">
No dishes available for this user.
<a href="{{ route('dishes.index') }}" class="text-accent-blue hover:underline">Add dishes</a> first.
</p>
</div>
@endif
<div class="flex justify-end space-x-3">
<button wire:click="cancel"
class="px-4 py-2 border-2 border-secondary text-gray-100 rounded hover:bg-gray-700 transition-colors duration-200">
Cancel
</button>
@if(count($availableDishes) > 0)
<button wire:click="saveDish"
class="px-4 py-2 bg-primary text-white rounded hover:bg-secondary transition-colors duration-200">
Save
</button>
@endif
</div>
</div>
</div>
@endif
<!-- Add Dish Modal -->
@if($showAddDishModal)
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-gray-600 border-2 border-secondary rounded-lg p-6 w-full max-w-md mx-4">
<h2 class="text-xl text-accent-blue mb-4">Add Dish</h2>
<p class="text-gray-300 text-sm mb-4">
Add a dish for {{ \Carbon\Carbon::parse($addDate)->format('M j, Y') }}
</p>
@if(count($addAvailableUsers) > 0)
<!-- User selection -->
<div class="mb-4">
<label class="block text-sm font-medium mb-2">User</label>
<select wire:model.live="addUserId"
class="w-full p-2 border rounded bg-gray-700 border-secondary text-gray-100 focus:bg-gray-900 focus:outline-none focus:border-accent-blue">
<option value="">Select a user...</option>
@foreach($addAvailableUsers as $user)
<option value="{{ $user->id }}">{{ $user->name }}</option>
@endforeach
</select>
</div>
<!-- Dish selection (shows after user is selected) -->
@if($addUserId)
<div class="mb-6">
<label class="block text-sm font-medium mb-2">Dish</label>
@if(count($addAvailableDishes) > 0)
<select wire:model="addSelectedDishId"
class="w-full p-2 border rounded bg-gray-700 border-secondary text-gray-100 focus:bg-gray-900 focus:outline-none focus:border-accent-blue">
<option value="">Select a dish...</option>
@foreach($addAvailableDishes as $dish)
<option value="{{ $dish->id }}">{{ $dish->name }}</option>
@endforeach
</select>
@else
<p class="text-gray-400 text-sm italic">
No dishes available for this user.
<a href="{{ route('dishes.index') }}" class="text-accent-blue hover:underline">Add dishes</a> first.
</p>
@endif
</div>
@endif
@else
<div class="mb-6">
<p class="text-gray-400 text-sm italic">
No users available.
<a href="{{ route('users.index') }}" class="text-accent-blue hover:underline">Add users</a> first.
</p>
</div>
@endif
<div class="flex justify-end space-x-3">
<button wire:click="cancel"
class="px-4 py-2 border-2 border-secondary text-gray-100 rounded hover:bg-gray-700 transition-colors duration-200">
Cancel
</button>
@if(count($addAvailableUsers) > 0 && count($addAvailableDishes) > 0)
<button wire:click="saveAddDish"
class="px-4 py-2 bg-primary text-white rounded hover:bg-secondary transition-colors duration-200">
Add
</button>
@endif
</div>
</div>
</div>
@endif
<style> <style>
[x-cloak] { display: none !important; } [x-cloak] { display: none !important; }
</style> </style>