From 5b7dad4b7b3b9ece38b5ffed2aaf3b841315ccf5 Mon Sep 17 00:00:00 2001 From: Antonio Date: Thu, 11 Jun 2026 17:03:19 +0200 Subject: [PATCH 1/2] fix: new Layout for phonebook section --- .../public/locales/en/translations.json | 5 + .../public/locales/it/translations.json | 5 + .../PhonebookModule/AddToPhonebookBox.tsx | 190 +++++++++++++++--- .../SearchResults/SearchNumberDetail.tsx | 36 +++- src/shared/phonebook.ts | 122 +++++++++++ src/shared/types.ts | 3 + src/shared/useNethVoiceAPI.ts | 16 +- 7 files changed, 329 insertions(+), 48 deletions(-) create mode 100644 src/shared/phonebook.ts diff --git a/src/renderer/public/locales/en/translations.json b/src/renderer/public/locales/en/translations.json index 3707b604..a57deddc 100644 --- a/src/renderer/public/locales/en/translations.json +++ b/src/renderer/public/locales/en/translations.json @@ -181,8 +181,13 @@ "Try changing your search filters": "Try changing your search filters", "Delete contact": "Delete contact", "Visibility": "Visibility", + "Everybody": "Everybody", "Only me": "Only me", "Public": "Public", + "Groups": "Groups", + "Choose one or more groups": "Choose one or more groups", + "No groups available": "No groups available", + "Select at least one group": "Select at least one group", "Open contact menu": "Open contact menu", "Last switchboard calls": "Last switchboard calls", "Last personal calls": "Last personal calls", diff --git a/src/renderer/public/locales/it/translations.json b/src/renderer/public/locales/it/translations.json index c076fb73..28514cbe 100644 --- a/src/renderer/public/locales/it/translations.json +++ b/src/renderer/public/locales/it/translations.json @@ -181,8 +181,13 @@ "Try changing your search filters": "Prova a cambiare i tuoi filtri di ricerca", "Delete contact": "Elimina contact", "Visibility": "Visibilità", + "Everybody": "Tutti", "Only me": "Solo me", "Public": "Public", + "Groups": "Gruppi", + "Choose one or more groups": "Scegli uno o più gruppi", + "No groups available": "Nessun gruppo disponibile", + "Select at least one group": "Seleziona almeno un gruppo", "Open contact menu": "Open contact menu", "Last switchboard calls": "Last switchboard calls", "Last personal calls": "Last personal calls", diff --git a/src/renderer/src/components/Modules/NethVoice/PhonebookModule/AddToPhonebookBox.tsx b/src/renderer/src/components/Modules/NethVoice/PhonebookModule/AddToPhonebookBox.tsx index f760678c..d1af0436 100644 --- a/src/renderer/src/components/Modules/NethVoice/PhonebookModule/AddToPhonebookBox.tsx +++ b/src/renderer/src/components/Modules/NethVoice/PhonebookModule/AddToPhonebookBox.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useRef } from 'react' import { Button, TextInput } from '../../../Nethesis' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faSpinner as LoadingIcon } from '@fortawesome/free-solid-svg-icons' +import { faChevronDown, faCheck, faSpinner as LoadingIcon } from '@fortawesome/free-solid-svg-icons' import { ContactType } from '@shared/types' import { useForm, SubmitHandler } from 'react-hook-form' import { t } from 'i18next' @@ -13,15 +13,25 @@ import { Log } from '@shared/utils/logger' import { Scrollable } from '@renderer/components/Scrollable' import { ModuleTitle } from '@renderer/components/ModuleTitle' import { usePhonebookSearchModule } from '../SearchResults/hook/usePhoneBookSearchModule' +import { useSharedState, useNethlinkData } from '@renderer/store' +import { + canWritePhonebookVisibility, + normalizeSharedGroups, +} from '@shared/phonebook' +import classNames from 'classnames' export function AddToPhonebookBox({ close }) { const phoneBookSearchModule = usePhonebookSearchModule() const phonebookModule = usePhonebookModule() const [searchText] = phoneBookSearchModule.searchTextState const [selectedContact] = phonebookModule.selectedContact + const [account] = useSharedState('account') + const [operators] = useNethlinkData('operators') const submitButtonRef = useRef(null) + const sharedGroupsDropdownRef = useRef(null) const baseSchema = z.object({ privacy: z.string(), + shared_groups: z.array(z.string()).default([]), extension: z .string() .trim() @@ -60,6 +70,8 @@ export function AddToPhonebookBox({ close }) { .and(baseSchema) const [isLoading, setIsLoading] = useState(false) + const [isSharedGroupsDropdownOpen, setIsSharedGroupsDropdownOpen] = useState(false) + const [sharedGroupsError, setSharedGroupsError] = useState('') const { register, @@ -73,6 +85,7 @@ export function AddToPhonebookBox({ close }) { } = useForm({ defaultValues: { privacy: '', + shared_groups: [], type: '', name: '', company: '', @@ -86,6 +99,26 @@ export function AddToPhonebookBox({ close }) { }) const watchType = watch('type') + const watchPrivacy = watch('privacy') + const selectedGroups = watch('shared_groups') || [] + const profile = account?.data?.profile + const availableGroups = Object.keys(operators?.groups || {}).sort((left, right) => + left.localeCompare(right), + ) + const visibilityOptions = [ + { + id: 'public', + label: t('Phonebook.Everybody'), + }, + { + id: 'private', + label: t('Phonebook.Only me'), + }, + { + id: 'group', + label: t('Phonebook.Groups'), + }, + ].filter((option) => canWritePhonebookVisibility(profile, option.id as 'public' | 'private' | 'group')) useEffect(() => { !!errors.name && trigger('name') @@ -98,6 +131,7 @@ export function AddToPhonebookBox({ close }) { useEffect(() => { setValue('privacy', 'public') + setValue('shared_groups', []) setValue('type', 'person') if (searchText != undefined) { @@ -120,7 +154,46 @@ export function AddToPhonebookBox({ close }) { } }, []) + useEffect(() => { + const allowedVisibilityValues = visibilityOptions.map((option) => option.id) + if (!allowedVisibilityValues.includes(watchPrivacy || '')) { + setValue('privacy', visibilityOptions[0]?.id || 'private') + } + }, [setValue, visibilityOptions, watchPrivacy]) + + useEffect(() => { + if (watchPrivacy !== 'group') { + setValue('shared_groups', []) + setSharedGroupsError('') + setIsSharedGroupsDropdownOpen(false) + } + }, [setValue, watchPrivacy]) + + useEffect(() => { + function handleOutsideClick(event: MouseEvent) { + if ( + sharedGroupsDropdownRef.current && + !sharedGroupsDropdownRef.current.contains(event.target as Node) + ) { + setIsSharedGroupsDropdownOpen(false) + } + } + + document.addEventListener('mousedown', handleOutsideClick) + return () => document.removeEventListener('mousedown', handleOutsideClick) + }, []) + function handleSave(data: ContactType) { + if (data.privacy === 'group') { + const normalizedGroups = normalizeSharedGroups(data.shared_groups) + if (normalizedGroups.length === 0) { + setSharedGroupsError(`${t('Phonebook.Select at least one group')}`) + return + } + data.shared_groups = normalizedGroups + } + + setSharedGroupsError('') //NETHVOICE uses the value '-' when entering a company that is unnamed //data.name === '' can only be true in the case where you enter a company setIsLoading(true) @@ -167,6 +240,18 @@ export function AddToPhonebookBox({ close }) { } } + function toggleSharedGroup(groupName: string) { + const normalizedGroups = normalizeSharedGroups(selectedGroups) + const nextGroups = normalizedGroups.includes(groupName) + ? normalizedGroups.filter((group) => group !== groupName) + : [...normalizedGroups, groupName] + + setValue('shared_groups', nextGroups, { shouldDirty: true }) + if (nextGroups.length > 0) { + setSharedGroupsError('') + } + } + return ( <>

{t('Phonebook.Visibility')}

-
- - -
-
- -
+ + + {watchPrivacy === 'group' && ( +
+

{t('Phonebook.Groups')}

+
+ + {isSharedGroupsDropdownOpen && ( +
+ {availableGroups.length > 0 ? ( + availableGroups.map((groupName) => { + const isSelected = selectedGroups.includes(groupName) + return ( + + ) + }) + ) : ( +
+ {t('Phonebook.No groups available')} +
+ )} +
+ )}
+ {sharedGroupsError && ( +

{sharedGroupsError}

+ )}
- + )}