You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ngcp-csc-ui/doc/composables.md

418 lines
9.1 KiB

# Composables API Reference
Detailed usage examples for composables using `<script setup>`.
## Table of Contents
- [useStore](#usestore)
- [useGlobals](#useglobals)
---
## useStore
Generic helpers for accessing Vuex store. It's a wrapper around Vuex that makes it easier to use the store without directly importing it everywhere.
*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.
> ⚠️ 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
#### `useStore()`
Returns Vuex store instance.
*Use when*: You need direct store access (rare).
```vue
<script setup>
import { computed } from 'vue'
import { useStore } from 'src/composables/useStore'
const store = useStore()
const user = computed(() => store.state.user.subscriber)
</script>
```
#### `useState(moduleName, keys)`
Map state properties from a module to reactive computed refs.
*Use when*: You need reactive access to store state.
```vue
<script setup>
import { useState } from 'src/composables/useStore'
// Single property
const subscriber = useState('user', 'subscriber')
// Multiple properties
const { subscriber, loginState } = useState('user', [
'subscriber',
'loginState'
])
</script>
```
#### `useGetters(moduleName, keys)`
Map getters from a module to reactive computed refs.
*Use when*: You need reactive access to store getters.
```vue
<script setup>
import { useGetters } from 'src/composables/useStore'
const { isLogged, isAdmin } = useGetters('user', [
'isLogged',
'isAdmin'
])
</script>
```
#### `useActions(moduleName, keys)`
Map actions from a module to functions.
*Use when*: You need to dispatch store actions.
```vue
<script setup>
import { useActions } from 'src/composables/useStore'
const { login, logout } = useActions('user', ['login', 'logout'])
// Use the actions
await login({ username: 'test', password: 'pass' })
</script>
```
#### `useMutations(moduleName, keys)` **Avoid unless it's absolutely necessary**
Map mutations from a module to functions.
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
<script setup>
import { useMutations } from 'src/composables/useStore'
// Single mutation
const setUser = useMutations('user', 'setUser')
setUser({ id: 1, name: 'John' })
// Multiple mutations
const { setUser, clearUser } = useMutations('user', ['setUser', 'clearUser'])
</script>
```
### Complete Example: Options API vs `<script setup>`
**Options API:**
```vue
<script>
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapState('user', ['subscriber']),
...mapGetters('user', ['isLogged', 'isAdmin'])
},
methods: {
...mapActions('user', ['login', 'logout']),
async handleLogin() {
await this.login({ username: 'test', password: 'pass' })
if (this.isLogged) {
console.log('Welcome', this.subscriber.username)
}
}
}
}
</script>
```
**`<script setup>`:**
```vue
<script setup>
import { useState, useGetters, useActions } from 'src/composables/useStore'
// Map state
const { subscriber } = useState('user', ['subscriber'])
// Map getters
const { isLogged, isAdmin } = useGetters('user', ['isLogged', 'isAdmin'])
// Map actions
const { login, logout } = useActions('user', ['login', 'logout'])
// Use them
const handleLogin = async () => {
await login({ username: 'test', password: 'pass' })
if (isLogged.value) {
console.log('Welcome', subscriber.value.username)
}
}
</script>
<template>
<div>
<button @click="handleLogin">Login</button>
<button @click="logout">Logout</button>
<div v-if="isLogged">Welcome {{ subscriber.username }}</div>
</div>
</template>
```
---
## useGlobals
Access global application properties.
### Available Functions
- `useAppConfig()` - Application configuration
- `useConstants()` - Global constants
- `useFilters()` - Formatting functions
- `useValidationErrors()` - Validation helpers
### Example: useAppConfig()
```vue
<script setup>
import { useAppConfig } from 'src/composables/useGlobals'
const config = useAppConfig()
const apiEndpoint = `${config.baseHttpUrl}/api/users`
</script>
```
### Example: useFilters()
```vue
<script setup>
import { useFilters } from 'src/composables/useGlobals'
const filters = useFilters()
const formatDate = (date) => filters.readableDate(date)
const formatPhone = (number) => filters.numberFormat(number)
</script>
<template>
<div>
<p>Date: {{ formatDate(new Date()) }}</p>
<p>Phone: {{ formatPhone('1234567890') }}</p>
</div>
</template>
```
---
## Creating New Composables
### When to Create a Composable
**Create composables for:**
- Feature-specific logic that doesn't need global state (phonebook, NCOS, registrations)
- Reusable business logic across components
- Local state management with associated operations
- API operations that don't affect global app state
### Template for Feature Composables
Create composables that manage their own local state and business logic:
```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
}
}
const loadSets = async () => {
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
}
}
const updateLevel = async (value) => {
loading.value = true
error.value = null
try {
await setPreference(getSubscriberId(), 'ncos', value)
} catch (err) {
error.value = err.message
throw err
} finally {
loading.value = false
}
}
const reset = () => {
levels.value = []
sets.value = []
error.value = null
}
return {
// State
levels,
sets,
loading,
error,
// Computed
hasLevels,
hasSets,
// Actions
loadLevels,
loadSets,
updateLevel,
reset
}
}
```
### Using the Feature Composable
```vue
<script setup>
import { onMounted } from 'vue'
import { useNcos } from 'src/composables/useNcos'
const {
levels,
sets,
loading,
error,
hasLevels,
loadLevels,
loadSets,
updateLevel
} = useNcos()
// Load data on mount
onMounted(async () => {
await loadLevels()
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)
}
}
</script>
<template>
<div>
<div v-if="loading">Loading...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<div v-else>
<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>
</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
}
}
```