bug - 14 - Add user multi-select

This commit is contained in:
myrmidex 2026-01-04 15:54:31 +01:00
parent 7412316746
commit 0baa87e373
5 changed files with 172 additions and 132 deletions

View file

@ -119,4 +119,14 @@ public function cancel()
$this->showDeleteModal = false; $this->showDeleteModal = false;
$this->reset(['name', 'selectedUsers', 'editingDish', 'deletingDish']); $this->reset(['name', 'selectedUsers', 'editingDish', 'deletingDish']);
} }
public function toggleAllUsers(): void
{
$users = User::where('planner_id', auth()->id())->get();
if (count($this->selectedUsers) === $users->count()) {
$this->selectedUsers = [];
} else {
$this->selectedUsers = $users->pluck('id')->map(fn($id) => (string) $id)->toArray();
}
}
} }

View file

@ -35,7 +35,7 @@ class ScheduleCalendar extends Component
// Add dish modal // Add dish modal
public $showAddDishModal = false; public $showAddDishModal = false;
public $addDate = null; public $addDate = null;
public $addUserId = null; public $addUserIds = [];
public $addSelectedDishId = null; public $addSelectedDishId = null;
public $addAvailableUsers = []; public $addAvailableUsers = [];
public $addAvailableDishes = []; public $addAvailableDishes = [];
@ -170,7 +170,7 @@ public function cancel(): void
$this->availableDishes = []; $this->availableDishes = [];
$this->showAddDishModal = false; $this->showAddDishModal = false;
$this->addDate = null; $this->addDate = null;
$this->addUserId = null; $this->addUserIds = [];
$this->addSelectedDishId = null; $this->addSelectedDishId = null;
$this->addAvailableUsers = []; $this->addAvailableUsers = [];
$this->addAvailableDishes = []; $this->addAvailableDishes = [];
@ -212,21 +212,37 @@ public function openAddDishModal($date): void
->get(); ->get();
$this->addAvailableDishes = []; $this->addAvailableDishes = [];
$this->addUserId = null; $this->addUserIds = [];
$this->addSelectedDishId = null; $this->addSelectedDishId = null;
$this->showAddDishModal = true; $this->showAddDishModal = true;
} }
public function updatedAddUserId($value): void public function toggleAllUsers(): void
{ {
if ($value) { if (count($this->addUserIds) === count($this->addAvailableUsers)) {
// Load dishes available for selected user $this->addUserIds = [];
$this->addAvailableDishes = Dish::whereHas('users', function ($query) use ($value) {
$query->where('users.id', $value);
})->orderBy('name')->get();
} else { } else {
$this->addUserIds = $this->addAvailableUsers->pluck('id')->map(fn($id) => (string) $id)->toArray();
}
$this->updateAvailableDishes();
}
public function updatedAddUserIds(): void
{
$this->updateAvailableDishes();
}
private function updateAvailableDishes(): void
{
if (empty($this->addUserIds)) {
$this->addAvailableDishes = []; $this->addAvailableDishes = [];
} else {
// Load dishes that ALL selected users have in common
$selectedCount = count($this->addUserIds);
$this->addAvailableDishes = Dish::whereHas('users', function ($query) {
$query->whereIn('users.id', $this->addUserIds);
}, '=', $selectedCount)->orderBy('name')->get();
} }
$this->addSelectedDishId = null; $this->addSelectedDishId = null;
} }
@ -234,13 +250,8 @@ public function updatedAddUserId($value): void
public function saveAddDish(): void public function saveAddDish(): void
{ {
try { try {
if (!$this->addUserId) { if (empty($this->addUserIds)) {
session()->flash('error', 'Please select a user.'); session()->flash('error', 'Please select at least one user.');
return;
}
if (!$this->authorizeUser($this->addUserId)) {
session()->flash('error', 'Unauthorized action.');
return; return;
} }
@ -258,49 +269,71 @@ public function saveAddDish(): void
['is_skipped' => false] ['is_skipped' => false]
); );
// Check if user already has a dish scheduled for this date $addedCount = 0;
$existing = ScheduledUserDish::where('schedule_id', $schedule->id) $skippedCount = 0;
->where('user_id', $this->addUserId)
->first();
if ($existing) { foreach ($this->addUserIds as $userId) {
session()->flash('error', 'This user already has a dish scheduled for this date. Use Edit instead.'); if (!$this->authorizeUser((int) $userId)) {
return; $skippedCount++;
continue;
}
// Check if user already has a dish scheduled for this date
$existing = ScheduledUserDish::where('schedule_id', $schedule->id)
->where('user_id', $userId)
->first();
if ($existing) {
$skippedCount++;
continue;
}
// Find the UserDish for this user and dish
$userDish = UserDish::where('user_id', $userId)
->where('dish_id', $this->addSelectedDishId)
->first();
if (!$userDish) {
$skippedCount++;
continue;
}
// Create the scheduled user dish
ScheduledUserDish::create([
'schedule_id' => $schedule->id,
'user_id' => $userId,
'user_dish_id' => $userDish->id,
'is_skipped' => false,
]);
$addedCount++;
} }
// Find the UserDish for this user and dish $this->closeAddDishModal();
$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(); $this->loadCalendar();
session()->flash('success', 'Dish added successfully!');
if ($addedCount > 0 && $skippedCount > 0) {
session()->flash('success', "Dish added for {$addedCount} user(s). {$skippedCount} user(s) skipped (already scheduled).");
} elseif ($addedCount > 0) {
session()->flash('success', "Dish added for {$addedCount} user(s)!");
} else {
session()->flash('error', 'No users could be scheduled. They may already have dishes for this date.');
}
} catch (Exception $e) { } catch (Exception $e) {
Log::error('Add dish failed', ['exception' => $e]); Log::error('Add dish failed', ['exception' => $e]);
session()->flash('error', 'Unable to add dish. Please try again.'); session()->flash('error', 'Unable to add dish. Please try again.');
} }
} }
private function closeAddDishModal(): void
{
$this->showAddDishModal = false;
$this->addDate = null;
$this->addUserIds = [];
$this->addSelectedDishId = null;
$this->addAvailableUsers = [];
$this->addAvailableDishes = [];
}
public function editDish($date, $userId): void public function editDish($date, $userId): void
{ {
if (!$this->authorizeUser($userId)) { if (!$this->authorizeUser($userId)) {

View file

@ -0,0 +1,36 @@
@props([
'users',
'selectedIds' => [],
'wireModel' => null,
'toggleAllMethod' => null,
'label' => 'Users',
])
<div class="mb-4">
<label class="block text-sm font-medium mb-2">{{ $label }}</label>
<div class="space-y-2 max-h-40 overflow-y-auto p-2 bg-gray-700 rounded border border-secondary">
@if($toggleAllMethod)
<!-- Select All -->
<label class="flex items-center cursor-pointer hover:bg-gray-600 p-1 rounded">
<input type="checkbox"
wire:click="{{ $toggleAllMethod }}"
{{ count($selectedIds) === count($users) && count($users) > 0 ? 'checked' : '' }}
class="mr-2">
<span class="text-accent-blue font-medium">Select All</span>
</label>
<hr class="border-secondary">
@endif
<!-- Individual users -->
@forelse($users as $user)
<label class="flex items-center cursor-pointer hover:bg-gray-600 p-1 rounded">
<input type="checkbox"
@if($wireModel) wire:model.live="{{ $wireModel }}" @endif
value="{{ $user->id }}"
class="mr-2">
<span>{{ $user->name }}</span>
</label>
@empty
<p class="text-gray-400 text-sm italic p-1">No users available.</p>
@endforelse
</div>
</div>

View file

@ -84,26 +84,14 @@ class="w-full p-2 border rounded bg-gray-600 border-secondary text-gray-100 focu
</div> </div>
@if($users->count() > 0) @if($users->count() > 0)
<div class="mb-6"> <x-user-multi-select
<label class="block text-sm font-medium mb-2">Assign to Users</label> :users="$users"
<div class="space-y-2 max-h-40 overflow-y-auto border border-secondary rounded p-3 bg-gray-700"> :selectedIds="$selectedUsers"
@foreach($users as $user) wireModel="selectedUsers"
<label class="flex items-center"> toggleAllMethod="toggleAllUsers"
<input type="checkbox" label="Assign to Users"
wire:model="selectedUsers" />
value="{{ $user->id }}" @error('selectedUsers') <span class="text-danger text-xs">{{ $message }}</span> @enderror
class="rounded border-secondary bg-gray-600 text-primary focus:ring-accent-blue mr-2">
<div class="flex items-center">
<div class="w-6 h-6 bg-primary rounded-full flex items-center justify-center text-white text-xs font-bold mr-2">
{{ strtoupper(substr($user->name, 0, 1)) }}
</div>
{{ $user->name }}
</div>
</label>
@endforeach
</div>
@error('selectedUsers') <span class="text-danger text-xs">{{ $message }}</span> @enderror
</div>
@else @else
<div class="mb-6"> <div class="mb-6">
<p class="text-gray-400 text-sm italic">No users available to assign. <a href="{{ route('users.index') }}" class="text-accent-blue hover:underline">Add users</a> to assign them to dishes.</p> <p class="text-gray-400 text-sm italic">No users available to assign. <a href="{{ route('users.index') }}" class="text-accent-blue hover:underline">Add users</a> to assign them to dishes.</p>
@ -111,12 +99,12 @@ class="rounded border-secondary bg-gray-600 text-primary focus:ring-accent-blue
@endif @endif
<div class="flex justify-end space-x-3"> <div class="flex justify-end space-x-3">
<button type="button" <button type="button"
wire:click="cancel" wire:click="cancel"
class="px-4 py-2 border-2 border-secondary text-gray-100 rounded hover:bg-gray-700 transition-colors duration-200"> class="px-4 py-2 border-2 border-secondary text-gray-100 rounded hover:bg-gray-700 transition-colors duration-200">
Cancel Cancel
</button> </button>
<button type="submit" <button type="submit"
class="px-4 py-2 bg-primary text-white rounded hover:bg-secondary transition-colors duration-200"> class="px-4 py-2 bg-primary text-white rounded hover:bg-secondary transition-colors duration-200">
Create Dish Create Dish
</button> </button>
@ -142,26 +130,14 @@ class="w-full p-2 border rounded bg-gray-600 border-secondary text-gray-100 focu
</div> </div>
@if($users->count() > 0) @if($users->count() > 0)
<div class="mb-6"> <x-user-multi-select
<label class="block text-sm font-medium mb-2">Assign to Users</label> :users="$users"
<div class="space-y-2 max-h-40 overflow-y-auto border border-secondary rounded p-3 bg-gray-700"> :selectedIds="$selectedUsers"
@foreach($users as $user) wireModel="selectedUsers"
<label class="flex items-center"> toggleAllMethod="toggleAllUsers"
<input type="checkbox" label="Assign to Users"
wire:model="selectedUsers" />
value="{{ $user->id }}" @error('selectedUsers') <span class="text-danger text-xs">{{ $message }}</span> @enderror
class="rounded border-secondary bg-gray-600 text-primary focus:ring-accent-blue mr-2">
<div class="flex items-center">
<div class="w-6 h-6 bg-primary rounded-full flex items-center justify-center text-white text-xs font-bold mr-2">
{{ strtoupper(substr($user->name, 0, 1)) }}
</div>
{{ $user->name }}
</div>
</label>
@endforeach
</div>
@error('selectedUsers') <span class="text-danger text-xs">{{ $message }}</span> @enderror
</div>
@else @else
<div class="mb-6"> <div class="mb-6">
<p class="text-gray-400 text-sm italic">No users available to assign. <a href="{{ route('users.index') }}" class="text-accent-blue hover:underline">Add users</a> to assign them to dishes.</p> <p class="text-gray-400 text-sm italic">No users available to assign. <a href="{{ route('users.index') }}" class="text-accent-blue hover:underline">Add users</a> to assign them to dishes.</p>
@ -169,12 +145,12 @@ class="rounded border-secondary bg-gray-600 text-primary focus:ring-accent-blue
@endif @endif
<div class="flex justify-end space-x-3"> <div class="flex justify-end space-x-3">
<button type="button" <button type="button"
wire:click="cancel" wire:click="cancel"
class="px-4 py-2 border-2 border-secondary text-gray-100 rounded hover:bg-gray-700 transition-colors duration-200"> class="px-4 py-2 border-2 border-secondary text-gray-100 rounded hover:bg-gray-700 transition-colors duration-200">
Cancel Cancel
</button> </button>
<button type="submit" <button type="submit"
class="px-4 py-2 bg-primary text-white rounded hover:bg-secondary transition-colors duration-200"> class="px-4 py-2 bg-primary text-white rounded hover:bg-secondary transition-colors duration-200">
Update Dish Update Dish
</button> </button>

View file

@ -266,45 +266,30 @@ class="px-4 py-2 bg-primary text-white rounded hover:bg-secondary transition-col
Add a dish for {{ \Carbon\Carbon::parse($addDate)->format('M j, Y') }} Add a dish for {{ \Carbon\Carbon::parse($addDate)->format('M j, Y') }}
</p> </p>
@if(count($addAvailableUsers) > 0) <x-user-multi-select
<!-- User selection --> :users="$addAvailableUsers"
<div class="mb-4"> :selectedIds="$addUserIds"
<label class="block text-sm font-medium mb-2">User</label> wireModel="addUserIds"
<select wire:model.live="addUserId" toggleAllMethod="toggleAllUsers"
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(count($addUserIds) > 0)
@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"> <div class="mb-6">
<p class="text-gray-400 text-sm italic"> <label class="block text-sm font-medium mb-2">Dish</label>
No users available. @if(count($addAvailableDishes) > 0)
<a href="{{ route('users.index') }}" class="text-accent-blue hover:underline">Add users</a> first. <select wire:model="addSelectedDishId"
</p> 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 in common for selected users.
<a href="{{ route('dishes.index') }}" class="text-accent-blue hover:underline">Add dishes</a> first.
</p>
@endif
</div> </div>
@endif @endif