cmms/frontend/src/stores/locations.js

389 lines
10 KiB
JavaScript

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useLocations } from '@/composables/locations/useLocations'
/**
* Locations Store
* Global state management for location data following CLAUDE.md guidelines
* Orchestrates between composables and provides cross-component data sharing
*/
export const useLocationsStore = defineStore('locations', () => {
// ==================== COMPOSABLE INTEGRATION ====================
const {
locations,
selectedLocation,
loading,
error,
searchCriteria,
miniLocations,
searchLocations,
loadMiniLocations,
refreshLocations,
updateSearchTerm,
changePage,
changePageSize,
changeSorting,
getLocationById,
createLocation,
updateLocation,
deleteLocation,
getChildLocations,
selectLocation,
clearSelectedLocation,
validateLocation,
formatLocationName,
getLocationDisplayName,
clearError
} = useLocations()
// ==================== ADDITIONAL GLOBAL STATE ====================
/** @type {import('vue').Ref<Array>} */
const recentLocations = ref([])
/** @type {import('vue').Ref<Object>} */
const locationFilters = ref({
type: '',
status: 'all', // 'all', 'active', 'inactive'
searchTerm: ''
})
/** @type {import('vue').Ref<boolean>} */
const showLocationModal = ref(false)
/** @type {import('vue').Ref<string>} */
const locationModalMode = ref('create') // 'create', 'edit', 'view'
/** @type {import('vue').Ref<Object>} */
const locationHierarchy = ref({})
// ==================== COMPUTED PROPERTIES ====================
const activeLocations = computed(() =>
locations.value.content.filter(location => location.status !== 'inactive')
)
const inactiveLocations = computed(() =>
locations.value.content.filter(location => location.status === 'inactive')
)
const locationsByType = computed(() => {
const grouped = {}
locations.value.content.forEach(location => {
const typeKey = location.type || 'General'
if (!grouped[typeKey]) {
grouped[typeKey] = []
}
grouped[typeKey].push(location)
})
return grouped
})
const filteredLocations = computed(() => {
let filtered = locations.value.content
// Filter by type
if (locationFilters.value.type) {
filtered = filtered.filter(location =>
location.type === locationFilters.value.type
)
}
// Filter by status
if (locationFilters.value.status === 'active') {
filtered = filtered.filter(location => location.status !== 'inactive')
} else if (locationFilters.value.status === 'inactive') {
filtered = filtered.filter(location => location.status === 'inactive')
}
// Filter by search term
if (locationFilters.value.searchTerm) {
const searchTerm = locationFilters.value.searchTerm.toLowerCase()
filtered = filtered.filter(location =>
location.name?.toLowerCase().includes(searchTerm) ||
location.description?.toLowerCase().includes(searchTerm) ||
location.address?.toLowerCase().includes(searchTerm)
)
}
return filtered
})
const locationStats = computed(() => ({
total: locations.value.totalElements,
active: activeLocations.value.length,
inactive: inactiveLocations.value.length,
byType: Object.keys(locationsByType.value).reduce((stats, type) => {
stats[type] = locationsByType.value[type].length
return stats
}, {})
}))
// ==================== ACTIONS ====================
/**
* Initialize locations data
* @returns {Promise<void>}
*/
const initializeLocations = async () => {
try {
await Promise.all([
searchLocations(),
loadMiniLocations()
])
} catch (err) {
console.error('Failed to initialize locations:', err)
}
}
/**
* Search locations with filters
* @param {Object} criteria - Search criteria
* @returns {Promise<Object>} Search results
*/
const searchWithFilters = async (criteria = {}) => {
const searchParams = {
...criteria,
searchValue: locationFilters.value.searchTerm
}
return await searchLocations(searchParams)
}
/**
* Update location filters and trigger search
* @param {Object} filters - Filter updates
* @returns {Promise<Object>} Search results
*/
const updateFilters = async (filters) => {
locationFilters.value = { ...locationFilters.value, ...filters }
return await searchWithFilters()
}
/**
* Create new location and refresh data
* @param {Object} locationData - Location data
* @returns {Promise<Object>} Created location
*/
const createLocationAndRefresh = async (locationData) => {
const response = await createLocation(locationData)
// Refresh locations list after successful creation
await refreshLocations()
return response
}
/**
* Update location and refresh data
* @param {string|number} locationId - Location ID
* @param {Object} locationData - Location data
* @returns {Promise<Object>} Updated location
*/
const updateLocationAndRefresh = async (locationId, locationData) => {
const updatedLocation = await updateLocation(locationId, locationData)
await addToRecentLocations(updatedLocation)
return updatedLocation
}
/**
* Delete location and refresh data
* @param {string|number} locationId - Location ID
* @returns {Promise<Object>} Delete response
*/
const deleteLocationAndRefresh = async (locationId) => {
const response = await deleteLocation(locationId)
await refreshLocations()
return response
}
/**
* Select location and open in modal
* @param {Object} location - Location to select
* @param {string} mode - Modal mode ('view', 'edit')
*/
const openLocationModal = (location = null, mode = 'create') => {
if (location) {
selectLocation(location)
locationModalMode.value = mode
} else {
clearSelectedLocation()
locationModalMode.value = 'create'
}
showLocationModal.value = true
}
/**
* Close location modal
*/
const closeLocationModal = () => {
showLocationModal.value = false
clearSelectedLocation()
}
/**
* Add location to recent locations list
* @param {Object} location - Location to add
*/
const addToRecentLocations = (location) => {
if (!location) return
// Remove if already exists
const existingIndex = recentLocations.value.findIndex(l => l.id === location.id)
if (existingIndex !== -1) {
recentLocations.value.splice(existingIndex, 1)
}
// Add to beginning
recentLocations.value.unshift(location)
// Keep only last 10
if (recentLocations.value.length > 10) {
recentLocations.value = recentLocations.value.slice(0, 10)
}
}
/**
* Get location by ID with caching
* @param {string|number} locationId - Location ID
* @returns {Promise<Object>} Location details
*/
const getLocation = async (locationId) => {
// Check if location is already in current list
const existingLocation = locations.value.content.find(l => l.id === locationId)
if (existingLocation) {
selectLocation(existingLocation)
return existingLocation
}
// Fetch from API
const location = await getLocationById(locationId)
await addToRecentLocations(location)
return location
}
/**
* Build location hierarchy for a parent location
* @param {string|number} parentLocationId - Parent location ID
* @returns {Promise<Array>} Child locations hierarchy
*/
const buildLocationHierarchy = async (parentLocationId) => {
const children = await getChildLocations(parentLocationId)
locationHierarchy.value[parentLocationId] = children
// Recursively build hierarchy for child locations
for (const child of children) {
if (child.hasChildren) {
await buildLocationHierarchy(child.id)
}
}
return children
}
/**
* Clear all filters
*/
const clearFilters = () => {
locationFilters.value = {
type: '',
status: 'all',
searchTerm: ''
}
}
/**
* Reset store state
*/
const resetStore = () => {
clearFilters()
clearSelectedLocation()
clearError()
showLocationModal.value = false
recentLocations.value = []
locationHierarchy.value = {}
}
// ==================== LEGACY SUPPORT ====================
/**
* Legacy loadLocations method for backward compatibility
* @deprecated Use initializeLocations instead
*/
const loadLocations = async () => {
return await initializeLocations()
}
/**
* Legacy locationCount for backward compatibility
* @deprecated Use locationStats.total instead
*/
const locationCount = computed(() => locations.value.totalElements || 0)
// ==================== RETURN STORE INTERFACE ====================
return {
// State from composable
locations,
selectedLocation,
loading,
error,
searchCriteria,
miniLocations,
// Additional global state
recentLocations: computed(() => recentLocations.value),
locationFilters: computed(() => locationFilters.value),
showLocationModal: computed(() => showLocationModal.value),
locationModalMode: computed(() => locationModalMode.value),
locationHierarchy: computed(() => locationHierarchy.value),
// Computed properties
activeLocations,
inactiveLocations,
locationsByType,
filteredLocations,
locationStats,
// Actions
initializeLocations,
searchWithFilters,
updateFilters,
createLocationAndRefresh,
updateLocationAndRefresh,
deleteLocationAndRefresh,
openLocationModal,
closeLocationModal,
addToRecentLocations,
getLocation,
buildLocationHierarchy,
clearFilters,
resetStore,
// Direct composable actions
searchLocations,
loadMiniLocations,
refreshLocations,
updateSearchTerm,
changePage,
changePageSize,
changeSorting,
getLocationById,
createLocation,
updateLocation,
deleteLocation,
getChildLocations,
selectLocation,
clearSelectedLocation,
validateLocation,
formatLocationName,
getLocationDisplayName,
clearError,
// Legacy support
loadLocations,
locationCount
}
})
// Also export the legacy singular name for backward compatibility
export const useLocationStore = useLocationsStore