From e2cb4499bbdf472f7925f139875e285bda35d83e Mon Sep 17 00:00:00 2001 From: Nyavokevin <42602932+nyavokevin@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:58:25 +0300 Subject: [PATCH] detail client --- .../Controllers/Api/ContactController.php | 23 + .../app/Repositories/ContactRepository.php | 7 + .../ContactRepositoryInterface.php | 2 + thanasoft-back/routes/api.php | 2 + thanasoft-front/CLIENT_DETAIL_NEW.md | 259 +++++++ .../Organism/CRM/ClientDetailPresentation.vue | 157 +++- .../Organism/CRM/ContactPresentation.vue | 2 +- .../CRM/client/ClientDetailContent.vue | 84 +++ .../CRM/client/ClientDetailSidebar.vue | 74 ++ .../components/atoms/client/ClientAvatar.vue | 64 ++ .../src/components/atoms/client/InfoCard.vue | 24 + .../components/atoms/client/StatusBadge.vue | 27 + .../atoms/client/TabNavigationItem.vue | 69 ++ .../molecules/client/ClientAddressTab.vue | 60 ++ .../molecules/client/ClientContactsTab.vue | 148 ++++ .../molecules/client/ClientInfoTab.vue | 674 ++++++++++++++++++ .../molecules/client/ClientNotesTab.vue | 23 + .../molecules/client/ClientOverview.vue | 153 ++++ .../molecules/client/ClientProfileCard.vue | 76 ++ .../molecules/client/ClientTabNavigation.vue | 54 ++ .../molecules/client/ListContact.vue | 198 ++++- .../molecules/contact/ContactModal.vue | 418 +++++++++++ .../templates/CRM/ClientDetailTemplate.vue | 20 +- thanasoft-front/src/stores/clientStore.ts | 7 + thanasoft-front/src/stores/contactStore.ts | 69 +- .../src/views/pages/CRM/ClientDetailNew.vue | 578 +++++++++++++++ .../src/views/pages/CRM/ClientDetails.vue | 67 +- 27 files changed, 3245 insertions(+), 94 deletions(-) create mode 100644 thanasoft-front/CLIENT_DETAIL_NEW.md create mode 100644 thanasoft-front/src/components/Organism/CRM/client/ClientDetailContent.vue create mode 100644 thanasoft-front/src/components/Organism/CRM/client/ClientDetailSidebar.vue create mode 100644 thanasoft-front/src/components/atoms/client/ClientAvatar.vue create mode 100644 thanasoft-front/src/components/atoms/client/InfoCard.vue create mode 100644 thanasoft-front/src/components/atoms/client/StatusBadge.vue create mode 100644 thanasoft-front/src/components/atoms/client/TabNavigationItem.vue create mode 100644 thanasoft-front/src/components/molecules/client/ClientAddressTab.vue create mode 100644 thanasoft-front/src/components/molecules/client/ClientContactsTab.vue create mode 100644 thanasoft-front/src/components/molecules/client/ClientInfoTab.vue create mode 100644 thanasoft-front/src/components/molecules/client/ClientNotesTab.vue create mode 100644 thanasoft-front/src/components/molecules/client/ClientOverview.vue create mode 100644 thanasoft-front/src/components/molecules/client/ClientProfileCard.vue create mode 100644 thanasoft-front/src/components/molecules/client/ClientTabNavigation.vue create mode 100644 thanasoft-front/src/components/molecules/contact/ContactModal.vue create mode 100644 thanasoft-front/src/views/pages/CRM/ClientDetailNew.vue diff --git a/thanasoft-back/app/Http/Controllers/Api/ContactController.php b/thanasoft-back/app/Http/Controllers/Api/ContactController.php index c7af595..92b0cbc 100644 --- a/thanasoft-back/app/Http/Controllers/Api/ContactController.php +++ b/thanasoft-back/app/Http/Controllers/Api/ContactController.php @@ -164,4 +164,27 @@ class ContactController extends Controller ], 500); } } + + + public function getContactsByClient(string $clientId): JsonResponse + { + try { + $intId = (int) $clientId; + $contacts = $this->contactRepository->getByClientId($intId); + return response()->json([ + 'data' => ContactResource::collection($contacts), + ], 200); + } catch (\Exception $e) { + Log::error('Error fetching contacts by client: ' . $e->getMessage(), [ + 'exception' => $e, + 'trace' => $e->getTraceAsString(), + 'client_id' => $clientId, + ]); + + return response()->json([ + 'message' => 'Une erreur est survenue lors de la récupération des contacts du client.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } } diff --git a/thanasoft-back/app/Repositories/ContactRepository.php b/thanasoft-back/app/Repositories/ContactRepository.php index f346beb..8b4295d 100644 --- a/thanasoft-back/app/Repositories/ContactRepository.php +++ b/thanasoft-back/app/Repositories/ContactRepository.php @@ -55,4 +55,11 @@ class ContactRepository extends BaseRepository implements ContactRepositoryInter return $query->paginate($perPage); } + + public function getByClientId(int $clientId) + { + return $this->model->newQuery() + ->where('client_id', $clientId) + ->get(); + } } diff --git a/thanasoft-back/app/Repositories/ContactRepositoryInterface.php b/thanasoft-back/app/Repositories/ContactRepositoryInterface.php index e37a0fb..f8b4984 100644 --- a/thanasoft-back/app/Repositories/ContactRepositoryInterface.php +++ b/thanasoft-back/app/Repositories/ContactRepositoryInterface.php @@ -7,4 +7,6 @@ namespace App\Repositories; interface ContactRepositoryInterface extends BaseRepositoryInterface { function paginate(int $perPage = 15, array $filters = []); + + function getByClientId(int $clientId); } diff --git a/thanasoft-back/routes/api.php b/thanasoft-back/routes/api.php index b354b1b..7fdde85 100644 --- a/thanasoft-back/routes/api.php +++ b/thanasoft-back/routes/api.php @@ -41,8 +41,10 @@ Route::middleware('auth:sanctum')->group(function () { Route::apiResource('client-groups', ClientGroupController::class); Route::apiResource('client-locations', ClientLocationController::class); + // Contact management Route::apiResource('contacts', ContactController::class); + Route::get('clients/{clientId}/contacts', [ContactController::class, 'getContactsByClient']); Route::apiResource('client-categories', ClientCategoryController::class); diff --git a/thanasoft-front/CLIENT_DETAIL_NEW.md b/thanasoft-front/CLIENT_DETAIL_NEW.md new file mode 100644 index 0000000..35533a1 --- /dev/null +++ b/thanasoft-front/CLIENT_DETAIL_NEW.md @@ -0,0 +1,259 @@ +# New Client Detail Page - Modern Design + +## Overview +A completely redesigned client detail page with modern UI/UX, inspired by the Settings page structure with tabs, client avatar/logo, and better ergonomics. + +## Features + +### 🎨 **Modern Layout** +- **Left Sidebar**: Sticky client card with navigation +- **Right Content Area**: Tabbed content with clean organization + +### 👤 **Client Profile Card** +- **Avatar/Logo**: + - Shows client initials if no image + - Click edit button to upload logo + - Large, prominent display +- **Quick Stats**: + - Number of contacts + - Active/Inactive status +- **Client Info**: Name and type + +### 📑 **5 Tab Sections** + +#### 1. **Aperçu (Overview)** +Default view with key information: +- Contact info card (email, phone) +- Business info card (SIRET, VAT) +- Address card +- Recent contacts (first 3) +- Edit button to modify client + +#### 2. **Informations (Information)** +Complete client details: +- Name, type +- SIRET, VAT number +- Email, phone +- Commercial + +#### 3. **Contacts** +Full contact list with: +- Contact table with avatars +- Email, phone, position +- Primary contact badge +- "Add Contact" button +- Empty state if no contacts + +#### 4. **Adresse (Address)** +Billing address details: +- Address line 1 & 2 +- Postal code, city, country + +#### 5. **Notes** +Client notes section + +## File Location +``` +src/views/pages/CRM/ClientDetailNew.vue +``` + +## Usage + +### Update Router +Add the new route in your router configuration: + +```javascript +// router/index.js +{ + path: '/clients/:id/detail', + name: 'ClientDetailNew', + component: () => import('@/views/pages/CRM/ClientDetailNew.vue') +} +``` + +### Replace Old ClientDetail +To use this as the main client detail page: + +```javascript +// Change existing route +{ + path: '/clients/:id', + name: 'ClientDetail', + component: () => import('@/views/pages/CRM/ClientDetailNew.vue') // Changed from ClientDetails.vue +} +``` + +## Component Structure + +### Template Sections +1. **Header** - Back button to clients list +2. **Loading State** - Spinner while fetching data +3. **Error State** - Alert for errors +4. **Main Content** + - Left: Client card + navigation + - Right: Tab content + +### Script Features +```javascript +// Reactive data +const activeTab = ref('overview') // Current tab +const clientAvatar = ref(null) // Client logo/avatar +const contacts_client = ref([]) // Client contacts + +// Methods +getInitials(name) // Generate initials from name +formatAddress(client) // Format full address string +triggerFileInput() // Open file selector +handleAvatarUpload() // Handle logo upload +``` + +### Styling Features +- **Sticky sidebar** - Stays visible when scrolling +- **Active tab highlighting** - Gradient background +- **Smooth transitions** - Hover effects +- **Responsive design** - Works on mobile +- **Clean cards** - Soft shadows and borders + +## Design Principles + +### Color Scheme +- **Primary Actions**: Gradient purple-pink (`#7928ca` to `#ff0080`) +- **Success**: Green for active status and badges +- **Info**: Blue for contact info +- **Warning**: Yellow for business info +- **Danger**: Red for inactive status + +### Typography +- **Headers**: Bold, clear hierarchy +- **Body**: `text-sm` for readability +- **Labels**: Uppercase with spacing + +### Icons +Using Font Awesome: +- 📊 `fa-eye` - Overview +- ℹ️ `fa-info-circle` - Information +- 👥 `fa-users` - Contacts +- 📍 `fa-map-marker-alt` - Address +- 📝 `fa-sticky-note` - Notes + +## Avatar/Logo Upload + +### Current Implementation +```javascript +handleAvatarUpload(event) { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + clientAvatar.value = e.target.result; + // TODO: Upload to server + }; + reader.readAsDataURL(file); + } +} +``` + +### TODO: Connect to Backend +To implement server upload: + +```javascript +const handleAvatarUpload = async (event) => { + const file = event.target.files[0]; + if (!file) return; + + const formData = new FormData(); + formData.append('avatar', file); + formData.append('client_id', clientStore.currentClient.id); + + try { + const response = await api.post('/clients/upload-avatar', formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }); + + clientAvatar.value = response.data.avatar_url; + // Update store + await clientStore.fetchClient(client_id); + } catch (error) { + console.error('Upload failed:', error); + } +}; +``` + +## Comparison: Old vs New + +### Old Design +- Single page layout +- All info visible at once +- No image/avatar support +- Basic styling +- No tab organization + +### New Design ✨ +- Modern tabbed interface +- Client avatar/logo with upload +- Sticky sidebar navigation +- Card-based organization +- Better mobile responsive +- Visual hierarchy +- Quick stats +- Empty states +- Badge indicators + +## Customization + +### Change Tab Order +```vue + + +``` + +### Add New Tab +1. Add nav item in sidebar +2. Add content section with `v-show` +3. Update `activeTab` ref + +```vue + + + + +
+
+
Documents
+
+
+ +
+
+``` + +## Dependencies +- Vue 3 Composition API +- Vue Router +- Pinia stores (clientStore, contactStore) +- Font Awesome icons +- Bootstrap 5 classes + +## Browser Support +- Chrome, Firefox, Safari (latest) +- Edge (latest) +- Mobile browsers + +--- + +**Status**: ✅ Ready to use +**Created**: October 20, 2025 +**File**: `src/views/pages/CRM/ClientDetailNew.vue` diff --git a/thanasoft-front/src/components/Organism/CRM/ClientDetailPresentation.vue b/thanasoft-front/src/components/Organism/CRM/ClientDetailPresentation.vue index 1cfe033..d93e354 100644 --- a/thanasoft-front/src/components/Organism/CRM/ClientDetailPresentation.vue +++ b/thanasoft-front/src/components/Organism/CRM/ClientDetailPresentation.vue @@ -1,48 +1,151 @@ diff --git a/thanasoft-front/src/components/Organism/CRM/client/ClientDetailSidebar.vue b/thanasoft-front/src/components/Organism/CRM/client/ClientDetailSidebar.vue new file mode 100644 index 0000000..1574698 --- /dev/null +++ b/thanasoft-front/src/components/Organism/CRM/client/ClientDetailSidebar.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/thanasoft-front/src/components/atoms/client/ClientAvatar.vue b/thanasoft-front/src/components/atoms/client/ClientAvatar.vue new file mode 100644 index 0000000..7471ee2 --- /dev/null +++ b/thanasoft-front/src/components/atoms/client/ClientAvatar.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/thanasoft-front/src/components/atoms/client/InfoCard.vue b/thanasoft-front/src/components/atoms/client/InfoCard.vue new file mode 100644 index 0000000..6332c61 --- /dev/null +++ b/thanasoft-front/src/components/atoms/client/InfoCard.vue @@ -0,0 +1,24 @@ + + + diff --git a/thanasoft-front/src/components/atoms/client/StatusBadge.vue b/thanasoft-front/src/components/atoms/client/StatusBadge.vue new file mode 100644 index 0000000..0943660 --- /dev/null +++ b/thanasoft-front/src/components/atoms/client/StatusBadge.vue @@ -0,0 +1,27 @@ + + + diff --git a/thanasoft-front/src/components/atoms/client/TabNavigationItem.vue b/thanasoft-front/src/components/atoms/client/TabNavigationItem.vue new file mode 100644 index 0000000..62540c9 --- /dev/null +++ b/thanasoft-front/src/components/atoms/client/TabNavigationItem.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/thanasoft-front/src/components/molecules/client/ClientAddressTab.vue b/thanasoft-front/src/components/molecules/client/ClientAddressTab.vue new file mode 100644 index 0000000..8340338 --- /dev/null +++ b/thanasoft-front/src/components/molecules/client/ClientAddressTab.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/thanasoft-front/src/components/molecules/client/ClientContactsTab.vue b/thanasoft-front/src/components/molecules/client/ClientContactsTab.vue new file mode 100644 index 0000000..1f1fe2f --- /dev/null +++ b/thanasoft-front/src/components/molecules/client/ClientContactsTab.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/thanasoft-front/src/components/molecules/client/ClientInfoTab.vue b/thanasoft-front/src/components/molecules/client/ClientInfoTab.vue new file mode 100644 index 0000000..59dbdb7 --- /dev/null +++ b/thanasoft-front/src/components/molecules/client/ClientInfoTab.vue @@ -0,0 +1,674 @@ + + + + + diff --git a/thanasoft-front/src/components/molecules/client/ClientNotesTab.vue b/thanasoft-front/src/components/molecules/client/ClientNotesTab.vue new file mode 100644 index 0000000..cbdbba6 --- /dev/null +++ b/thanasoft-front/src/components/molecules/client/ClientNotesTab.vue @@ -0,0 +1,23 @@ + + + diff --git a/thanasoft-front/src/components/molecules/client/ClientOverview.vue b/thanasoft-front/src/components/molecules/client/ClientOverview.vue new file mode 100644 index 0000000..6ba7155 --- /dev/null +++ b/thanasoft-front/src/components/molecules/client/ClientOverview.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/thanasoft-front/src/components/molecules/client/ClientProfileCard.vue b/thanasoft-front/src/components/molecules/client/ClientProfileCard.vue new file mode 100644 index 0000000..2e8b1b5 --- /dev/null +++ b/thanasoft-front/src/components/molecules/client/ClientProfileCard.vue @@ -0,0 +1,76 @@ + + + diff --git a/thanasoft-front/src/components/molecules/client/ClientTabNavigation.vue b/thanasoft-front/src/components/molecules/client/ClientTabNavigation.vue new file mode 100644 index 0000000..d318467 --- /dev/null +++ b/thanasoft-front/src/components/molecules/client/ClientTabNavigation.vue @@ -0,0 +1,54 @@ + + + diff --git a/thanasoft-front/src/components/molecules/client/ListContact.vue b/thanasoft-front/src/components/molecules/client/ListContact.vue index d767123..7b88873 100644 --- a/thanasoft-front/src/components/molecules/client/ListContact.vue +++ b/thanasoft-front/src/components/molecules/client/ListContact.vue @@ -1,35 +1,193 @@ + + diff --git a/thanasoft-front/src/components/molecules/contact/ContactModal.vue b/thanasoft-front/src/components/molecules/contact/ContactModal.vue new file mode 100644 index 0000000..d76c52d --- /dev/null +++ b/thanasoft-front/src/components/molecules/contact/ContactModal.vue @@ -0,0 +1,418 @@ + + + + + diff --git a/thanasoft-front/src/components/templates/CRM/ClientDetailTemplate.vue b/thanasoft-front/src/components/templates/CRM/ClientDetailTemplate.vue index 72d6ae9..50df771 100644 --- a/thanasoft-front/src/components/templates/CRM/ClientDetailTemplate.vue +++ b/thanasoft-front/src/components/templates/CRM/ClientDetailTemplate.vue @@ -1,13 +1,15 @@