Compare commits
9 Commits
936a73fde3
...
2f3d4c5260
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f3d4c5260 | |||
| 51edd7f655 | |||
| 2e4d61c3ea | |||
| 52f75c5301 | |||
| 84048bf3a2 | |||
| 0bfb69ad13 | |||
| ddce3ff3ae | |||
| b5af7f13b6 | |||
| e99f053233 |
@@ -10,6 +10,7 @@ export function useApi () {
|
|||||||
const apiCall = async (endpoint, options = {}) => {
|
const apiCall = async (endpoint, options = {}) => {
|
||||||
const url = `${API_BASE_URL}${endpoint}`
|
const url = `${API_BASE_URL}${endpoint}`
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
@@ -23,6 +24,10 @@ export function useApi () {
|
|||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
...options,
|
...options,
|
||||||
|
headers: {
|
||||||
|
...defaultOptions.headers,
|
||||||
|
...options.headers
|
||||||
|
},
|
||||||
signal: controller.signal
|
signal: controller.signal
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -32,7 +37,7 @@ export function useApi () {
|
|||||||
let data = null
|
let data = null
|
||||||
if (response.status !== 204) {
|
if (response.status !== 204) {
|
||||||
const contentType = response.headers.get('content-type') || ''
|
const contentType = response.headers.get('content-type') || ''
|
||||||
if (contentType.includes('application/json')) {
|
if (contentType.includes('application/json') || contentType.includes('application/ld+json') || contentType.includes('+json')) {
|
||||||
const text = await response.text()
|
const text = await response.text()
|
||||||
data = text ? JSON.parse(text) : null
|
data = text ? JSON.parse(text) : null
|
||||||
} else {
|
} else {
|
||||||
@@ -69,6 +74,9 @@ export function useApi () {
|
|||||||
const post = async (endpoint, data) => {
|
const post = async (endpoint, data) => {
|
||||||
return apiCall(endpoint, {
|
return apiCall(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/ld+json'
|
||||||
|
},
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -76,6 +84,9 @@ export function useApi () {
|
|||||||
const patch = async (endpoint, data) => {
|
const patch = async (endpoint, data) => {
|
||||||
return apiCall(endpoint, {
|
return apiCall(endpoint, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/merge-patch+json'
|
||||||
|
},
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,27 @@ import { useToast } from './useToast'
|
|||||||
import { useApi } from './useApi'
|
import { useApi } from './useApi'
|
||||||
import { buildConstructeurRequestPayload, uniqueConstructeurIds } from '~/shared/constructeurUtils'
|
import { buildConstructeurRequestPayload, uniqueConstructeurIds } from '~/shared/constructeurUtils'
|
||||||
import { useConstructeurs } from './useConstructeurs'
|
import { useConstructeurs } from './useConstructeurs'
|
||||||
|
import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations'
|
||||||
|
|
||||||
const composants = ref([])
|
const composants = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const extractCollection = (payload) => {
|
||||||
|
if (Array.isArray(payload)) {
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.member)) {
|
||||||
|
return payload.member
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.['hydra:member'])) {
|
||||||
|
return payload['hydra:member']
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.data)) {
|
||||||
|
return payload.data
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
export function useComposants () {
|
export function useComposants () {
|
||||||
const { showSuccess, showError, showInfo } = useToast()
|
const { showSuccess, showError, showInfo } = useToast()
|
||||||
const { get, post, patch, delete: del } = useApi()
|
const { get, post, patch, delete: del } = useApi()
|
||||||
@@ -16,15 +33,29 @@ export function useComposants () {
|
|||||||
if (!composant || typeof composant !== 'object') {
|
if (!composant || typeof composant !== 'object') {
|
||||||
return composant
|
return composant
|
||||||
}
|
}
|
||||||
|
if (!composant.typeComposantId) {
|
||||||
|
const typeComposantId = extractRelationId(composant.typeComposant)
|
||||||
|
if (typeComposantId) {
|
||||||
|
composant.typeComposantId = typeComposantId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!composant.productId) {
|
||||||
|
const productId = extractRelationId(composant.product)
|
||||||
|
if (productId) {
|
||||||
|
composant.productId = productId
|
||||||
|
}
|
||||||
|
}
|
||||||
const ids = uniqueConstructeurIds(
|
const ids = uniqueConstructeurIds(
|
||||||
composant.constructeurIds,
|
composant.constructeurIds,
|
||||||
composant.constructeurs,
|
composant.constructeurs,
|
||||||
composant.constructeur,
|
composant.constructeur,
|
||||||
)
|
)
|
||||||
const hasConstructeurs =
|
const hasResolvedConstructeurs =
|
||||||
Array.isArray(composant.constructeurs) && composant.constructeurs.length > 0
|
Array.isArray(composant.constructeurs)
|
||||||
|
&& composant.constructeurs.length > 0
|
||||||
|
&& composant.constructeurs.every((item) => item && typeof item === 'object')
|
||||||
|
|
||||||
if (ids.length && !hasConstructeurs) {
|
if (ids.length && !hasResolvedConstructeurs) {
|
||||||
const resolved = await ensureConstructeurs(ids)
|
const resolved = await ensureConstructeurs(ids)
|
||||||
if (resolved.length) {
|
if (resolved.length) {
|
||||||
composant.constructeurs = resolved
|
composant.constructeurs = resolved
|
||||||
@@ -34,27 +65,28 @@ export function useComposants () {
|
|||||||
return composant
|
return composant
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadComposants = async () => {
|
const loadComposants = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const result = await get('/composants')
|
const result = await get('/composants')
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const items = Array.isArray(result.data) ? result.data : []
|
const items = extractCollection(result.data)
|
||||||
const enrichedItems = await Promise.all(items.map((item) => withResolvedConstructeurs(item)))
|
const enrichedItems = await Promise.all(items.map((item) => withResolvedConstructeurs(item)))
|
||||||
composants.value = enrichedItems
|
composants.value = enrichedItems
|
||||||
showInfo(`Chargement de ${composants.value.length} composant(s) réussi`)
|
showInfo(`Chargement de ${composants.value.length} composant(s) réussi`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du chargement des composants:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('Erreur lors du chargement des composants:', error)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const createComposant = async (composantData) => {
|
const createComposant = async (composantData) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const result = await post('/composants', buildConstructeurRequestPayload(composantData))
|
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(composantData))
|
||||||
|
const result = await post('/composants', normalizedPayload)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const enriched = await withResolvedConstructeurs(result.data)
|
const enriched = await withResolvedConstructeurs(result.data)
|
||||||
composants.value.push(enriched)
|
composants.value.push(enriched)
|
||||||
@@ -76,7 +108,8 @@ const loadComposants = async () => {
|
|||||||
const updateComposantData = async (id, composantData) => {
|
const updateComposantData = async (id, composantData) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const result = await patch(`/composants/${id}`, buildConstructeurRequestPayload(composantData))
|
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(composantData))
|
||||||
|
const result = await patch(`/composants/${id}`, normalizedPayload)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const updated = await withResolvedConstructeurs(result.data)
|
const updated = await withResolvedConstructeurs(result.data)
|
||||||
const index = composants.value.findIndex(comp => comp.id === id)
|
const index = composants.value.findIndex(comp => comp.id === id)
|
||||||
|
|||||||
@@ -39,6 +39,22 @@ const upsertConstructeurs = (items = []) => {
|
|||||||
const getIndexedConstructeur = (id) =>
|
const getIndexedConstructeur = (id) =>
|
||||||
constructeurs.value.find((item) => item && item.id === id) || null
|
constructeurs.value.find((item) => item && item.id === id) || null
|
||||||
|
|
||||||
|
const extractCollection = (payload) => {
|
||||||
|
if (Array.isArray(payload)) {
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.member)) {
|
||||||
|
return payload.member
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.['hydra:member'])) {
|
||||||
|
return payload['hydra:member']
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.data)) {
|
||||||
|
return payload.data
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const pendingFetches = new Map()
|
const pendingFetches = new Map()
|
||||||
|
|
||||||
export function useConstructeurs () {
|
export function useConstructeurs () {
|
||||||
@@ -51,7 +67,7 @@ export function useConstructeurs () {
|
|||||||
const query = search ? `?search=${encodeURIComponent(search)}` : ''
|
const query = search ? `?search=${encodeURIComponent(search)}` : ''
|
||||||
const result = await get(`/constructeurs${query}`)
|
const result = await get(`/constructeurs${query}`)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const items = Array.isArray(result.data) ? result.data : []
|
const items = extractCollection(result.data)
|
||||||
constructeurs.value = uniqueConstructeurs(items)
|
constructeurs.value = uniqueConstructeurs(items)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,10 +1,27 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useApi } from './useApi'
|
import { useApi } from './useApi'
|
||||||
import { useToast } from './useToast'
|
import { useToast } from './useToast'
|
||||||
|
import { normalizeRelationIds } from '~/shared/apiRelations'
|
||||||
|
|
||||||
const documents = ref([])
|
const documents = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const extractCollection = (payload) => {
|
||||||
|
if (Array.isArray(payload)) {
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.member)) {
|
||||||
|
return payload.member
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.['hydra:member'])) {
|
||||||
|
return payload['hydra:member']
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.data)) {
|
||||||
|
return payload.data
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const fileToBase64 = file =>
|
const fileToBase64 = file =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
@@ -22,7 +39,7 @@ export function useDocuments () {
|
|||||||
try {
|
try {
|
||||||
const result = await get(endpoint)
|
const result = await get(endpoint)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const data = result.data || []
|
const data = extractCollection(result.data)
|
||||||
if (updateStore) {
|
if (updateStore) {
|
||||||
documents.value = data
|
documents.value = data
|
||||||
}
|
}
|
||||||
@@ -80,14 +97,14 @@ export function useDocuments () {
|
|||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const dataUrl = await fileToBase64(file)
|
const dataUrl = await fileToBase64(file)
|
||||||
|
|
||||||
const payload = {
|
const payload = normalizeRelationIds({
|
||||||
name: file.name,
|
name: file.name,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
mimeType: file.type || 'application/octet-stream',
|
mimeType: file.type || 'application/octet-stream',
|
||||||
size: file.size,
|
size: file.size,
|
||||||
path: dataUrl,
|
path: dataUrl,
|
||||||
...context
|
...context
|
||||||
}
|
})
|
||||||
|
|
||||||
const result = await post('/documents', payload)
|
const result = await post('/documents', payload)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|||||||
@@ -1,11 +1,30 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useToast } from './useToast'
|
import { useToast } from './useToast'
|
||||||
import { useApi } from './useApi'
|
import { useApi } from './useApi'
|
||||||
|
import { extractRelationId } from '~/shared/apiRelations'
|
||||||
|
|
||||||
const machineTypes = ref([])
|
const machineTypes = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
const normalizeRequirementList = (value) => (Array.isArray(value) ? value : [])
|
const normalizeRequirementList = (value, relationKey) => {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return value.map((entry) => {
|
||||||
|
if (!entry || typeof entry !== 'object') {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
const normalized = { ...entry }
|
||||||
|
if (relationKey && !normalized[relationKey]) {
|
||||||
|
const relationValue = normalized[relationKey.replace('Id', '')]
|
||||||
|
const relationId = extractRelationId(relationValue)
|
||||||
|
if (relationId) {
|
||||||
|
normalized[relationKey] = relationId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const normalizeMachineType = (type) => {
|
const normalizeMachineType = (type) => {
|
||||||
if (!type || typeof type !== 'object') {
|
if (!type || typeof type !== 'object') {
|
||||||
@@ -13,12 +32,28 @@ const normalizeMachineType = (type) => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...type,
|
...type,
|
||||||
componentRequirements: normalizeRequirementList(type.componentRequirements),
|
componentRequirements: normalizeRequirementList(type.componentRequirements, 'typeComposantId'),
|
||||||
pieceRequirements: normalizeRequirementList(type.pieceRequirements),
|
pieceRequirements: normalizeRequirementList(type.pieceRequirements, 'typePieceId'),
|
||||||
productRequirements: normalizeRequirementList(type.productRequirements),
|
productRequirements: normalizeRequirementList(type.productRequirements, 'typeProductId'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extractCollection = (payload) => {
|
||||||
|
if (Array.isArray(payload)) {
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.member)) {
|
||||||
|
return payload.member
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.['hydra:member'])) {
|
||||||
|
return payload['hydra:member']
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.data)) {
|
||||||
|
return payload.data
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
export function useMachineTypesApi () {
|
export function useMachineTypesApi () {
|
||||||
const { showSuccess, showError, showInfo } = useToast()
|
const { showSuccess, showError, showInfo } = useToast()
|
||||||
const { get, post, patch, delete: del } = useApi()
|
const { get, post, patch, delete: del } = useApi()
|
||||||
@@ -26,11 +61,10 @@ export function useMachineTypesApi () {
|
|||||||
const loadMachineTypes = async () => {
|
const loadMachineTypes = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const result = await get('/types/machines')
|
const result = await get('/type_machines')
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
machineTypes.value = Array.isArray(result.data)
|
const items = extractCollection(result.data)
|
||||||
? result.data.map(normalizeMachineType)
|
machineTypes.value = items.map(normalizeMachineType)
|
||||||
: []
|
|
||||||
showInfo(`Chargement de ${machineTypes.value.length} type(s) de machine réussi`)
|
showInfo(`Chargement de ${machineTypes.value.length} type(s) de machine réussi`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -43,7 +77,7 @@ export function useMachineTypesApi () {
|
|||||||
const createMachineType = async (typeData) => {
|
const createMachineType = async (typeData) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const result = await post('/types/machines', typeData)
|
const result = await post('/type_machines', typeData)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
machineTypes.value.push(normalizeMachineType(result.data))
|
machineTypes.value.push(normalizeMachineType(result.data))
|
||||||
showSuccess(`Type de machine "${typeData.name}" créé avec succès`)
|
showSuccess(`Type de machine "${typeData.name}" créé avec succès`)
|
||||||
@@ -60,7 +94,7 @@ export function useMachineTypesApi () {
|
|||||||
const updateMachineType = async (id, typeData) => {
|
const updateMachineType = async (id, typeData) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const result = await patch(`/types/machines/${id}`, typeData)
|
const result = await patch(`/type_machines/${id}`, typeData)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const normalized = normalizeMachineType(result.data)
|
const normalized = normalizeMachineType(result.data)
|
||||||
const index = machineTypes.value.findIndex(type => type.id === id)
|
const index = machineTypes.value.findIndex(type => type.id === id)
|
||||||
@@ -81,7 +115,7 @@ export function useMachineTypesApi () {
|
|||||||
const deleteMachineType = async (id) => {
|
const deleteMachineType = async (id) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const result = await del(`/types/machines/${id}`)
|
const result = await del(`/type_machines/${id}`)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const deletedType = machineTypes.value.find(type => type.id === id)
|
const deletedType = machineTypes.value.find(type => type.id === id)
|
||||||
machineTypes.value = machineTypes.value.filter(type => type.id !== id)
|
machineTypes.value = machineTypes.value.filter(type => type.id !== id)
|
||||||
@@ -105,7 +139,7 @@ export function useMachineTypesApi () {
|
|||||||
|
|
||||||
// Si pas trouvé localement, récupérer depuis l'API
|
// Si pas trouvé localement, récupérer depuis l'API
|
||||||
try {
|
try {
|
||||||
const result = await get(`/types/machines/${id}`)
|
const result = await get(`/type_machines/${id}`)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// Ajouter au cache local
|
// Ajouter au cache local
|
||||||
machineTypes.value.push(normalizeMachineType(result.data))
|
machineTypes.value.push(normalizeMachineType(result.data))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ref } from 'vue'
|
|||||||
import { useToast } from './useToast'
|
import { useToast } from './useToast'
|
||||||
import { useApi } from './useApi'
|
import { useApi } from './useApi'
|
||||||
import { buildConstructeurRequestPayload } from '~/shared/constructeurUtils'
|
import { buildConstructeurRequestPayload } from '~/shared/constructeurUtils'
|
||||||
|
import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations'
|
||||||
|
|
||||||
const machines = ref([])
|
const machines = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -32,6 +33,20 @@ const normalizeMachineResponse = (payload) => {
|
|||||||
|
|
||||||
const normalized = { ...container }
|
const normalized = { ...container }
|
||||||
|
|
||||||
|
if (!normalized.siteId) {
|
||||||
|
const siteId = extractRelationId(container.site)
|
||||||
|
if (siteId) {
|
||||||
|
normalized.siteId = siteId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!normalized.typeMachineId) {
|
||||||
|
const typeMachineId = extractRelationId(container.typeMachine)
|
||||||
|
if (typeMachineId) {
|
||||||
|
normalized.typeMachineId = typeMachineId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const componentLinks = resolveLinkCollection(payload, ['componentLinks', 'machineComponentLinks']) ??
|
const componentLinks = resolveLinkCollection(payload, ['componentLinks', 'machineComponentLinks']) ??
|
||||||
resolveLinkCollection(container, ['componentLinks', 'machineComponentLinks']) ??
|
resolveLinkCollection(container, ['componentLinks', 'machineComponentLinks']) ??
|
||||||
[]
|
[]
|
||||||
@@ -56,11 +71,15 @@ export function useMachines () {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
const machineList = Array.isArray(result.data)
|
const machineList = Array.isArray(result.data)
|
||||||
? result.data
|
? result.data
|
||||||
: Array.isArray(result.data?.machines)
|
: Array.isArray(result.data?.member)
|
||||||
? result.data.machines
|
? result.data.member
|
||||||
: Array.isArray(result.data?.data)
|
: Array.isArray(result.data?.['hydra:member'])
|
||||||
? result.data.data
|
? result.data['hydra:member']
|
||||||
: []
|
: Array.isArray(result.data?.machines)
|
||||||
|
? result.data.machines
|
||||||
|
: Array.isArray(result.data?.data)
|
||||||
|
? result.data.data
|
||||||
|
: []
|
||||||
const normalized = machineList
|
const normalized = machineList
|
||||||
.map((item) => normalizeMachineResponse(item))
|
.map((item) => normalizeMachineResponse(item))
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@@ -77,7 +96,8 @@ export function useMachines () {
|
|||||||
const createMachine = async (machineData) => {
|
const createMachine = async (machineData) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const result = await post('/machines', buildConstructeurRequestPayload(machineData))
|
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(machineData))
|
||||||
|
const result = await post('/machines', normalizedPayload)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const createdMachine = normalizeMachineResponse(result.data) ||
|
const createdMachine = normalizeMachineResponse(result.data) ||
|
||||||
normalizeMachineResponse(result.data?.machine) ||
|
normalizeMachineResponse(result.data?.machine) ||
|
||||||
@@ -106,13 +126,14 @@ export function useMachines () {
|
|||||||
// Les composants et pièces seront créés automatiquement
|
// Les composants et pièces seront créés automatiquement
|
||||||
}
|
}
|
||||||
|
|
||||||
return await createMachine(buildConstructeurRequestPayload(machineWithStructure))
|
return await createMachine(machineWithStructure)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateMachineData = async (id, machineData) => {
|
const updateMachineData = async (id, machineData) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const result = await patch(`/machines/${id}`, buildConstructeurRequestPayload(machineData))
|
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(machineData))
|
||||||
|
const result = await patch(`/machines/${id}`, normalizedPayload)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const updatedMachine = normalizeMachineResponse(result.data) ||
|
const updatedMachine = normalizeMachineResponse(result.data) ||
|
||||||
normalizeMachineResponse(result.data?.machine) ||
|
normalizeMachineResponse(result.data?.machine) ||
|
||||||
|
|||||||
53
app/composables/usePersistedSort.ts
Normal file
53
app/composables/usePersistedSort.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useCookie } from '#imports'
|
||||||
|
|
||||||
|
type SortCookie = {
|
||||||
|
field?: string
|
||||||
|
direction?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const readSortCookie = (value: unknown): SortCookie | null => {
|
||||||
|
if (!value) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
return value as SortCookie
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value) as SortCookie
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePersistedSort = <
|
||||||
|
TField extends string,
|
||||||
|
TDirection extends string,
|
||||||
|
>(
|
||||||
|
key: string,
|
||||||
|
defaults: { field: TField; direction: TDirection },
|
||||||
|
) => {
|
||||||
|
const cookie = useCookie<string | null>(`sort:${key}`, {
|
||||||
|
sameSite: 'lax',
|
||||||
|
})
|
||||||
|
const stored = readSortCookie(cookie.value)
|
||||||
|
const sortField = ref<TField>((stored?.field as TField) || defaults.field)
|
||||||
|
const sortDirection = ref<TDirection>(
|
||||||
|
(stored?.direction as TDirection) || defaults.direction,
|
||||||
|
)
|
||||||
|
|
||||||
|
watch([sortField, sortDirection], () => {
|
||||||
|
cookie.value = JSON.stringify({
|
||||||
|
field: sortField.value,
|
||||||
|
direction: sortDirection.value,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
sortField,
|
||||||
|
sortDirection,
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/composables/usePersistedValue.ts
Normal file
34
app/composables/usePersistedValue.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useCookie } from '#imports'
|
||||||
|
|
||||||
|
const parseValue = <T>(value: unknown, fallback: T): T => {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value) as T
|
||||||
|
} catch {
|
||||||
|
return value as unknown as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value as T
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePersistedValue = <T>(key: string, fallback: T) => {
|
||||||
|
const cookie = useCookie<string | null>(`pref:${key}`, {
|
||||||
|
sameSite: 'lax',
|
||||||
|
})
|
||||||
|
const initial = parseValue(cookie.value, fallback)
|
||||||
|
const state = ref<T>(initial)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
state,
|
||||||
|
(value) => {
|
||||||
|
cookie.value = JSON.stringify(value)
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
@@ -3,10 +3,27 @@ import { useToast } from './useToast'
|
|||||||
import { useApi } from './useApi'
|
import { useApi } from './useApi'
|
||||||
import { buildConstructeurRequestPayload, uniqueConstructeurIds } from '~/shared/constructeurUtils'
|
import { buildConstructeurRequestPayload, uniqueConstructeurIds } from '~/shared/constructeurUtils'
|
||||||
import { useConstructeurs } from './useConstructeurs'
|
import { useConstructeurs } from './useConstructeurs'
|
||||||
|
import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations'
|
||||||
|
|
||||||
const pieces = ref([])
|
const pieces = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const extractCollection = (payload) => {
|
||||||
|
if (Array.isArray(payload)) {
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.member)) {
|
||||||
|
return payload.member
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.['hydra:member'])) {
|
||||||
|
return payload['hydra:member']
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.data)) {
|
||||||
|
return payload.data
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
export function usePieces () {
|
export function usePieces () {
|
||||||
const { showSuccess, showError, showInfo } = useToast()
|
const { showSuccess, showError, showInfo } = useToast()
|
||||||
const { get, post, patch, delete: del } = useApi()
|
const { get, post, patch, delete: del } = useApi()
|
||||||
@@ -16,15 +33,29 @@ export function usePieces () {
|
|||||||
if (!piece || typeof piece !== 'object') {
|
if (!piece || typeof piece !== 'object') {
|
||||||
return piece
|
return piece
|
||||||
}
|
}
|
||||||
|
if (!piece.typePieceId) {
|
||||||
|
const typePieceId = extractRelationId(piece.typePiece)
|
||||||
|
if (typePieceId) {
|
||||||
|
piece.typePieceId = typePieceId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!piece.productId) {
|
||||||
|
const productId = extractRelationId(piece.product)
|
||||||
|
if (productId) {
|
||||||
|
piece.productId = productId
|
||||||
|
}
|
||||||
|
}
|
||||||
const ids = uniqueConstructeurIds(
|
const ids = uniqueConstructeurIds(
|
||||||
piece.constructeurIds,
|
piece.constructeurIds,
|
||||||
piece.constructeurs,
|
piece.constructeurs,
|
||||||
piece.constructeur,
|
piece.constructeur,
|
||||||
)
|
)
|
||||||
const hasConstructeurs =
|
const hasResolvedConstructeurs =
|
||||||
Array.isArray(piece.constructeurs) && piece.constructeurs.length > 0
|
Array.isArray(piece.constructeurs)
|
||||||
|
&& piece.constructeurs.length > 0
|
||||||
|
&& piece.constructeurs.every((item) => item && typeof item === 'object')
|
||||||
|
|
||||||
if (ids.length && !hasConstructeurs) {
|
if (ids.length && !hasResolvedConstructeurs) {
|
||||||
const resolved = await ensureConstructeurs(ids)
|
const resolved = await ensureConstructeurs(ids)
|
||||||
if (resolved.length) {
|
if (resolved.length) {
|
||||||
piece.constructeurs = resolved
|
piece.constructeurs = resolved
|
||||||
@@ -39,7 +70,7 @@ export function usePieces () {
|
|||||||
try {
|
try {
|
||||||
const result = await get('/pieces')
|
const result = await get('/pieces')
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const items = Array.isArray(result.data) ? result.data : []
|
const items = extractCollection(result.data)
|
||||||
const enrichedItems = await Promise.all(items.map((item) => withResolvedConstructeurs(item)))
|
const enrichedItems = await Promise.all(items.map((item) => withResolvedConstructeurs(item)))
|
||||||
pieces.value = enrichedItems
|
pieces.value = enrichedItems
|
||||||
showInfo(`Chargement de ${pieces.value.length} pièce(s) réussi`)
|
showInfo(`Chargement de ${pieces.value.length} pièce(s) réussi`)
|
||||||
@@ -54,7 +85,8 @@ export function usePieces () {
|
|||||||
const createPiece = async (pieceData) => {
|
const createPiece = async (pieceData) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const result = await post('/pieces', buildConstructeurRequestPayload(pieceData))
|
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(pieceData))
|
||||||
|
const result = await post('/pieces', normalizedPayload)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const enriched = await withResolvedConstructeurs(result.data)
|
const enriched = await withResolvedConstructeurs(result.data)
|
||||||
pieces.value.push(enriched)
|
pieces.value.push(enriched)
|
||||||
@@ -76,7 +108,8 @@ export function usePieces () {
|
|||||||
const updatePieceData = async (id, pieceData) => {
|
const updatePieceData = async (id, pieceData) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const result = await patch(`/pieces/${id}`, buildConstructeurRequestPayload(pieceData))
|
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(pieceData))
|
||||||
|
const result = await patch(`/pieces/${id}`, normalizedPayload)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const updated = await withResolvedConstructeurs(result.data)
|
const updated = await withResolvedConstructeurs(result.data)
|
||||||
const index = pieces.value.findIndex(piece => piece.id === id)
|
const index = pieces.value.findIndex(piece => piece.id === id)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useToast } from './useToast'
|
|||||||
import { useApi } from './useApi'
|
import { useApi } from './useApi'
|
||||||
import { buildConstructeurRequestPayload, uniqueConstructeurIds } from '~/shared/constructeurUtils'
|
import { buildConstructeurRequestPayload, uniqueConstructeurIds } from '~/shared/constructeurUtils'
|
||||||
import { useConstructeurs } from './useConstructeurs'
|
import { useConstructeurs } from './useConstructeurs'
|
||||||
|
import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations'
|
||||||
|
|
||||||
const products = ref([])
|
const products = ref([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
@@ -25,6 +26,22 @@ const replaceInCache = (item) => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extractCollection = (payload) => {
|
||||||
|
if (Array.isArray(payload)) {
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.member)) {
|
||||||
|
return payload.member
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.['hydra:member'])) {
|
||||||
|
return payload['hydra:member']
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.data)) {
|
||||||
|
return payload.data
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
export function useProducts () {
|
export function useProducts () {
|
||||||
const { showError } = useToast()
|
const { showError } = useToast()
|
||||||
const { get, post, patch, delete: del } = useApi()
|
const { get, post, patch, delete: del } = useApi()
|
||||||
@@ -34,15 +51,23 @@ export function useProducts () {
|
|||||||
if (!product || typeof product !== 'object') {
|
if (!product || typeof product !== 'object') {
|
||||||
return product
|
return product
|
||||||
}
|
}
|
||||||
|
if (!product.typeProductId) {
|
||||||
|
const typeProductId = extractRelationId(product.typeProduct)
|
||||||
|
if (typeProductId) {
|
||||||
|
product.typeProductId = typeProductId
|
||||||
|
}
|
||||||
|
}
|
||||||
const ids = uniqueConstructeurIds(
|
const ids = uniqueConstructeurIds(
|
||||||
product.constructeurIds,
|
product.constructeurIds,
|
||||||
product.constructeurs,
|
product.constructeurs,
|
||||||
product.constructeur,
|
product.constructeur,
|
||||||
)
|
)
|
||||||
const hasConstructeurs =
|
const hasResolvedConstructeurs =
|
||||||
Array.isArray(product.constructeurs) && product.constructeurs.length > 0
|
Array.isArray(product.constructeurs)
|
||||||
|
&& product.constructeurs.length > 0
|
||||||
|
&& product.constructeurs.every((item) => item && typeof item === 'object')
|
||||||
|
|
||||||
if (ids.length && !hasConstructeurs) {
|
if (ids.length && !hasResolvedConstructeurs) {
|
||||||
const resolved = await ensureConstructeurs(ids)
|
const resolved = await ensureConstructeurs(ids)
|
||||||
if (resolved.length) {
|
if (resolved.length) {
|
||||||
product.constructeurs = resolved
|
product.constructeurs = resolved
|
||||||
@@ -69,12 +94,14 @@ export function useProducts () {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
const result = await get('/products?limit=100')
|
const result = await get('/products?itemsPerPage=100')
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const items = Array.isArray(result.data?.items) ? result.data.items : []
|
const items = extractCollection(result.data)
|
||||||
const enrichedItems = await Promise.all(items.map((item) => withResolvedConstructeurs(item)))
|
const enrichedItems = await Promise.all(items.map((item) => withResolvedConstructeurs(item)))
|
||||||
products.value = enrichedItems
|
products.value = enrichedItems
|
||||||
total.value = typeof result.data?.total === 'number' ? result.data.total : items.length
|
total.value = typeof result.data?.totalItems === 'number'
|
||||||
|
? result.data.totalItems
|
||||||
|
: items.length
|
||||||
loaded.value = true
|
loaded.value = true
|
||||||
} else if (result.error) {
|
} else if (result.error) {
|
||||||
error.value = result.error
|
error.value = result.error
|
||||||
@@ -93,7 +120,7 @@ export function useProducts () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createProduct = async (payload) => {
|
const createProduct = async (payload) => {
|
||||||
const normalizedPayload = buildConstructeurRequestPayload(payload)
|
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(payload))
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
@@ -121,7 +148,7 @@ export function useProducts () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateProduct = async (id, payload) => {
|
const updateProduct = async (id, payload) => {
|
||||||
const normalizedPayload = buildConstructeurRequestPayload(payload)
|
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(payload))
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { useState, useRequestHeaders, useRuntimeConfig } from '#imports'
|
|||||||
|
|
||||||
const buildUrl = (path) => {
|
const buildUrl = (path) => {
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const base = config.public.apiBaseUrl?.replace(/\/$/, '') || ''
|
const baseUrl = process.server
|
||||||
|
? (config.apiBaseUrl || config.public.apiBaseUrl || '')
|
||||||
|
: (config.public.apiBaseUrl || '')
|
||||||
|
const base = baseUrl.replace(/\/$/, '')
|
||||||
return `${base}${path}`
|
return `${base}${path}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function useProfiles () {
|
|||||||
const fetchProfiles = async () => {
|
const fetchProfiles = async () => {
|
||||||
loadingProfiles.value = true
|
loadingProfiles.value = true
|
||||||
try {
|
try {
|
||||||
profiles.value = await $fetch(buildUrl('/profiles'), {
|
profiles.value = await $fetch(buildUrl('/session/profiles'), {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: getSessionHeaders()
|
headers: getSessionHeaders()
|
||||||
@@ -37,7 +37,7 @@ export function useProfiles () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createProfile = async ({ firstName, lastName }) => {
|
const createProfile = async ({ firstName, lastName }) => {
|
||||||
const profile = await $fetch(buildUrl('/profiles'), {
|
const profile = await $fetch(buildUrl('/session/profiles'), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
body: { firstName, lastName },
|
body: { firstName, lastName },
|
||||||
@@ -48,7 +48,7 @@ export function useProfiles () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteProfile = async (profileId) => {
|
const deleteProfile = async (profileId) => {
|
||||||
await $fetch(buildUrl(`/profiles/${profileId}`), {
|
await $fetch(buildUrl(`/session/profiles/${profileId}`), {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: getSessionHeaders()
|
headers: getSessionHeaders()
|
||||||
|
|||||||
@@ -13,9 +13,20 @@ export function useSites () {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const result = await get('/sites')
|
const result = await get('/sites')
|
||||||
|
console.log('sites api result', result)
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
sites.value = result.data
|
const collection = Array.isArray(result.data)
|
||||||
showInfo(`Chargement de ${sites.value.length} site(s) réussi`)
|
? result.data
|
||||||
|
: Array.isArray(result.data?.member)
|
||||||
|
? result.data.member
|
||||||
|
: Array.isArray(result.data?.['hydra:member'])
|
||||||
|
? result.data['hydra:member']
|
||||||
|
: Array.isArray(result.data?.data)
|
||||||
|
? result.data.data
|
||||||
|
: []
|
||||||
|
sites.value = collection
|
||||||
|
showInfo(`Chargement de ${collection.length} site(s) réussi`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur lors du chargement des sites:', error)
|
console.error('Erreur lors du chargement des sites:', error)
|
||||||
|
|||||||
@@ -140,19 +140,34 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useComposants } from '~/composables/useComposants'
|
import { useComposants } from '~/composables/useComposants'
|
||||||
|
import { useComponentTypes } from '~/composables/useComponentTypes'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
|
import { usePersistedSort } from '~/composables/usePersistedSort'
|
||||||
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
||||||
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
||||||
|
|
||||||
const { showError } = useToast()
|
const { showError } = useToast()
|
||||||
const { composants, loadComposants, loading: loadingComposantsRef, deleteComposant } = useComposants()
|
const { composants, loadComposants, loading: loadingComposantsRef, deleteComposant } = useComposants()
|
||||||
|
const { componentTypes, loadComponentTypes } = useComponentTypes()
|
||||||
const loadingComposants = computed(() => loadingComposantsRef.value)
|
const loadingComposants = computed(() => loadingComposantsRef.value)
|
||||||
const composantsList = computed(() => composants.value || [])
|
|
||||||
|
// Enrichir les composants avec les types de composants complets
|
||||||
|
const composantsList = computed(() => {
|
||||||
|
return (composants.value || []).map((composant) => {
|
||||||
|
const typeComposant = componentTypes.value.find(t => t.id === composant.typeComposantId)
|
||||||
|
return {
|
||||||
|
...composant,
|
||||||
|
typeComposant: typeComposant || composant.typeComposant || null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
const composantsTotal = computed(() => composantsList.value.length)
|
const composantsTotal = computed(() => composantsList.value.length)
|
||||||
|
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const sortField = ref<'name' | 'createdAt'>('name')
|
const { sortField, sortDirection } = usePersistedSort<'name' | 'createdAt', 'asc' | 'desc'>(
|
||||||
const sortDirection = ref<'asc' | 'desc'>('asc')
|
'component-catalog',
|
||||||
|
{ field: 'name', direction: 'asc' },
|
||||||
|
)
|
||||||
|
|
||||||
const resolvePrimaryDocument = (component: Record<string, any>) => {
|
const resolvePrimaryDocument = (component: Record<string, any>) => {
|
||||||
const documents = Array.isArray(component?.documents) ? component.documents : []
|
const documents = Array.isArray(component?.documents) ? component.documents : []
|
||||||
@@ -298,6 +313,9 @@ const handleDeleteComponent = async (component: Record<string, any>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadComposants()
|
await Promise.all([
|
||||||
|
loadComposants(),
|
||||||
|
loadComponentTypes()
|
||||||
|
])
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -403,6 +403,7 @@ import { useComposants } from '~/composables/useComposants'
|
|||||||
import { useCustomFields } from '~/composables/useCustomFields'
|
import { useCustomFields } from '~/composables/useCustomFields'
|
||||||
import { useApi } from '~/composables/useApi'
|
import { useApi } from '~/composables/useApi'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
|
import { extractRelationId } from '~/shared/apiRelations'
|
||||||
import { useDocuments } from '~/composables/useDocuments'
|
import { useDocuments } from '~/composables/useDocuments'
|
||||||
import { useConstructeurs } from '~/composables/useConstructeurs'
|
import { useConstructeurs } from '~/composables/useConstructeurs'
|
||||||
import { formatStructurePreview, normalizeStructureForEditor } from '~/shared/modelUtils'
|
import { formatStructurePreview, normalizeStructureForEditor } from '~/shared/modelUtils'
|
||||||
@@ -435,7 +436,7 @@ const { get } = useApi()
|
|||||||
const { componentTypes, loadComponentTypes } = useComponentTypes()
|
const { componentTypes, loadComponentTypes } = useComponentTypes()
|
||||||
const { updateComposant } = useComposants()
|
const { updateComposant } = useComposants()
|
||||||
const { ensureConstructeurs } = useConstructeurs()
|
const { ensureConstructeurs } = useConstructeurs()
|
||||||
const { upsertCustomFieldValue, updateCustomFieldValue } = useCustomFields()
|
const { upsertCustomFieldValue, updateCustomFieldValue, getCustomFieldValuesByEntity } = useCustomFields()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const { loadDocumentsByComponent, uploadDocuments, deleteDocument } = useDocuments()
|
const { loadDocumentsByComponent, uploadDocuments, deleteDocument } = useDocuments()
|
||||||
|
|
||||||
@@ -636,6 +637,11 @@ const fetchComponent = async () => {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
component.value = result.data
|
component.value = result.data
|
||||||
componentDocuments.value = Array.isArray(result.data?.documents) ? result.data.documents : []
|
componentDocuments.value = Array.isArray(result.data?.documents) ? result.data.documents : []
|
||||||
|
|
||||||
|
const customValues = await getCustomFieldValuesByEntity('composant', result.data.id)
|
||||||
|
if (customValues.success && Array.isArray(customValues.data)) {
|
||||||
|
component.value.customFieldValues = customValues.data
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
component.value = null
|
component.value = null
|
||||||
componentDocuments.value = []
|
componentDocuments.value = []
|
||||||
@@ -651,7 +657,13 @@ watch(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedTypeId.value = currentComponent.typeComposantId || ''
|
const resolvedTypeId = currentComponent.typeComposantId
|
||||||
|
|| extractRelationId(currentComponent.typeComposant)
|
||||||
|
|| ''
|
||||||
|
if (resolvedTypeId && !currentComponent.typeComposantId) {
|
||||||
|
currentComponent.typeComposantId = resolvedTypeId
|
||||||
|
}
|
||||||
|
selectedTypeId.value = resolvedTypeId
|
||||||
|
|
||||||
editionForm.name = currentComponent.name || ''
|
editionForm.name = currentComponent.name || ''
|
||||||
editionForm.reference = currentComponent.reference || ''
|
editionForm.reference = currentComponent.reference || ''
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ import FieldEmail from '~/components/form/FieldEmail.vue'
|
|||||||
import FieldPhone from '~/components/form/FieldPhone.vue'
|
import FieldPhone from '~/components/form/FieldPhone.vue'
|
||||||
import { useConstructeurs } from '~/composables/useConstructeurs'
|
import { useConstructeurs } from '~/composables/useConstructeurs'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
|
import { usePersistedValue } from '~/composables/usePersistedValue'
|
||||||
import { formatPhone } from '~/utils/formatters/phone'
|
import { formatPhone } from '~/utils/formatters/phone'
|
||||||
import IconLucidePlus from '~icons/lucide/plus'
|
import IconLucidePlus from '~icons/lucide/plus'
|
||||||
|
|
||||||
@@ -129,7 +130,7 @@ const { constructeurs, loading, searchConstructeurs, createConstructeur, updateC
|
|||||||
const { showError, showSuccess } = useToast()
|
const { showError, showSuccess } = useToast()
|
||||||
|
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const sortKey = ref('name')
|
const sortKey = usePersistedValue('constructeurs-sort', 'name')
|
||||||
const modalOpen = ref(false)
|
const modalOpen = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const editingConstructeur = ref(null)
|
const editingConstructeur = ref(null)
|
||||||
|
|||||||
@@ -3921,7 +3921,7 @@ const applyMachineLinks = (source) => {
|
|||||||
const loadMachineData = async () => {
|
const loadMachineData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const machineResult = await get(`/machines/${machineId}`)
|
const machineResult = await get(`/machines/${machineId}/skeleton`)
|
||||||
|
|
||||||
if (!machineResult.success) {
|
if (!machineResult.success) {
|
||||||
console.error('Machine non trouvée:', machineId, machineResult.error)
|
console.error('Machine non trouvée:', machineId, machineResult.error)
|
||||||
|
|||||||
@@ -163,8 +163,21 @@ const categories = computed(() => {
|
|||||||
return Array.from(cats)
|
return Array.from(cats)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Enrichir les machines avec les objets site et typeMachine complets
|
||||||
|
const enrichedMachines = computed(() => {
|
||||||
|
return machines.value.map((machine) => {
|
||||||
|
const site = sites.value.find(s => s.id === machine.siteId)
|
||||||
|
const typeMachine = machineTypes.value.find(t => t.id === machine.typeMachineId)
|
||||||
|
return {
|
||||||
|
...machine,
|
||||||
|
site: site || null,
|
||||||
|
typeMachine: typeMachine || null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const filteredMachines = computed(() => {
|
const filteredMachines = computed(() => {
|
||||||
let filtered = machines.value
|
let filtered = enrichedMachines.value
|
||||||
|
|
||||||
if (selectedSite.value) {
|
if (selectedSite.value) {
|
||||||
filtered = filtered.filter(machine => machine.siteId === selectedSite.value)
|
filtered = filtered.filter(machine => machine.siteId === selectedSite.value)
|
||||||
|
|||||||
@@ -162,19 +162,34 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { usePieces } from '~/composables/usePieces'
|
import { usePieces } from '~/composables/usePieces'
|
||||||
|
import { usePieceTypes } from '~/composables/usePieceTypes'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
|
import { usePersistedSort } from '~/composables/usePersistedSort'
|
||||||
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
||||||
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
||||||
|
|
||||||
const { showError } = useToast()
|
const { showError } = useToast()
|
||||||
const { pieces, loadPieces, loading: loadingPiecesRef, deletePiece } = usePieces()
|
const { pieces, loadPieces, loading: loadingPiecesRef, deletePiece } = usePieces()
|
||||||
|
const { pieceTypes, loadPieceTypes } = usePieceTypes()
|
||||||
const loadingPieces = computed(() => loadingPiecesRef.value)
|
const loadingPieces = computed(() => loadingPiecesRef.value)
|
||||||
const piecesList = computed(() => pieces.value || [])
|
|
||||||
|
// Enrichir les pièces avec les types de pièces complets
|
||||||
|
const piecesList = computed(() => {
|
||||||
|
return (pieces.value || []).map((piece) => {
|
||||||
|
const typePiece = pieceTypes.value.find(t => t.id === piece.typePieceId)
|
||||||
|
return {
|
||||||
|
...piece,
|
||||||
|
typePiece: typePiece || piece.typePiece || null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
const piecesTotal = computed(() => piecesList.value.length)
|
const piecesTotal = computed(() => piecesList.value.length)
|
||||||
|
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const sortField = ref<'name' | 'createdAt'>('name')
|
const { sortField, sortDirection } = usePersistedSort<'name' | 'createdAt', 'asc' | 'desc'>(
|
||||||
const sortDirection = ref<'asc' | 'desc'>('asc')
|
'pieces-catalog',
|
||||||
|
{ field: 'name', direction: 'asc' },
|
||||||
|
)
|
||||||
|
|
||||||
const resolvePrimaryDocument = (piece: Record<string, any>) => {
|
const resolvePrimaryDocument = (piece: Record<string, any>) => {
|
||||||
const documents = Array.isArray(piece?.documents) ? piece.documents : []
|
const documents = Array.isArray(piece?.documents) ? piece.documents : []
|
||||||
@@ -413,6 +428,9 @@ const handleDeletePiece = async (piece: Record<string, any>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadPieces()
|
await Promise.all([
|
||||||
|
loadPieces(),
|
||||||
|
loadPieceTypes()
|
||||||
|
])
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -164,7 +164,9 @@
|
|||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useHead } from '#imports'
|
import { useHead } from '#imports'
|
||||||
import { useProducts } from '~/composables/useProducts'
|
import { useProducts } from '~/composables/useProducts'
|
||||||
|
import { useProductTypes } from '~/composables/useProductTypes'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
|
import { usePersistedSort } from '~/composables/usePersistedSort'
|
||||||
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
||||||
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
||||||
|
|
||||||
@@ -181,13 +183,25 @@ const {
|
|||||||
loadProducts,
|
loadProducts,
|
||||||
deleteProduct,
|
deleteProduct,
|
||||||
} = useProducts()
|
} = useProducts()
|
||||||
|
const { productTypes, loadProductTypes } = useProductTypes()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const sortField = ref<'name' | 'createdAt'>('name')
|
const { sortField, sortDirection } = usePersistedSort<'name' | 'createdAt', 'asc' | 'desc'>(
|
||||||
const sortDirection = ref<'asc' | 'desc'>('asc')
|
'product-catalog',
|
||||||
|
{ field: 'name', direction: 'asc' },
|
||||||
|
)
|
||||||
|
|
||||||
const normalizedProducts = computed(() => (Array.isArray(products.value) ? products.value : []))
|
// Enrichir les produits avec les types de produits complets
|
||||||
|
const normalizedProducts = computed(() => {
|
||||||
|
return (Array.isArray(products.value) ? products.value : []).map((product) => {
|
||||||
|
const typeProduct = productTypes.value.find(t => t.id === product.typeProductId)
|
||||||
|
return {
|
||||||
|
...product,
|
||||||
|
typeProduct: typeProduct || product.typeProduct || null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
const hasLoaded = computed(() => loaded.value)
|
const hasLoaded = computed(() => loaded.value)
|
||||||
const errorMessage = computed(() => (typeof error.value === 'string' && error.value.length ? error.value : null))
|
const errorMessage = computed(() => (typeof error.value === 'string' && error.value.length ? error.value : null))
|
||||||
|
|
||||||
@@ -383,6 +397,9 @@ const confirmDelete = async (product: Record<string, any>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadProducts()
|
await Promise.all([
|
||||||
|
loadProducts(),
|
||||||
|
loadProductTypes()
|
||||||
|
])
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -187,6 +187,14 @@ onMounted(async () => {
|
|||||||
const typeId = route.params.id
|
const typeId = route.params.id
|
||||||
console.log('=== TYPE DETAIL PAGE LOADING ===')
|
console.log('=== TYPE DETAIL PAGE LOADING ===')
|
||||||
console.log('Loading type with ID:', typeId)
|
console.log('Loading type with ID:', typeId)
|
||||||
|
console.log('Full route params:', route.params)
|
||||||
|
|
||||||
|
if (!typeId) {
|
||||||
|
console.error('No type ID provided in route')
|
||||||
|
showError('Aucun identifiant de type fourni')
|
||||||
|
loading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const result = await getMachineTypeById(typeId)
|
const result = await getMachineTypeById(typeId)
|
||||||
console.log('API Result:', result)
|
console.log('API Result:', result)
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ export interface ModelType extends BaseModelTypePayload {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
category: ModelCategory;
|
category: ModelCategory;
|
||||||
structure: ModelTypeStructure;
|
structure: ModelTypeStructure;
|
||||||
|
componentSkeleton?: ComponentModelStructure | null;
|
||||||
|
pieceSkeleton?: PieceModelStructure | null;
|
||||||
|
productSkeleton?: ProductModelStructure | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelTypeListParams {
|
export interface ModelTypeListParams {
|
||||||
@@ -65,7 +68,7 @@ export interface ModelTypeListResponse {
|
|||||||
limit: number;
|
limit: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ENDPOINT = '/api/model-types';
|
const ENDPOINT = '/model_types';
|
||||||
|
|
||||||
function resolveBaseUrl() {
|
function resolveBaseUrl() {
|
||||||
const runtimeConfig = useRuntimeConfig();
|
const runtimeConfig = useRuntimeConfig();
|
||||||
@@ -80,7 +83,47 @@ function createOptions<T>(options: FetchOptions<T> = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listModelTypes(params: ModelTypeListParams = {}, opts: { signal?: AbortSignal } = {}) {
|
const normalizeModelType = (item: any): ModelType => {
|
||||||
|
if (!item || typeof item !== 'object') {
|
||||||
|
return item as ModelType;
|
||||||
|
}
|
||||||
|
if (!item.structure) {
|
||||||
|
if (item.category === 'COMPONENT' && item.componentSkeleton) {
|
||||||
|
item.structure = item.componentSkeleton;
|
||||||
|
} else if (item.category === 'PIECE' && item.pieceSkeleton) {
|
||||||
|
item.structure = item.pieceSkeleton;
|
||||||
|
} else if (item.category === 'PRODUCT' && item.productSkeleton) {
|
||||||
|
item.structure = item.productSkeleton;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item as ModelType;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStructureToSkeleton = <T extends Record<string, any>>(payload: T): T => {
|
||||||
|
if (!payload || typeof payload !== 'object') {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
if (!('structure' in payload)) {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
const structure = (payload as any).structure;
|
||||||
|
if (!structure) {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
const category = (payload as any).category;
|
||||||
|
const next = { ...payload } as Record<string, any>;
|
||||||
|
if (category === 'COMPONENT') {
|
||||||
|
next.componentSkeleton = structure;
|
||||||
|
} else if (category === 'PIECE') {
|
||||||
|
next.pieceSkeleton = structure;
|
||||||
|
} else if (category === 'PRODUCT') {
|
||||||
|
next.productSkeleton = structure;
|
||||||
|
}
|
||||||
|
delete next.structure;
|
||||||
|
return next as T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function listModelTypes(params: ModelTypeListParams = {}, opts: { signal?: AbortSignal } = {}) {
|
||||||
const requestFetch = useRequestFetch();
|
const requestFetch = useRequestFetch();
|
||||||
const query: Record<string, string | number> = {};
|
const query: Record<string, string | number> = {};
|
||||||
|
|
||||||
@@ -96,36 +139,84 @@ export function listModelTypes(params: ModelTypeListParams = {}, opts: { signal?
|
|||||||
if (params.dir) {
|
if (params.dir) {
|
||||||
query.dir = params.dir;
|
query.dir = params.dir;
|
||||||
}
|
}
|
||||||
if (typeof params.limit === 'number') {
|
const hasCategoryFilter = Boolean(params.category);
|
||||||
query.limit = params.limit;
|
const effectiveLimit = typeof params.limit === 'number' ? params.limit : undefined;
|
||||||
}
|
const effectiveOffset = typeof params.offset === 'number' ? params.offset : 0;
|
||||||
if (typeof params.offset === 'number') {
|
|
||||||
query.offset = params.offset;
|
if (hasCategoryFilter) {
|
||||||
|
// Fetch enough items to allow client-side category filtering + pagination.
|
||||||
|
query.itemsPerPage = Math.max(effectiveLimit ?? 200, 200);
|
||||||
|
query.offset = 0;
|
||||||
|
} else {
|
||||||
|
if (typeof params.limit === 'number') {
|
||||||
|
query.itemsPerPage = params.limit;
|
||||||
|
}
|
||||||
|
if (typeof params.offset === 'number') {
|
||||||
|
query.offset = params.offset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return requestFetch<ModelTypeListResponse>(ENDPOINT, createOptions({
|
const payload = await requestFetch<Record<string, any>>(ENDPOINT, createOptions({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
query,
|
query,
|
||||||
signal: opts.signal,
|
signal: opts.signal,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const rawItems = Array.isArray(payload?.member)
|
||||||
|
? payload.member
|
||||||
|
: Array.isArray(payload?.['hydra:member'])
|
||||||
|
? payload['hydra:member']
|
||||||
|
: Array.isArray(payload?.items)
|
||||||
|
? payload.items
|
||||||
|
: [];
|
||||||
|
const filteredItems = params.category
|
||||||
|
? rawItems.filter((item: any) => item?.category === params.category)
|
||||||
|
: rawItems;
|
||||||
|
const total = params.category
|
||||||
|
? filteredItems.length
|
||||||
|
: typeof payload?.totalItems === 'number'
|
||||||
|
? payload.totalItems
|
||||||
|
: Array.isArray(payload?.items)
|
||||||
|
? payload.items.length
|
||||||
|
: rawItems.length;
|
||||||
|
const items = (params.category && typeof effectiveLimit === 'number'
|
||||||
|
? filteredItems.slice(effectiveOffset, effectiveOffset + effectiveLimit)
|
||||||
|
: filteredItems).map(normalizeModelType);
|
||||||
|
|
||||||
|
return {
|
||||||
|
items,
|
||||||
|
total,
|
||||||
|
offset: effectiveOffset,
|
||||||
|
limit: typeof effectiveLimit === 'number' ? effectiveLimit : items.length,
|
||||||
|
} satisfies ModelTypeListResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createModelType(payload: ModelTypePayload, opts: { signal?: AbortSignal } = {}) {
|
export function createModelType(payload: ModelTypePayload, opts: { signal?: AbortSignal } = {}) {
|
||||||
const requestFetch = useRequestFetch();
|
const requestFetch = useRequestFetch();
|
||||||
|
const mappedPayload = mapStructureToSkeleton(payload);
|
||||||
return requestFetch<ModelType>(ENDPOINT, createOptions({
|
return requestFetch<ModelType>(ENDPOINT, createOptions({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: payload,
|
headers: {
|
||||||
|
'Content-Type': 'application/ld+json',
|
||||||
|
Accept: 'application/ld+json',
|
||||||
|
},
|
||||||
|
body: mappedPayload,
|
||||||
signal: opts.signal,
|
signal: opts.signal,
|
||||||
}));
|
})).then(normalizeModelType);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateModelType(id: string, payload: Partial<ModelTypePayload>, opts: { signal?: AbortSignal } = {}) {
|
export function updateModelType(id: string, payload: Partial<ModelTypePayload>, opts: { signal?: AbortSignal } = {}) {
|
||||||
const requestFetch = useRequestFetch();
|
const requestFetch = useRequestFetch();
|
||||||
|
const mappedPayload = mapStructureToSkeleton(payload);
|
||||||
return requestFetch<ModelType>(`${ENDPOINT}/${id}`, createOptions({
|
return requestFetch<ModelType>(`${ENDPOINT}/${id}`, createOptions({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: payload,
|
headers: {
|
||||||
|
'Content-Type': 'application/merge-patch+json',
|
||||||
|
Accept: 'application/ld+json',
|
||||||
|
},
|
||||||
|
body: mappedPayload,
|
||||||
signal: opts.signal,
|
signal: opts.signal,
|
||||||
}));
|
})).then(normalizeModelType);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteModelType(id: string, opts: { signal?: AbortSignal } = {}) {
|
export function deleteModelType(id: string, opts: { signal?: AbortSignal } = {}) {
|
||||||
@@ -141,5 +232,5 @@ export function getModelType(id: string, opts: { signal?: AbortSignal } = {}) {
|
|||||||
return requestFetch<ModelType>(`${ENDPOINT}/${id}`, createOptions({
|
return requestFetch<ModelType>(`${ENDPOINT}/${id}`, createOptions({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
signal: opts.signal,
|
signal: opts.signal,
|
||||||
}));
|
})).then(normalizeModelType);
|
||||||
}
|
}
|
||||||
|
|||||||
57
app/shared/apiRelations.ts
Normal file
57
app/shared/apiRelations.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
export const RELATION_ID_MAP: Record<string, { key: string; path: string }> = {
|
||||||
|
siteId: { key: 'site', path: 'sites' },
|
||||||
|
machineId: { key: 'machine', path: 'machines' },
|
||||||
|
composantId: { key: 'composant', path: 'composants' },
|
||||||
|
pieceId: { key: 'piece', path: 'pieces' },
|
||||||
|
productId: { key: 'product', path: 'products' },
|
||||||
|
typeMachineId: { key: 'typeMachine', path: 'type_machines' },
|
||||||
|
typeComposantId: { key: 'typeComposant', path: 'model_types' },
|
||||||
|
typePieceId: { key: 'typePiece', path: 'model_types' },
|
||||||
|
typeProductId: { key: 'typeProduct', path: 'model_types' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toIri = (path: string, id: string): string => `/api/${path}/${id}`;
|
||||||
|
|
||||||
|
export const extractRelationId = (value: unknown): string | null => {
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (trimmed.includes('/')) {
|
||||||
|
const parts = trimmed.split('/').filter(Boolean);
|
||||||
|
return parts.length ? parts[parts.length - 1] : null;
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
if (typeof value === 'object' && 'id' in (value as Record<string, any>)) {
|
||||||
|
const id = (value as Record<string, any>).id;
|
||||||
|
return typeof id === 'string' ? id : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const normalizeRelationIds = <T extends Record<string, any>>(payload: T): T => {
|
||||||
|
if (!payload || typeof payload !== 'object') {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
const next: Record<string, any> = { ...payload };
|
||||||
|
Object.entries(RELATION_ID_MAP).forEach(([sourceKey, config]) => {
|
||||||
|
const raw = next[sourceKey];
|
||||||
|
if (typeof raw !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const trimmed = raw.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next[config.key] = toIri(config.path, trimmed);
|
||||||
|
delete next[sourceKey];
|
||||||
|
});
|
||||||
|
|
||||||
|
return next as T;
|
||||||
|
};
|
||||||
@@ -15,7 +15,14 @@ const toStringId = (value: unknown): string | null => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
return trimmed.length > 0 ? trimmed : null;
|
if (!trimmed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (trimmed.includes('/')) {
|
||||||
|
const parts = trimmed.split('/').filter(Boolean);
|
||||||
|
return parts.length ? parts[parts.length - 1] : null;
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uniqueConstructeurIds = (...sources: unknown[]): string[] => {
|
export const uniqueConstructeurIds = (...sources: unknown[]): string[] => {
|
||||||
@@ -53,7 +60,9 @@ export const uniqueConstructeurIds = (...sources: unknown[]): string[] => {
|
|||||||
if (value.constructeur) {
|
if (value.constructeur) {
|
||||||
explore(value.constructeur);
|
explore(value.constructeur);
|
||||||
}
|
}
|
||||||
if (typeof value.id === 'string') {
|
// Only extract ID if this looks like a constructeur object (has @type or recognizable fields)
|
||||||
|
// Don't extract ID from component/piece/product objects that happen to be passed in
|
||||||
|
if (typeof value.id === 'string' && !value.name && !value.typeComposant && !value.typePiece && !value.typeProduct) {
|
||||||
pushId(value.id);
|
pushId(value.id);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -107,7 +116,7 @@ export const formatConstructeurContact = (
|
|||||||
|
|
||||||
export const buildConstructeurRequestPayload = <T extends Record<string, any>>(
|
export const buildConstructeurRequestPayload = <T extends Record<string, any>>(
|
||||||
payload: T,
|
payload: T,
|
||||||
): T & { constructeurIds: string[] } => {
|
): T & { constructeurs?: string[] } => {
|
||||||
const ids = uniqueConstructeurIds(
|
const ids = uniqueConstructeurIds(
|
||||||
payload?.constructeurIds,
|
payload?.constructeurIds,
|
||||||
payload?.constructeurId,
|
payload?.constructeurId,
|
||||||
@@ -116,10 +125,13 @@ export const buildConstructeurRequestPayload = <T extends Record<string, any>>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const next = { ...payload } as Record<string, any>;
|
const next = { ...payload } as Record<string, any>;
|
||||||
next.constructeurIds = ids;
|
if (ids.length) {
|
||||||
|
next.constructeurs = ids.map((id) => `/api/constructeurs/${id}`);
|
||||||
|
}
|
||||||
delete next.constructeurId;
|
delete next.constructeurId;
|
||||||
delete next.constructeur;
|
delete next.constructeur;
|
||||||
delete next.constructeurs;
|
delete next.constructeurs;
|
||||||
|
delete next.constructeurIds;
|
||||||
|
|
||||||
return next as T & { constructeurIds: string[] };
|
return next as T & { constructeurs?: string[] };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: '2025-07-15',
|
compatibilityDate: '2025-07-15',
|
||||||
|
ssr: false, // Désactive le SSR pour un mode SPA pur (Client-Side Rendering uniquement)
|
||||||
devtools: { enabled: true },
|
devtools: { enabled: true },
|
||||||
devServer: {
|
devServer: {
|
||||||
|
host: '0.0.0.0',
|
||||||
port: 3001
|
port: 3001
|
||||||
},
|
},
|
||||||
modules: [
|
modules: [
|
||||||
@@ -18,8 +20,11 @@ export default defineNuxtConfig({
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
|
apiBaseUrl: process.env.NUXT_API_BASE_URL
|
||||||
|
|| process.env.NUXT_PUBLIC_API_BASE_URL
|
||||||
|
|| 'http://localhost/api',
|
||||||
public: {
|
public: {
|
||||||
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || 'http://localhost:3000',
|
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || 'http://localhost:8081/api',
|
||||||
appUrl: process.env.NUXT_PUBLIC_APP_URL || 'http://localhost:3001',
|
appUrl: process.env.NUXT_PUBLIC_APP_URL || 'http://localhost:3001',
|
||||||
appName: process.env.NUXT_PUBLIC_APP_NAME || 'Inventory Management System',
|
appName: process.env.NUXT_PUBLIC_APP_NAME || 'Inventory Management System',
|
||||||
appVersion: process.env.NUXT_PUBLIC_APP_VERSION || '0.1.0',
|
appVersion: process.env.NUXT_PUBLIC_APP_VERSION || '0.1.0',
|
||||||
|
|||||||
3
package-lock.json
generated
3
package-lock.json
generated
@@ -3754,7 +3754,6 @@
|
|||||||
"integrity": "sha512-UG8hdElzuBDzIbjG1QDwnYH0MQ73YLXDFHgZzB4Zh/YJfnw8XNsloVtytqzx0I2Qky9THSdpTmi8Vjn/pf/Lew==",
|
"integrity": "sha512-UG8hdElzuBDzIbjG1QDwnYH0MQ73YLXDFHgZzB4Zh/YJfnw8XNsloVtytqzx0I2Qky9THSdpTmi8Vjn/pf/Lew==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.9.0",
|
"@eslint-community/eslint-utils": "^4.9.0",
|
||||||
"@typescript-eslint/types": "^8.44.0",
|
"@typescript-eslint/types": "^8.44.0",
|
||||||
@@ -11673,7 +11672,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
|
||||||
"integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
|
"integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
},
|
},
|
||||||
@@ -12483,6 +12481,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
|
||||||
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
|
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/source-map": "^0.3.3",
|
"@jridgewell/source-map": "^0.3.3",
|
||||||
"acorn": "^8.15.0",
|
"acorn": "^8.15.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user