diff --git a/public/locales/en/translations.json b/public/locales/en/translations.json index 73d62d6e..a7339c97 100644 --- a/public/locales/en/translations.json +++ b/public/locales/en/translations.json @@ -181,8 +181,17 @@ "Try changing your search filters": "Try changing your search filters", "Delete contact": "Delete contact", "Visibility": "Visibility", + "Visibility info": "Determine who can view this contact: public, private, or shared with specific groups", + "Everybody": "Everybody", "Only me": "Only me", "Public": "Public", + "Private": "Private", + "Groups": "Groups", + "Choose one or more groups": "Choose one or more groups", + "Selected": "Selected", + "No groups available": "No groups available", + "Select at least one group": "Select at least one group", + "Cannot create contact": "You do not have permission to create this contact", "Open contact menu": "Open contact menu", "Last switchboard calls": "Last switchboard calls", "Last personal calls": "Last personal calls", diff --git a/public/locales/it/translations.json b/public/locales/it/translations.json index bef48a86..41242861 100644 --- a/public/locales/it/translations.json +++ b/public/locales/it/translations.json @@ -181,8 +181,16 @@ "Try changing your search filters": "Prova a cambiare i tuoi filtri di ricerca", "Delete contact": "Elimina contact", "Visibility": "Visibilità", + "Visibility info": "Determina chi può vedere questo contatto: pubblico, privato o condiviso con gruppi specifici", "Only me": "Solo me", "Public": "Public", + "Private": "Privato", + "Groups": "Gruppi", + "Choose one or more groups": "Scegli uno o più gruppi", + "Selected": "Selezionati", + "No groups available": "Nessun gruppo disponibile", + "Select at least one group": "Seleziona almeno un gruppo", + "Cannot create contact": "Non hai il permesso di creare questo contatto", "Open contact menu": "Open contact menu", "Last switchboard calls": "Last switchboard calls", "Last personal calls": "Last personal calls", diff --git a/src/main/main.ts b/src/main/main.ts index 32929c53..b0ccaf87 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -848,11 +848,15 @@ async function createNethLink(show: boolean = true) { } async function checkForUpdate() { - Log.info('Current app version:', app.getVersion(), 'check for updates...') - const latestVersionData = await NetworkController.instance.get(GIT_RELEASES_URL) - Log.info('Head add version:', latestVersionData.name) - if (latestVersionData.name !== ("v" + app.getVersion()) || isDev()) { - NethLinkController.instance.sendUpdateNotification() + try { + Log.info('Current app version:', app.getVersion(), 'check for updates...') + const latestVersionData = await NetworkController.instance.get(GIT_RELEASES_URL) + Log.info('Head add version:', latestVersionData.name) + if (latestVersionData.name !== ("v" + app.getVersion()) || isDev()) { + NethLinkController.instance.sendUpdateNotification() + } + } catch (error: any) { + Log.warning('Skipping update check after network error', error?.message || error) } } diff --git a/src/renderer/public/locales/en/translations.json b/src/renderer/public/locales/en/translations.json index 3707b604..bbc826c6 100644 --- a/src/renderer/public/locales/en/translations.json +++ b/src/renderer/public/locales/en/translations.json @@ -181,8 +181,17 @@ "Try changing your search filters": "Try changing your search filters", "Delete contact": "Delete contact", "Visibility": "Visibility", + "Visibility info": "Determine who can view this contact: public, private, or shared with specific groups", + "Everybody": "Everybody", "Only me": "Only me", "Public": "Public", + "Private": "Private", + "Groups": "Groups", + "Choose one or more groups": "Choose one or more groups", + "Selected": "Selected", + "No groups available": "No groups available", + "Select at least one group": "Select at least one group", + "Cannot create contact": "You do not have permission to create this contact", "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..87e8450a 100644 --- a/src/renderer/public/locales/it/translations.json +++ b/src/renderer/public/locales/it/translations.json @@ -181,8 +181,17 @@ "Try changing your search filters": "Prova a cambiare i tuoi filtri di ricerca", "Delete contact": "Elimina contact", "Visibility": "Visibilità", + "Visibility info": "Determina chi può vedere questo contatto: pubblico, privato o condiviso con gruppi specifici", + "Everybody": "Tutti", "Only me": "Solo me", - "Public": "Public", + "Public": "Pubblico", + "Private": "Privato", + "Groups": "Gruppi", + "Choose one or more groups": "Scegli uno o più gruppi", + "Selected": "Selezionati", + "No groups available": "Nessun gruppo disponibile", + "Select at least one group": "Seleziona almeno un gruppo", + "Cannot create contact": "Non hai il permesso di creare questo contatto", "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..2f99a846 100644 --- a/src/renderer/src/components/Modules/NethVoice/PhonebookModule/AddToPhonebookBox.tsx +++ b/src/renderer/src/components/Modules/NethVoice/PhonebookModule/AddToPhonebookBox.tsx @@ -1,7 +1,14 @@ -import { useState, useEffect, useRef } from 'react' +import { useState, useEffect, useMemo, 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, + faCircleInfo, + faSpinner as LoadingIcon, + faUsers, + faXmark, +} from '@fortawesome/free-solid-svg-icons' import { ContactType } from '@shared/types' import { useForm, SubmitHandler } from 'react-hook-form' import { t } from 'i18next' @@ -13,15 +20,29 @@ 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, + getAllowedOperatorGroupsIds, + getPresencePanelPermissions, + getVisiblePhonebookGroups, + normalizeSharedGroups, +} from '@shared/phonebook' +import classNames from 'classnames' +import { CustomThemedTooltip } from '@renderer/components/Nethesis/CurstomThemedTooltip' 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 +81,9 @@ export function AddToPhonebookBox({ close }) { .and(baseSchema) const [isLoading, setIsLoading] = useState(false) + const [isSharedGroupsDropdownOpen, setIsSharedGroupsDropdownOpen] = useState(false) + const [sharedGroupsError, setSharedGroupsError] = useState('') + const [visibilityError, setVisibilityError] = useState('') const { register, @@ -73,6 +97,7 @@ export function AddToPhonebookBox({ close }) { } = useForm({ defaultValues: { privacy: '', + shared_groups: [], type: '', name: '', company: '', @@ -86,6 +111,41 @@ export function AddToPhonebookBox({ close }) { }) const watchType = watch('type') + const watchPrivacy = watch('privacy') + const selectedGroups = watch('shared_groups') || [] + const profile = account?.data?.profile + const username = account?.data?.username || account?.username + const presencePanelPermissions = getPresencePanelPermissions(profile) + const availableGroups = useMemo( + () => + getVisiblePhonebookGroups( + getAllowedOperatorGroupsIds(profile), + operators?.groups, + presencePanelPermissions?.all_groups?.value, + username, + ), + [operators?.groups, presencePanelPermissions?.all_groups?.value, profile, username], + ) + const visibilityOptions = useMemo( + () => + [ + { + id: 'public', + label: t('Phonebook.Public'), + }, + { + id: 'private', + label: t('Phonebook.Private'), + }, + { + id: 'group', + label: t('Phonebook.Group'), + }, + ].filter((option) => + canWritePhonebookVisibility(profile, option.id as 'public' | 'private' | 'group'), + ), + [profile], + ) useEffect(() => { !!errors.name && trigger('name') @@ -98,6 +158,7 @@ export function AddToPhonebookBox({ close }) { useEffect(() => { setValue('privacy', 'public') + setValue('shared_groups', []) setValue('type', 'person') if (searchText != undefined) { @@ -120,7 +181,62 @@ export function AddToPhonebookBox({ close }) { } }, []) + useEffect(() => { + const allowedVisibilityValues = visibilityOptions.map((option) => option.id) + if (!allowedVisibilityValues.includes(watchPrivacy || '')) { + setValue('privacy', visibilityOptions[0]?.id || '') + } + }, [setValue, visibilityOptions, watchPrivacy]) + + useEffect(() => { + if (watchPrivacy !== 'group') { + setValue('shared_groups', []) + setSharedGroupsError('') + setIsSharedGroupsDropdownOpen(false) + } + }, [setValue, watchPrivacy]) + + useEffect(() => { + const visibleSelectedGroups = normalizeSharedGroups(selectedGroups).filter((groupName) => + availableGroups.includes(groupName), + ) + + if (visibleSelectedGroups.length !== selectedGroups.length) { + setValue('shared_groups', visibleSelectedGroups) + } + }, [availableGroups, selectedGroups, setValue]) + + 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 (!canWritePhonebookVisibility(profile, data.privacy as 'public' | 'private' | 'group')) { + setVisibilityError(`${t('Phonebook.Cannot create contact')}`) + return + } + + setVisibilityError('') + 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 +283,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 ( <> -