MT#65003 Use composable helpers instead of direct Vuex store access

- Replace direct `useStore` usage with composable helpers
  (useActions, useState, useGetters)
- Add `setPopupShow` action to wrap `popupShow` mutation
  and eliminate mutations in favor of `setPopupShow` action
- Remove `usePbx` and `useUser` composables
- Update documentation

This prepares the codebase for eventual Pinia migration by:
- Avoiding mutations (Pinia has no mutations)
- Centralizing store access through helpers
- Enabling future migration with minimal component changes

Change-Id: Iddc63b8c3c39fe1a06bb6a78ef322f01fe77a9af
(cherry picked from commit e64887e492)
mr26.0
Debora Crescenzo 1 month ago
parent 8760189634
commit 4233c0432c

@ -33,8 +33,8 @@ This document describes the architecture of the ngcp-csc-ui project (Customer Se
- `router/`: route definitions and meta configuration in `src/router/routes.js`. - `router/`: route definitions and meta configuration in `src/router/routes.js`.
- `components/`, `pages/`, `layouts/`: UI building blocks and pages organized by feature. - `components/`, `pages/`, `layouts/`: UI building blocks and pages organized by feature.
- `assets/`, `helpers/`, `mixins/`, `validators/`: supporting libraries and utilities. - `assets/`, `helpers/`, `mixins/`, `validators/`: supporting libraries and utilities.
- `composables/`: Reusable composition functions that encapsulate state management logic and provide reactive access to Vuex store. Notable files: - `composables/`: Reusable composition functions that encapsulate logic and provide reactive access to Vuex store. Notable files:
- `src/composables/useUser.js` — provides reactive access to user state, authentication actions, and permission checks (capabilities, platform features, profile attributes). - `src/composables/useStore.js` — provides helpers (`useState`, `useGetters`, `useActions`) for accessing Vuex store modules in Composition API components. **Note: `useMutations` is available but should be avoided. Use `useActions` instead for Pinia migration compatibility.**
## HTTP API layer (src/api/common.js) ## HTTP API layer (src/api/common.js)
@ -98,10 +98,11 @@ VoIP notes and constraints:
## Composables Layer ## Composables Layer
Starting with Vue 3 migration, the application introduces composables (`src/composables/`) as an optional layer for accessing Vuex store in Composition API components: Starting with Vue 3 migration, the application uses composables (`src/composables/`) for accessing Vuex store in Composition API components:
- **Purpose**: Provide reactive, encapsulated access to store state and actions - **Purpose**: Provide reactive, encapsulated access to store state and actions
- **Available composables**: `useStore.js` (generic), `useUser.js` (authentication), `usePbx.js` (PBX features), `useGlobals.js` (global properties), `useWait.js` (loading states) - **Available composables**: `useStore.js` (generic helpers), `useWait.js` (loading states)
- **Recommended pattern**: Use `useState`, `useGetters`, and `useActions` helpers. Avoid `useMutations` and instead wrap mutations in actions for Pinia migration compatibility (Pinia has no mutations).
- **Coexistence**: Options API components continue using `mapState`/`mapGetters`/`mapActions`; composables are used when converting to Composition API - **Coexistence**: Options API components continue using `mapState`/`mapGetters`/`mapActions`; composables are used when converting to Composition API
- **Details**: See `migration-guide.md` for usage patterns and `composables.md` for API reference - **Details**: See `migration-guide.md` for usage patterns and `composables.md` for API reference

@ -16,6 +16,8 @@ Generic helpers for accessing Vuex store. It's a wrapper around Vuex that makes
*Problem it solves*: *Problem it solves*:
In Composition API, you can't use `mapState`, `mapGetters`, `mapActions` like in Options API. You need a different approach to access store state, getters, and actions reactively. In Composition API, you can't use `mapState`, `mapGetters`, `mapActions` like in Options API. You need a different approach to access store state, getters, and actions reactively.
> ⚠️ Use `useState`, `useGetters`, and `useActions` helpers. **Avoid `useMutations` if possible** — wrap mutations in actions instead. Pinia has no mutations, so using `useActions` now makes future migration easier.
### Functions ### Functions
#### `useStore()` #### `useStore()`
@ -85,10 +87,11 @@ await login({ username: 'test', password: 'pass' })
</script> </script>
``` ```
#### `useMutations(moduleName, keys)` #### `useMutations(moduleName, keys)` **Avoid unless it's absolutely necessary**
Map mutations from a module to functions. Map mutations from a module to functions.
*Use when*: You need to commit store mutations (rarely needed - prefer actions).
Remember, Pinia has no mutations. Create an action in your store that calls the mutation, then use `useActions` to access it.This helper remains in case the mutation needs to be accessed directly for some reason.
```vue ```vue
<script setup> <script setup>
@ -212,81 +215,203 @@ const formatPhone = (number) => filters.numberFormat(number)
## Creating New Composables ## Creating New Composables
Template for creating module-specific composables: ### When to Create a Composable
```javascript **Create composables for:**
import { computed } from 'vue' - Feature-specific logic that doesn't need global state (phonebook, NCOS, registrations)
import { useStore } from './useStore' - Reusable business logic across components
- Local state management with associated operations
- API operations that don't affect global app state
export function useMyModule() { ### Template for Feature Composables
const store = useStore()
// Map state Create composables that manage their own local state and business logic:
const myData = computed(() => store.state.myModule.data)
const loading = computed(() => store.state.myModule.loading) ```javascript
import { ref, computed } from 'vue'
import { getNcosLevels, getNcosSet, setPreference } from 'src/api/subscriber'
import { getSubscriberId } from 'src/auth'
/**
* Composable for managing NCOS (Network Class of Service) levels and sets.
* Manages local state and provides API operations.
*/
export function useNcos() {
// Local reactive state
const levels = ref([])
const sets = ref([])
const loading = ref(false)
const error = ref(null)
// Computed properties
const hasLevels = computed(() => levels.value.length > 0)
const hasSets = computed(() => sets.value.length > 0)
// Business logic functions
const loadLevels = async () => {
loading.value = true
error.value = null
try {
const list = await getNcosLevels()
levels.value = list.items.map(ncos => ({
label: ncos.level,
value: ncos.id
}))
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
// Map getters const loadSets = async () => {
const isValid = computed(() => store.getters['myModule/isValid']) loading.value = true
error.value = null
try {
const list = await getNcosSet()
sets.value = list.map(setNcos => ({
label: setNcos.name,
value: setNcos.id
}))
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
// Map actions const updateLevel = async (value) => {
const loadData = (payload) => store.dispatch('myModule/loadData', payload) loading.value = true
const saveData = (payload) => store.dispatch('myModule/saveData', payload) error.value = null
try {
await setPreference(getSubscriberId(), 'ncos', value)
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
// Map mutations const reset = () => {
const reset = () => store.commit('myModule/reset') levels.value = []
sets.value = []
error.value = null
}
return { return {
// State // State
myData, levels,
sets,
loading, loading,
error,
// Getters // Computed
isValid, hasLevels,
hasSets,
// Actions // Actions
loadData, loadLevels,
saveData, loadSets,
updateLevel,
// Mutations
reset reset
} }
} }
``` ```
### Using the Custom Composable ### Using the Feature Composable
```vue ```vue
<script setup> <script setup>
import { useMyModule } from 'src/composables/useMyModule' import { onMounted } from 'vue'
import { useNcos } from 'src/composables/useNcos'
const { const {
myData, levels,
sets,
loading, loading,
isValid, error,
loadData, hasLevels,
saveData, loadLevels,
reset loadSets,
} = useMyModule() updateLevel
} = useNcos()
// Use the composable
const handleLoad = async () => { // Load data on mount
await loadData({ id: 1 }) onMounted(async () => {
if (isValid.value) { await loadLevels()
console.log('Data loaded:', myData.value) await loadSets()
})
const handleUpdateLevel = async (levelId) => {
try {
await updateLevel(levelId)
// Show success message
} catch (err) {
// Error already set in composable
console.error('Failed to update level:', error.value)
} }
} }
const handleSave = async () => {
await saveData({ ...myData.value, updated: true })
}
</script> </script>
<template> <template>
<div> <div>
<button @click="handleLoad" :disabled="loading">Load Data</button> <div v-if="loading">Loading...</div>
<button @click="handleSave" :disabled="!isValid">Save Data</button> <div v-else-if="error" class="error">{{ error }}</div>
<button @click="reset">Reset</button> <div v-else>
<div v-if="myData">{{ myData }}</div> <select
@change="handleUpdateLevel($event.target.value)"
:disabled="!hasLevels"
>
<option value="">Select NCOS Level</option>
<option
v-for="level in levels"
:key="level.value"
:value="level.value"
>
{{ level.label }}
</option>
</select>
</div>
</div> </div>
</template> </template>
``` ```
### Accessing Store Within a Composable
If your composable needs global store state, use `useGetters` or `useActions`:
```javascript
import { ref } from 'vue'
import { useGetters } from 'src/composables/useStore'
import { getCustomerPhonebook } from 'src/api/subscriber'
export function useCustomerPhonebook() {
// Access global state
const { getCustomerId } = useGetters('user', ['getCustomerId'])
// Local state
const phonebook = ref([])
const loading = ref(false)
const loadPhonebook = async () => {
loading.value = true
try {
const list = await getCustomerPhonebook({
customerId: getCustomerId.value
})
phonebook.value = list.data
} finally {
loading.value = false
}
}
return {
phonebook,
loading,
loadPhonebook
}
}
```

@ -152,27 +152,25 @@ if (apiIsCanceledRequest(err)) {
## Composables for State Access ## Composables for State Access
Starting with the migration to Vue 3 Composition API, the application introduces composables as a layer between components and Vuex store: Starting with the migration to Vue 3 Composition API, the application uses composables for accessing Vuex store:
1. **Purpose**: Composables provide reactive access to store state and actions without direct store coupling in components. 1. **Purpose**: Composables provide reactive access to store state, getters, and actions in Composition API components.
2. **Pattern** (based on `src/composables/useUser.js`): 2. **Pattern** (using `src/composables/useStore.js` helpers):
- Import store and computed from Vue - Import the store helpers (`useState`, `useGetters`, `useActions`)
- Define reactive refs using `computed(() => store.state/getters.xxx)` - Map state, getters, or actions from specific store modules
- Wrap store actions in simple functions - All returned values are reactive computed refs
- Return an object with all reactive properties and methods
3. **Example**: 3. **Example**:
```javascript ```javascript
import { computed } from 'vue' import { useGetters, useActions } from 'src/composables/useStore'
import { store } from 'src/boot/store'
export function useUser() { // In your component setup
const isAdmin = computed(() => store.getters['user/isAdmin']) const { isAdmin, isLogged } = useGetters('user', ['isAdmin', 'isLogged'])
const login = (credentials) => store.dispatch('user/login', credentials) const { login, logout } = useActions('user', ['login', 'logout'])
return { isAdmin, login } // isAdmin and isLogged are computed refs
} // login and logout are action functions
``` ```
## Store Access Patterns ## Store Access Patterns
@ -195,19 +193,15 @@ export default {
### Composition API with `<script setup>` (Recommended) ### Composition API with `<script setup>` (Recommended)
For components using `<script setup>`, use store composables: For components using `<script setup>`, use store helpers:
```vue ```vue
<script setup> <script setup>
import { useUser } from 'src/composables/useUser' import { useState, useGetters, useActions } from 'src/composables/useStore'
const { const { subscriber } = useState('user', ['subscriber'])
subscriber, // reactive state (computed ref) const { isLogged, isAdmin } = useGetters('user', ['isLogged', 'isAdmin'])
isLogged, // reactive getter (computed ref) const { login, logout } = useActions('user', ['login', 'logout'])
isAdmin, // reactive getter (computed ref)
login, // action function
logout // action function
} = useUser()
// Use them directly // Use them directly
const handleLogin = async () => { const handleLogin = async () => {
@ -226,7 +220,7 @@ const handleLogin = async () => {
</template> </template>
``` ```
**Note**: Composables return computed refs, so access values with `.value` in script, but not in template. **Note**: Store helpers return computed refs, so access values with `.value` in script, but not in template.
### Direct Store Access (Services/Utilities) ### Direct Store Access (Services/Utilities)
@ -324,11 +318,11 @@ const actions = {
```vue ```vue
<script setup> <script setup>
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useUser } from 'src/composables/useUser' import { useState, useGetters, useActions } from 'src/composables/useStore'
import { useActions } from 'src/composables/useStore'
// Get user state and actions // Get user state and getters
const { subscriber, isLogged, isAdmin } = useUser() const { subscriber } = useState('user', ['subscriber'])
const { isLogged, isAdmin } = useGetters('user', ['isLogged', 'isAdmin'])
// Get profile actions // Get profile actions
const { loadProfile, updateProfile } = useActions('profile', [ const { loadProfile, updateProfile } = useActions('profile', [

@ -21,7 +21,7 @@
| **Boot Files** | `event-bus.js`, `filters.js`, `constants.js`, `appConfig.js`, `vuelidate.js` | Enhanced | None - new features available | | **Boot Files** | `event-bus.js`, `filters.js`, `constants.js`, `appConfig.js`, `vuelidate.js` | Enhanced | None - new features available |
| **API Layer** | `api/common.js` | Fixed | None - transparent fix | | **API Layer** | `api/common.js` | Fixed | None - transparent fix |
| **Store** | `store/common.js`, `store/apiHelper.js` | New helpers | Optional - use in new code | | **Store** | `store/common.js`, `store/apiHelper.js` | New helpers | Optional - use in new code |
| **Composables** | `useStore.js`, `useUser.js`, `usePbx.js`, `useGlobals.js` | New patterns | Use when converting to Composition API | | **Composables** | `useStore.js`, `useGlobals.js` | Store access helpers | Use when converting to Composition API |
--- ---
@ -59,11 +59,11 @@ export default {
} }
// New Composition API // New Composition API
import { useUser } from 'src/composables/useUser' import { useGetters } from 'src/composables/useStore'
export default { export default {
setup() { setup() {
const { isLogged, username } = useUser() const { isLogged, username } = useGetters('user', ['isLogged', 'getUsername'])
return { isLogged, username } return { isLogged, username }
} }
} }
@ -95,9 +95,9 @@ When converting components to Composition API:
| Composable | Purpose | Use When | | Composable | Purpose | Use When |
|------------|---------|----------| |------------|---------|----------|
| `useUser()` | User auth, permissions | Checking login, admin, capabilities | | `useGetters(module, keys)` | Access store getters | Need reactive computed state |
| `usePbx()` | PBX configuration | Managing seats, groups, devices | | `useActions(module, keys)` | Access store actions | Need to dispatch actions |
| `useStore()` | Generic store access | Need custom store logic | | `useState(module, keys)` | Access store state | Need raw state values |
| `useFilters()` | Formatting functions | Dates, numbers, currencies | | `useFilters()` | Formatting functions | Dates, numbers, currencies |
| `useAppConfig()` | App configuration | Need API URLs, settings | | `useAppConfig()` | App configuration | Need API URLs, settings |
| `useValidationErrors()` | Form validation | Vuelidate error messages | | `useValidationErrors()` | Form validation | Vuelidate error messages |
@ -116,7 +116,7 @@ computed: {
} }
// After (Composition API) // After (Composition API)
const { subscriber } = useUser() const { subscriber } = useState('user', ['subscriber'])
``` ```
### Pattern 2: Actions with Loading State ### Pattern 2: Actions with Loading State
@ -130,7 +130,8 @@ methods: {
} }
// After // After
const { loginRequesting, login } = useUser() const { loginRequesting } = useGetters('user', ['loginRequesting'])
const { login } = useActions('user', ['login'])
``` ```
### Pattern 3: Filters ### Pattern 3: Filters
@ -149,6 +150,61 @@ const formatted = filters.readableDate(new Date())
--- ---
## Future-Proofing: Pinia Migration
The composable helper pattern is designed to make eventual Pinia migration as easy as possible.
### Why Avoid `useMutations`
**Pinia has no mutations!** In Pinia, actions can directly modify state:
```javascript
// ❌ AVOID: useMutations (won't work with Pinia)
import { useMutations } from 'src/composables/useStore'
const { setProfile } = useMutations('user', ['setProfile'])
// ✅ USE: useActions instead (works with both Vuex & Pinia)
import { useActions } from 'src/composables/useStore'
const { setProfile } = useActions('user', ['setProfile'])
```
### Vuex Store Pattern (Transition-Ready)
Wrap mutations in actions, even for simple setters:
```javascript
// Vuex store (current)
export default {
state: { profile: null },
mutations: {
setProfile(state, data) {
state.profile = data
}
},
actions: {
setProfile({ commit }, data) {
commit('setProfile', data) // Action wraps mutation
}
}
}
```
When migrating to Pinia later, only the store changes and components stay identical:
```javascript
// Pinia store (future)
export const useUserStore = defineStore('user', {
state: () => ({ profile: null }),
actions: {
setProfile(data) {
this.profile = data // Direct assignment
}
}
})
```
---
## Migration Checklist ## Migration Checklist
When converting a component to Composition API: When converting a component to Composition API:
@ -156,6 +212,7 @@ When converting a component to Composition API:
- [ ] Replace `mapState` with composable state access - [ ] Replace `mapState` with composable state access
- [ ] Replace `mapGetters` with composable getters - [ ] Replace `mapGetters` with composable getters
- [ ] Replace `mapActions` with composable actions - [ ] Replace `mapActions` with composable actions
- [ ] **Avoid `useMutations`** - use `useActions` instead (Pinia-ready pattern)
- [ ] Replace `this.$filters` with direct imports or `useFilters()` - [ ] Replace `this.$filters` with direct imports or `useFilters()`
- [ ] Replace `this.$appConfig` with `useAppConfig()` or direct import - [ ] Replace `this.$appConfig` with `useAppConfig()` or direct import
- [ ] Replace `this.emitter.$emit` with `eventBus.$emit` or direct import - [ ] Replace `this.emitter.$emit` with `eventBus.$emit` or direct import
@ -169,7 +226,7 @@ When converting a component to Composition API:
## Next Steps ## Next Steps
1. **Read this guide** 1. **Read this guide**
2. **Review example composables**: `src/composables/useUser.js`, `src/composables/usePbx.js` 2. **Review composables documentation**: `doc/composables.md`
3. **Try converting one simple component** to get familiar with patterns 3. **Try converting one simple component** to get familiar with patterns
4. **Ask questions** if stuck 4. **Ask questions** if stuck

@ -163,14 +163,18 @@ export default {
```vue ```vue
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { useUser } from 'src/composables/useUser' import { useGetters } from 'src/composables/useStore'
import { PROFILE_ATTRIBUTE_MAP, LICENSES } from 'src/constants' import { PROFILE_ATTRIBUTE_MAP, LICENSES } from 'src/constants'
const { const {
hasSubscriberProfileAttribute, hasSubscriberProfileAttribute,
hasLicenses, hasLicenses,
isSpCe isSpCe
} = useUser() } = useGetters('user', [
'hasSubscriberProfileAttribute',
'hasLicenses',
'isSpCe'
])
const menuItems = [ const menuItems = [
{ {
@ -178,8 +182,8 @@ const menuItems = [
icon: 'call', icon: 'call',
label: 'Calls', label: 'Calls',
visible: computed(() => visible: computed(() =>
hasSubscriberProfileAttribute(PROFILE_ATTRIBUTE_MAP.cscCalls) && hasSubscriberProfileAttribute.value(PROFILE_ATTRIBUTE_MAP.cscCalls) &&
(isSpCe.value || hasLicenses([LICENSES.csc_calls])) (isSpCe.value || hasLicenses.value([LICENSES.csc_calls]))
) )
}, },
... ...
@ -187,7 +191,7 @@ const menuItems = [
</script> </script>
``` ```
**Note**: When using composables, getters return `computed` refs, so access the value with `.value` when needed in computed expressions. **Note**: When using store helpers, getters return `computed` refs, so access the value with `.value` when needed in computed expressions.
### Menu Hierarchy and Nested Items ### Menu Hierarchy and Nested Items

@ -54,12 +54,12 @@
<script setup> <script setup>
import AppBadgeApple from 'components/AppBadgeApple' import AppBadgeApple from 'components/AppBadgeApple'
import AppBadgeGoogle from 'components/AppBadgeGoogle' import AppBadgeGoogle from 'components/AppBadgeGoogle'
import { useUser } from 'src/composables/useUser' import { useState } from 'src/composables/useStore'
import { computed } from 'vue' import { computed } from 'vue'
defineOptions({ name: 'AuiMobileAppBadges' }) defineOptions({ name: 'AuiMobileAppBadges' })
const { platformInfo } = useUser() const { platformInfo } = useState('user', ['platformInfo'])
const appNameAndroid = computed(() => platformInfo.value?.app?.android?.name) const appNameAndroid = computed(() => platformInfo.value?.app?.android?.name)
const appNameApple = computed(() => platformInfo.value?.app?.apple?.name) const appNameApple = computed(() => platformInfo.value?.app?.apple?.name)

@ -1,6 +1,6 @@
<template> <template>
<q-popup-proxy <q-popup-proxy
@before-show="$store.commit('callForwarding/popupShow', null)" @before-show="setPopupShow(null)"
> >
<q-list <q-list
v-if="!gridView" v-if="!gridView"
@ -39,6 +39,7 @@
</template> </template>
<script> <script>
import { mapActions } from 'vuex'
export default { export default {
name: 'CscPopupMenu', name: 'CscPopupMenu',
props: { props: {
@ -46,6 +47,9 @@ export default {
type: Boolean, type: Boolean,
default: false default: false
} }
},
methods: {
...mapActions('callForwarding', ['setPopupShow'])
} }
} }
</script> </script>

@ -20,7 +20,7 @@
v-model="timeout" v-model="timeout"
buttons buttons
data-cy="csc-forwarding-ring-timeout-global-editbox" data-cy="csc-forwarding-ring-timeout-global-editbox"
@before-show="$store.commit('callForwarding/popupShow','after-ring-timeout')" @before-show="setPopupShow('after-ring-timeout')"
@save="updateRingTimeoutEvent($event)" @save="updateRingTimeoutEvent($event)"
> >
<csc-input <csc-input
@ -70,7 +70,8 @@ export default {
}, },
methods: { methods: {
...mapActions('callForwarding', [ ...mapActions('callForwarding', [
'updateRingTimeout' 'updateRingTimeout',
'setPopupShow'
]), ]),
async updateRingTimeoutEvent (event) { async updateRingTimeoutEvent (event) {
this.$wait.start('csc-cf-mappings-full') this.$wait.start('csc-cf-mappings-full')

@ -97,11 +97,10 @@
import { useVuelidate } from '@vuelidate/core' import { useVuelidate } from '@vuelidate/core'
import { required, requiredIf } from '@vuelidate/validators' import { required, requiredIf } from '@vuelidate/validators'
import CscInput from 'src/components/form/CscInput' import CscInput from 'src/components/form/CscInput'
import { useUser } from 'src/composables/useUser' import { useGetters, useState } from 'src/composables/useStore'
import { DestinationType, getDestinationIcon } from 'src/helpers/destination' import { DestinationType, getDestinationIcon } from 'src/helpers/destination'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'
defineOptions({ name: 'CscCfAddForm' }) defineOptions({ name: 'CscCfAddForm' })
@ -115,16 +114,15 @@ defineProps({
const emit = defineEmits(['save', 'cancel']) const emit = defineEmits(['save', 'cancel'])
const { t } = useI18n() const { t } = useI18n()
const store = useStore()
const cfType = ref('cfu') const cfType = ref('cfu')
const destinationType = ref(null) const destinationType = ref(null)
const destinationNumber = ref(null) const destinationNumber = ref(null)
const customAnnouncement = ref(null) const customAnnouncement = ref(null)
const announcements = computed(() => store.state.callForwarding.announcements) const { announcements } = useState('callForwarding', ['announcements'])
const { isSpCe, isPbxEnabled } = useUser() const { isSpCe, isPbxEnabled } = useGetters('user', ['isSpCe', 'isPbxEnabled'])
const isDestinationNumber = computed(() => destinationType.value === 'number') const isDestinationNumber = computed(() => destinationType.value === 'number')
const isDestinationCustomAnnouncement = computed(() => destinationType.value === 'customhours') const isDestinationCustomAnnouncement = computed(() => destinationType.value === 'customhours')

@ -9,7 +9,7 @@
v-model="announcement" v-model="announcement"
buttons buttons
anchor="top left" anchor="top left"
@before-show="$store.commit('callForwarding/popupShow', null)" @before-show="setPopupShow(null)"
@save="$emit('input', $event)" @save="$emit('input', $event)"
> >
<q-select <q-select
@ -26,6 +26,7 @@
<script> <script>
import CscCfDestination from 'components/call-forwarding/CscCfDestination' import CscCfDestination from 'components/call-forwarding/CscCfDestination'
import { showGlobalError } from 'src/helpers/ui' import { showGlobalError } from 'src/helpers/ui'
import { mapActions } from 'vuex'
export default { export default {
name: 'CscCfDestinationCustomAnnouncement', name: 'CscCfDestinationCustomAnnouncement',
components: { CscCfDestination }, components: { CscCfDestination },
@ -51,6 +52,7 @@ export default {
} }
}, },
methods: { methods: {
...mapActions('callForwarding', ['setPopupShow']),
checkAnnouncement () { checkAnnouncement () {
const fieldFilled = this.announcement const fieldFilled = this.announcement
if (!fieldFilled) { if (!fieldFilled) {

@ -8,7 +8,7 @@
v-slot="scope" v-slot="scope"
v-model="number" v-model="number"
buttons buttons
@before-show="$store.commit('callForwarding/popupShow', null)" @before-show="setPopupShow(null)"
@save="$emit('input', $event)" @save="$emit('input', $event)"
> >
<csc-input <csc-input
@ -30,6 +30,7 @@
<script> <script>
import CscCfDestination from 'components/call-forwarding/CscCfDestination' import CscCfDestination from 'components/call-forwarding/CscCfDestination'
import CscInput from 'components/form/CscInput' import CscInput from 'components/form/CscInput'
import { mapActions } from 'vuex'
export default { export default {
name: 'CscCfDestinationNumber', name: 'CscCfDestinationNumber',
components: { CscInput, CscCfDestination }, components: { CscInput, CscCfDestination },
@ -45,6 +46,9 @@ export default {
number: this.$attrs.value number: this.$attrs.value
} }
}, },
methods: {
...mapActions('callForwarding', ['setPopupShow'])
},
watch: { watch: {
'$attrs.value' (value) { '$attrs.value' (value) {
this.number = value this.number = value

@ -54,7 +54,7 @@
v-slot="scope" v-slot="scope"
v-model="changedDestinationTimeout" v-model="changedDestinationTimeout"
buttons buttons
@before-show="$store.commit('callForwarding/popupShow', null)" @before-show="setPopupShow(null)"
@save="updateDestinationTimeoutEvent({ @save="updateDestinationTimeoutEvent({
destinationTimeout: $event, destinationTimeout: $event,
destinationIndex: destinationIndex, destinationIndex: destinationIndex,
@ -222,7 +222,8 @@ export default {
'moveDestination', 'moveDestination',
'updateDestinationTimeout', 'updateDestinationTimeout',
'rewriteDestination', 'rewriteDestination',
'updateAnnouncement' 'updateAnnouncement',
'setPopupShow'
]), ]),
async moveDestinationEvent (direction) { async moveDestinationEvent (direction) {
const targetIndex = direction === 'up' ? this.destinationIndex - 1 : this.destinationIndex + 1 const targetIndex = direction === 'up' ? this.destinationIndex - 1 : this.destinationIndex + 1

@ -1,52 +0,0 @@
import { useStore } from 'src/composables/useStore'
import { computed } from 'vue'
export function usePbx () {
const store = useStore()
// State
const numbers = computed(() => store.getters['pbx/numbers'])
const seatList = computed(() => store.state.pbx.seatList)
const groupList = computed(() => store.state.pbx.groupList)
const deviceProfileList = computed(() => store.state.pbx.deviceProfileList)
// Getters
const numberOptions = computed(() => store.getters['pbx/getNumberOptions'])
const seatOptions = computed(() => store.getters['pbx/getSeatOptions'])
const groupOptions = computed(() => store.getters['pbx/getGroupOptions'])
const subscriberOptions = computed(() => store.getters['pbx/getSubscriberOptions'])
// Loading states
const isNumbersRequesting = computed(() => store.getters['pbx/isNumbersRequesting'])
const isSubscribersRequesting = computed(() => store.getters['pbx/isSubscribersRequesting'])
// Actions
const loadNumbers = () => store.dispatch('pbx/loadNumbers')
const loadSubscribers = () => store.dispatch('pbx/loadSubscribers')
const loadProfiles = () => store.dispatch('pbx/loadProfiles')
const loadDeviceModel = (payload) => store.dispatch('pbx/loadDeviceModel', payload)
return {
// State
numbers,
seatList,
groupList,
deviceProfileList,
// Getters
numberOptions,
seatOptions,
groupOptions,
subscriberOptions,
// Loading states
isNumbersRequesting,
isSubscribersRequesting,
// Actions
loadNumbers,
loadSubscribers,
loadProfiles,
loadDeviceModel
}
}

@ -1,61 +0,0 @@
import { useStore } from 'src/composables/useStore'
import { computed } from 'vue'
export function useUser () {
const store = useStore()
// State
const subscriber = computed(() => store.state.user.subscriber)
const platformInfo = computed(() => store.state.user.platformInfo)
// Getters
const isLogged = computed(() => store.getters['user/isLogged'])
const username = computed(() => store.getters['user/getUsername'])
const isAdmin = computed(() => store.getters['user/isAdmin'])
const isPbxAdmin = computed(() => store.getters['user/isPbxAdmin'])
const isPbxEnabled = computed(() => store.getters['user/isPbxEnabled'])
const isSpCe = computed(() => store.getters['user/isSpCe'])
const loginRequesting = computed(() => store.getters['user/loginRequesting'])
const isPasswordChanging = computed(() => store.getters['user/isPasswordChanging'])
// Actions
const login = (credentials) => store.dispatch('user/login', credentials)
const logout = () => store.dispatch('user/logout')
const changePassword = (newPassword) => store.dispatch('user/changePassword', newPassword)
const changeSIPPassword = (newPassword) => store.dispatch('user/changeSIPPassword', newPassword)
const initUser = () => store.dispatch('user/initUser')
// Getters with parameters
const hasCapability = (capability) => store.getters['user/hasCapability'](capability)
const hasPlatformFeature = (feature) => store.getters['user/hasPlatformFeature'](feature)
const hasSubscriberProfileAttribute = (attribute) =>
store.getters['user/hasSubscriberProfileAttribute'](attribute)
return {
// State
subscriber,
platformInfo,
// Getters
isLogged,
username,
isAdmin,
isPbxAdmin,
isPbxEnabled,
isSpCe,
loginRequesting,
isPasswordChanging,
// Actions
login,
logout,
changePassword,
changeSIPPassword,
initUser,
// Getters with parameters
hasCapability,
hasPlatformFeature,
hasSubscriberProfileAttribute
}
}

@ -55,6 +55,7 @@ import { useVuelidate } from '@vuelidate/core'
import { helpers, numeric } from '@vuelidate/validators' import { helpers, numeric } from '@vuelidate/validators'
import CscPage from 'components/CscPage' import CscPage from 'components/CscPage'
import CscInputSaveable from 'components/form/CscInputSaveable' import CscInputSaveable from 'components/form/CscInputSaveable'
import { useActions, useGetters, useState } from 'src/composables/useStore'
import { useWait } from 'src/composables/useWait' import { useWait } from 'src/composables/useWait'
import { PROFILE_ATTRIBUTE_MAP } from 'src/constants' import { PROFILE_ATTRIBUTE_MAP } from 'src/constants'
import { showToast } from 'src/helpers/ui' import { showToast } from 'src/helpers/ui'
@ -65,7 +66,6 @@ import {
ref ref
} from 'vue' } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'
defineOptions({ name: 'CscPagePbxSettingsConference' }) defineOptions({ name: 'CscPagePbxSettingsConference' })
@ -79,7 +79,12 @@ const WAITERS = {
updateConferencePin: 'csc-pbx-call-settings-update-conference-pin' updateConferencePin: 'csc-pbx-call-settings-update-conference-pin'
} }
const store = useStore() const { subscriberPreferences } = useState('callSettings', ['subscriberPreferences'])
const { hasSubscriberProfileAttribute } = useGetters('user', ['hasSubscriberProfileAttribute'])
const { loadSubscriberPreferencesAction, fieldUpdateAction } = useActions('callSettings', [
'loadSubscriberPreferencesAction',
'fieldUpdateAction'
])
const wait = useWait() const wait = useWait()
const { t } = useI18n() const { t } = useI18n()
@ -89,9 +94,6 @@ const changes = reactive({
conferencePin: '' conferencePin: ''
}) })
const subscriberPreferences = computed(() => store.state.callSettings.subscriberPreferences)
const hasSubscriberProfileAttribute = computed(() => store.getters['user/hasSubscriberProfileAttribute'])
const rules = computed(() => ({ const rules = computed(() => ({
conferenceMaxParticipants: { conferenceMaxParticipants: {
numeric: helpers.withMessage( numeric: helpers.withMessage(
@ -138,7 +140,7 @@ async function saveConferenceMaxParticipants () {
if (hasConferenceMaxParticipantsChanged.value && v$.value.conferenceMaxParticipants.$errors.length <= 0) { if (hasConferenceMaxParticipantsChanged.value && v$.value.conferenceMaxParticipants.$errors.length <= 0) {
wait.start(WAITERS.updateConferenceMaxParticipants) wait.start(WAITERS.updateConferenceMaxParticipants)
try { try {
await store.dispatch('callSettings/fieldUpdateAction', { await fieldUpdateAction({
field: 'conference_max_participants', field: 'conference_max_participants',
value: changes.conferenceMaxParticipants value: changes.conferenceMaxParticipants
}) })
@ -156,7 +158,7 @@ async function saveConferencePin () {
if (hasConferencePinChanged.value && v$.value.conferencePin.$errors.length <= 0) { if (hasConferencePinChanged.value && v$.value.conferencePin.$errors.length <= 0) {
wait.start(WAITERS.updateConferencePin) wait.start(WAITERS.updateConferencePin)
try { try {
await store.dispatch('callSettings/fieldUpdateAction', { await fieldUpdateAction({
field: 'conference_pin', field: 'conference_pin',
value: changes.conferencePin value: changes.conferencePin
}) })
@ -173,7 +175,7 @@ async function saveConferencePin () {
onMounted(async () => { onMounted(async () => {
wait.start(WAITERS.loadPreferences) wait.start(WAITERS.loadPreferences)
try { try {
await store.dispatch('callSettings/loadSubscriberPreferencesAction') await loadSubscriberPreferencesAction()
applyDefaultData() applyDefaultData()
isInitialized.value = true isInitialized.value = true
} finally { } finally {

@ -901,3 +901,7 @@ export async function updateAnnouncement ({ dispatch, commit, state }, payload)
export function resetCallForwardingState ({ commit }) { export function resetCallForwardingState ({ commit }) {
commit('resetState') commit('resetState')
} }
export function setPopupShow ({ commit }, popupId) {
commit('popupShow', popupId)
}

Loading…
Cancel
Save