bug - 14 - Add user multi-select
This commit is contained in:
parent
7412316746
commit
0baa87e373
5 changed files with 172 additions and 132 deletions
|
|
@ -119,4 +119,14 @@ public function cancel()
|
|||
$this->showDeleteModal = false;
|
||||
$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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ class ScheduleCalendar extends Component
|
|||
// Add dish modal
|
||||
public $showAddDishModal = false;
|
||||
public $addDate = null;
|
||||
public $addUserId = null;
|
||||
public $addUserIds = [];
|
||||
public $addSelectedDishId = null;
|
||||
public $addAvailableUsers = [];
|
||||
public $addAvailableDishes = [];
|
||||
|
|
@ -170,7 +170,7 @@ public function cancel(): void
|
|||
$this->availableDishes = [];
|
||||
$this->showAddDishModal = false;
|
||||
$this->addDate = null;
|
||||
$this->addUserId = null;
|
||||
$this->addUserIds = [];
|
||||
$this->addSelectedDishId = null;
|
||||
$this->addAvailableUsers = [];
|
||||
$this->addAvailableDishes = [];
|
||||
|
|
@ -212,21 +212,37 @@ public function openAddDishModal($date): void
|
|||
->get();
|
||||
|
||||
$this->addAvailableDishes = [];
|
||||
$this->addUserId = null;
|
||||
$this->addUserIds = [];
|
||||
$this->addSelectedDishId = null;
|
||||
|
||||
$this->showAddDishModal = true;
|
||||
}
|
||||
|
||||
public function updatedAddUserId($value): void
|
||||
public function toggleAllUsers(): 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();
|
||||
if (count($this->addUserIds) === count($this->addAvailableUsers)) {
|
||||
$this->addUserIds = [];
|
||||
} 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 = [];
|
||||
} 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;
|
||||
}
|
||||
|
|
@ -234,13 +250,8 @@ public function updatedAddUserId($value): void
|
|||
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.');
|
||||
if (empty($this->addUserIds)) {
|
||||
session()->flash('error', 'Please select at least one user.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -258,49 +269,71 @@ public function saveAddDish(): void
|
|||
['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();
|
||||
$addedCount = 0;
|
||||
$skippedCount = 0;
|
||||
|
||||
if ($existing) {
|
||||
session()->flash('error', 'This user already has a dish scheduled for this date. Use Edit instead.');
|
||||
return;
|
||||
foreach ($this->addUserIds as $userId) {
|
||||
if (!$this->authorizeUser((int) $userId)) {
|
||||
$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
|
||||
$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->closeAddDishModal();
|
||||
$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) {
|
||||
Log::error('Add dish failed', ['exception' => $e]);
|
||||
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
|
||||
{
|
||||
if (!$this->authorizeUser($userId)) {
|
||||
|
|
|
|||
36
resources/views/components/user-multi-select.blade.php
Normal file
36
resources/views/components/user-multi-select.blade.php
Normal 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>
|
||||
|
|
@ -84,26 +84,14 @@ class="w-full p-2 border rounded bg-gray-600 border-secondary text-gray-100 focu
|
|||
</div>
|
||||
|
||||
@if($users->count() > 0)
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium mb-2">Assign to Users</label>
|
||||
<div class="space-y-2 max-h-40 overflow-y-auto border border-secondary rounded p-3 bg-gray-700">
|
||||
@foreach($users as $user)
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
wire:model="selectedUsers"
|
||||
value="{{ $user->id }}"
|
||||
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>
|
||||
<x-user-multi-select
|
||||
:users="$users"
|
||||
:selectedIds="$selectedUsers"
|
||||
wireModel="selectedUsers"
|
||||
toggleAllMethod="toggleAllUsers"
|
||||
label="Assign to Users"
|
||||
/>
|
||||
@error('selectedUsers') <span class="text-danger text-xs">{{ $message }}</span> @enderror
|
||||
@else
|
||||
<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>
|
||||
|
|
@ -111,12 +99,12 @@ class="rounded border-secondary bg-gray-600 text-primary focus:ring-accent-blue
|
|||
@endif
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button type="button"
|
||||
wire:click="cancel"
|
||||
<button type="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>
|
||||
<button type="submit"
|
||||
<button type="submit"
|
||||
class="px-4 py-2 bg-primary text-white rounded hover:bg-secondary transition-colors duration-200">
|
||||
Create Dish
|
||||
</button>
|
||||
|
|
@ -142,26 +130,14 @@ class="w-full p-2 border rounded bg-gray-600 border-secondary text-gray-100 focu
|
|||
</div>
|
||||
|
||||
@if($users->count() > 0)
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium mb-2">Assign to Users</label>
|
||||
<div class="space-y-2 max-h-40 overflow-y-auto border border-secondary rounded p-3 bg-gray-700">
|
||||
@foreach($users as $user)
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox"
|
||||
wire:model="selectedUsers"
|
||||
value="{{ $user->id }}"
|
||||
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>
|
||||
<x-user-multi-select
|
||||
:users="$users"
|
||||
:selectedIds="$selectedUsers"
|
||||
wireModel="selectedUsers"
|
||||
toggleAllMethod="toggleAllUsers"
|
||||
label="Assign to Users"
|
||||
/>
|
||||
@error('selectedUsers') <span class="text-danger text-xs">{{ $message }}</span> @enderror
|
||||
@else
|
||||
<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>
|
||||
|
|
@ -169,12 +145,12 @@ class="rounded border-secondary bg-gray-600 text-primary focus:ring-accent-blue
|
|||
@endif
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button type="button"
|
||||
wire:click="cancel"
|
||||
<button type="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>
|
||||
<button type="submit"
|
||||
<button type="submit"
|
||||
class="px-4 py-2 bg-primary text-white rounded hover:bg-secondary transition-colors duration-200">
|
||||
Update Dish
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -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') }}
|
||||
</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>
|
||||
<x-user-multi-select
|
||||
:users="$addAvailableUsers"
|
||||
:selectedIds="$addUserIds"
|
||||
wireModel="addUserIds"
|
||||
toggleAllMethod="toggleAllUsers"
|
||||
/>
|
||||
|
||||
<!-- 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
|
||||
@if(count($addUserIds) > 0)
|
||||
<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>
|
||||
<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 in common for selected users.
|
||||
<a href="{{ route('dishes.index') }}" class="text-accent-blue hover:underline">Add dishes</a> first.
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue