From 11d9ba502dee444bf376fc033576712303fdbb4f Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 28 Jan 2026 06:34:39 -0600 Subject: [PATCH] Connect contacts to clients --- resources/views/clients/index.blade.php | 3 + .../components/⚡add-client-contact.blade.php | 188 ++++++++++++++++++ .../views/components/⚡client-list.blade.php | 38 +++- .../⚡remove-client-contact.blade.php | 181 +++++++++++++++++ .../⚡set-primary-contact.blade.php | 84 ++++++++ 5 files changed, 490 insertions(+), 4 deletions(-) create mode 100644 resources/views/components/⚡add-client-contact.blade.php create mode 100644 resources/views/components/⚡remove-client-contact.blade.php create mode 100644 resources/views/components/⚡set-primary-contact.blade.php diff --git a/resources/views/clients/index.blade.php b/resources/views/clients/index.blade.php index 6292d9e..1c8e076 100644 --- a/resources/views/clients/index.blade.php +++ b/resources/views/clients/index.blade.php @@ -5,5 +5,8 @@ + + + diff --git a/resources/views/components/⚡add-client-contact.blade.php b/resources/views/components/⚡add-client-contact.blade.php new file mode 100644 index 0000000..bcbdb26 --- /dev/null +++ b/resources/views/components/⚡add-client-contact.blade.php @@ -0,0 +1,188 @@ +clientId = $clientId; + $this->client = Client::findOrFail($clientId); + $this->contactId = null; + $this->isPrimary = !$this->client->contacts()->exists(); + + $this->resetValidation(); + Flux::modal('add-contact')->show(); + } + + #[Computed] + public function availableContacts() + { + if (!$this->client) { + return collect(); + } + + $existingContactIds = $this->client->contacts()->pluck('contacts.id'); + + return Contact::whereNotIn('id', $existingContactIds) + ->orderBy('last_name') + ->orderBy('first_name') + ->get(); + } + + public function openCreateModal(): void + { + $this->first_name = ''; + $this->last_name = ''; + $this->email = ''; + $this->phone = ''; + $this->newContactIsPrimary = !$this->client->contacts()->exists(); + + $this->resetValidation(); + Flux::modal('add-contact')->close(); + Flux::modal('create-client-contact')->show(); + } + + public function backToSelect(): void + { + Flux::modal('create-client-contact')->close(); + Flux::modal('add-contact')->show(); + } + + public function attachContact(): void + { + if (!$this->contactId) { + return; + } + + if ($this->isPrimary) { + $this->client->contacts()->updateExistingPivot( + $this->client->contacts()->wherePivot('is_primary', true)->pluck('contacts.id'), + ['is_primary' => false] + ); + } + + $this->client->contacts()->attach($this->contactId, ['is_primary' => $this->isPrimary]); + + $this->reset(['clientId', 'client', 'contactId', 'isPrimary']); + Flux::modal('add-contact')->close(); + $this->dispatch('client-updated'); + } + + public function createAndAttach(): void + { + $this->validate([ + 'first_name' => 'required|string|max:255', + 'last_name' => 'required|string|max:255', + 'email' => 'required|email|max:255|unique:contacts,email', + 'phone' => 'nullable|string|max:20', + ]); + + $contact = Contact::create([ + 'first_name' => $this->first_name, + 'last_name' => $this->last_name, + 'email' => $this->email, + 'phone' => $this->phone ?: null, + ]); + + if ($this->newContactIsPrimary) { + $this->client->contacts()->updateExistingPivot( + $this->client->contacts()->wherePivot('is_primary', true)->pluck('contacts.id'), + ['is_primary' => false] + ); + } + + $this->client->contacts()->attach($contact->id, ['is_primary' => $this->newContactIsPrimary]); + + $this->reset(['clientId', 'client', 'contactId', 'isPrimary', 'first_name', 'last_name', 'email', 'phone', 'newContactIsPrimary']); + Flux::modal('create-client-contact')->close(); + $this->dispatch('client-updated'); + $this->dispatch('contact-created'); + } + + #[Computed] + public function clientHasContacts(): bool + { + return $this->client?->contacts()->exists() ?? false; + } +}; +?> + +
+ +
+ Add Contact to {{ $client?->name }} + + + @foreach($this->availableContacts as $contact) + + {{ $contact->full_name }} ({{ $contact->email }}) + + @endforeach + + + + Create New Contact + + + @if($this->clientHasContacts) + + @endif + +
+ + + Add Contact + +
+
+
+ + +
+ Create Contact for {{ $client?->name }} + + + + + + + @if($this->clientHasContacts) + + @endif + +
+ Back + + Create & Add +
+ +
+
\ No newline at end of file diff --git a/resources/views/components/⚡client-list.blade.php b/resources/views/components/⚡client-list.blade.php index 9604b2c..08eac85 100644 --- a/resources/views/components/⚡client-list.blade.php +++ b/resources/views/components/⚡client-list.blade.php @@ -1,5 +1,6 @@ status = $client->status === ClientStatus::ACTIVE + ? ClientStatus::INACTIVE + : ClientStatus::ACTIVE; + $client->save(); + } + #[On('client-created')] #[On('client-updated')] public function refresh(): void @@ -75,8 +84,8 @@ new class extends Component { @if($client->primary_contact)
- {{ $client->primary_contact?->full_name }} +
@endif @foreach($client->secondaryContacts as $contact) @@ -102,14 +111,35 @@ new class extends Component { wire:click="$dispatch('edit-client', { clientId: {{ $client->id }} })" icon="pencil">Edit Client + @if($client->status === ClientStatus::ACTIVE) + Make Inactive + + @else + Make Active + + @endif Add Contact - Remove Contact - + @if($client->contacts()->count() > 0) + Remove Contact + + @endif + @if($client->contacts()->count() > 1) + Set Primary Contact + + @endif diff --git a/resources/views/components/⚡remove-client-contact.blade.php b/resources/views/components/⚡remove-client-contact.blade.php new file mode 100644 index 0000000..f781c7a --- /dev/null +++ b/resources/views/components/⚡remove-client-contact.blade.php @@ -0,0 +1,181 @@ +clientId = $clientId; + $this->client = Client::findOrFail($clientId); + $this->contactId = null; + $this->newPrimaryId = null; + + $this->resetValidation(); + Flux::modal('remove-contact')->show(); + } + + #[Computed] + public function clientContacts() + { + if (!$this->client) { + return collect(); + } + + return $this->client->contacts() + ->orderBy('last_name') + ->orderBy('first_name') + ->get(); + } + + #[Computed] + public function selectedContact(): ?Contact + { + if (!$this->contactId) { + return null; + } + + return Contact::find($this->contactId); + } + + #[Computed] + public function isRemovingPrimary(): bool + { + if (!$this->contactId || !$this->client) { + return false; + } + + return $this->client->contacts() + ->wherePivot('is_primary', true) + ->where('contacts.id', $this->contactId) + ->exists(); + } + + #[Computed] + public function otherContacts() + { + if (!$this->client || !$this->contactId) { + return collect(); + } + + return $this->client->contacts() + ->where('contacts.id', '!=', $this->contactId) + ->orderBy('last_name') + ->orderBy('first_name') + ->get(); + } + + #[Computed] + public function needsNewPrimarySelection(): bool + { + return $this->isRemovingPrimary && $this->otherContacts->count() > 1; + } + + public function removeContact(): void + { + if (!$this->contactId) { + return; + } + + $otherContacts = $this->otherContacts; + + // Detach the selected contact + $this->client->contacts()->detach($this->contactId); + + // Handle primary contact assignment + if ($otherContacts->count() === 1) { + // Only one remaining - make them primary + $this->client->contacts()->updateExistingPivot( + $otherContacts->first()->id, + ['is_primary' => true] + ); + } elseif ($otherContacts->count() > 1 && $this->isRemovingPrimary) { + // Multiple remaining and removing primary - use selected new primary + if ($this->newPrimaryId) { + // Clear any existing primary + $this->client->contacts()->wherePivot('is_primary', true) + ->each(fn ($contact) => $this->client->contacts()->updateExistingPivot( + $contact->id, + ['is_primary' => false] + )); + + // Set new primary + $this->client->contacts()->updateExistingPivot( + $this->newPrimaryId, + ['is_primary' => true] + ); + } + } + + $this->reset(['clientId', 'client', 'contactId', 'newPrimaryId']); + Flux::modal('remove-contact')->close(); + $this->dispatch('client-updated'); + } + + #[Computed] + public function canSubmit(): bool + { + if (!$this->contactId) { + return false; + } + + if ($this->needsNewPrimarySelection && !$this->newPrimaryId) { + return false; + } + + return true; + } +}; +?> + +
+ +
+ Remove Contact from {{ $client?->name }} + + @if($this->clientContacts->isEmpty()) +

This client has no contacts.

+ @else + + @foreach($this->clientContacts as $contact) + + {{ $contact->full_name }} + @if($contact->pivot->is_primary) (Primary) @endif + + @endforeach + + + @if($this->needsNewPrimarySelection) + + @foreach($this->otherContacts as $contact) + + @endforeach + + @endif + +
+ + + Remove Contact + +
+ @endif +
+
+
\ No newline at end of file diff --git a/resources/views/components/⚡set-primary-contact.blade.php b/resources/views/components/⚡set-primary-contact.blade.php new file mode 100644 index 0000000..2be740a --- /dev/null +++ b/resources/views/components/⚡set-primary-contact.blade.php @@ -0,0 +1,84 @@ +clientId = $clientId; + $this->client = Client::findOrFail($clientId); + $this->primaryId = $this->client->contacts() + ->wherePivot('is_primary', true) + ->first()?->id; + + Flux::modal('set-primary-contact')->show(); + } + + #[Computed] + public function clientContacts() + { + if (!$this->client) { + return collect(); + } + + return $this->client->contacts() + ->orderBy('last_name') + ->orderBy('first_name') + ->get(); + } + + public function save(): void + { + if (!$this->primaryId) { + return; + } + + // Clear existing primary + $this->client->contacts()->wherePivot('is_primary', true) + ->each(fn ($contact) => $this->client->contacts()->updateExistingPivot( + $contact->id, + ['is_primary' => false] + )); + + // Set new primary + $this->client->contacts()->updateExistingPivot( + $this->primaryId, + ['is_primary' => true] + ); + + $this->reset(['clientId', 'client', 'primaryId']); + Flux::modal('set-primary-contact')->close(); + $this->dispatch('client-updated'); + } +}; +?> + +
+ +
+ Set Primary Contact for {{ $client?->name }} + + + @foreach($this->clientContacts as $contact) + + @endforeach + + +
+ + + Save + +
+
+
+
\ No newline at end of file