Consolidate create and edit pages into single create pages with edit mode support. Remove obsolete catalog pages, history composables, and fix remaining code review issues. Include migration to relink orphaned custom fields. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
328 lines
9.7 KiB
TypeScript
328 lines
9.7 KiB
TypeScript
import { ref } from 'vue'
|
|
import { useApi } from './useApi'
|
|
import { useToast } from './useToast'
|
|
import { extractCollection, extractTotal } from '~/shared/utils/apiHelpers'
|
|
|
|
export interface Document {
|
|
id: string
|
|
name: string
|
|
filename: string
|
|
mimeType: string
|
|
size: number
|
|
fileUrl: string
|
|
downloadUrl: string
|
|
type?: string
|
|
/** @deprecated Legacy Base64 data URI — use fileUrl instead */
|
|
path?: string
|
|
createdAt?: string
|
|
siteId?: string
|
|
machineId?: string
|
|
composantId?: string
|
|
productId?: string
|
|
pieceId?: string
|
|
site?: { id: string; name?: string } | null
|
|
machine?: { id: string; name?: string } | null
|
|
composant?: { id: string; name?: string } | null
|
|
piece?: { id: string; name?: string } | null
|
|
product?: { id: string; name?: string } | null
|
|
}
|
|
|
|
export interface UploadContext {
|
|
siteId?: string
|
|
machineId?: string
|
|
composantId?: string
|
|
productId?: string
|
|
pieceId?: string
|
|
type?: string
|
|
}
|
|
|
|
export interface DocumentResult {
|
|
success: boolean
|
|
data?: Document | Document[]
|
|
error?: string
|
|
}
|
|
|
|
interface LoadDocumentsOptions {
|
|
search?: string
|
|
page?: number
|
|
itemsPerPage?: number
|
|
orderBy?: string
|
|
orderDir?: 'asc' | 'desc'
|
|
attachmentFilter?: string
|
|
type?: string
|
|
force?: boolean
|
|
}
|
|
|
|
const documents = ref<Document[]>([])
|
|
const total = ref(0)
|
|
const loading = ref(false)
|
|
const loaded = ref(false)
|
|
|
|
export function useDocuments() {
|
|
const { get, patch, postFormData, delete: del } = useApi()
|
|
const { showError, showSuccess } = useToast()
|
|
|
|
const loadFromEndpoint = async (
|
|
endpoint: string,
|
|
{ updateStore = false, itemsPerPage }: { updateStore?: boolean; itemsPerPage?: number } = {},
|
|
): Promise<DocumentResult> => {
|
|
loading.value = true
|
|
try {
|
|
const url = itemsPerPage ? `${endpoint}${endpoint.includes('?') ? '&' : '?'}itemsPerPage=${itemsPerPage}` : endpoint
|
|
const result = await get(url)
|
|
if (result.success) {
|
|
const data = extractCollection(result.data)
|
|
if (updateStore) {
|
|
documents.value = data
|
|
}
|
|
return { success: true, data }
|
|
}
|
|
if (result.error) {
|
|
showError(result.error)
|
|
}
|
|
return result as DocumentResult
|
|
} catch (error) {
|
|
const err = error as Error
|
|
console.error(`Erreur lors du chargement des documents (${endpoint}):`, error)
|
|
showError('Impossible de charger les documents')
|
|
return { success: false, error: err.message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const loadDocuments = async (options: LoadDocumentsOptions = {}): Promise<DocumentResult> => {
|
|
const {
|
|
search = '',
|
|
page = 1,
|
|
itemsPerPage = 30,
|
|
orderBy = 'createdAt',
|
|
orderDir = 'desc',
|
|
attachmentFilter = 'all',
|
|
type = 'all',
|
|
force = false,
|
|
} = options
|
|
|
|
if (!force && loaded.value && !search && page === 1 && attachmentFilter === 'all' && type === 'all') {
|
|
return { success: true, data: documents.value }
|
|
}
|
|
|
|
if (loading.value) {
|
|
return { success: true, data: documents.value }
|
|
}
|
|
|
|
loading.value = true
|
|
try {
|
|
const params = new URLSearchParams()
|
|
params.set('itemsPerPage', String(itemsPerPage))
|
|
params.set('page', String(page))
|
|
|
|
if (search && search.trim()) {
|
|
params.set('name', search.trim())
|
|
}
|
|
|
|
if (attachmentFilter && attachmentFilter !== 'all') {
|
|
params.set(`exists[${attachmentFilter}]`, 'true')
|
|
}
|
|
|
|
if (type && type !== 'all') {
|
|
params.set('type', type)
|
|
}
|
|
|
|
params.set(`order[${orderBy}]`, orderDir)
|
|
|
|
const result = await get(`/documents?${params.toString()}`)
|
|
if (result.success) {
|
|
const items = extractCollection(result.data)
|
|
documents.value = items
|
|
total.value = extractTotal(result.data, items.length)
|
|
loaded.value = true
|
|
return { success: true, data: items }
|
|
}
|
|
if (result.error) {
|
|
showError(result.error)
|
|
}
|
|
return result as DocumentResult
|
|
} catch (error) {
|
|
const err = error as Error
|
|
console.error('Erreur lors du chargement des documents:', error)
|
|
showError('Impossible de charger les documents')
|
|
return { success: false, error: err.message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const loadDocumentsBySite = async (
|
|
siteId: string,
|
|
options: { updateStore?: boolean } = {},
|
|
): Promise<DocumentResult> => {
|
|
if (!siteId) {
|
|
return { success: false, error: 'Aucun site sélectionné' }
|
|
}
|
|
return loadFromEndpoint(`/documents/site/${siteId}`, { updateStore: options.updateStore ?? false })
|
|
}
|
|
|
|
const loadDocumentsByMachine = async (
|
|
machineId: string,
|
|
options: { updateStore?: boolean } = {},
|
|
): Promise<DocumentResult> => {
|
|
if (!machineId) {
|
|
return { success: false, error: 'Aucune machine sélectionnée' }
|
|
}
|
|
return loadFromEndpoint(`/documents/machine/${machineId}`, { updateStore: options.updateStore ?? false })
|
|
}
|
|
|
|
const loadDocumentsByComponent = async (
|
|
componentId: string,
|
|
options: { updateStore?: boolean } = {},
|
|
): Promise<DocumentResult> => {
|
|
if (!componentId) {
|
|
return { success: false, error: 'Aucun composant sélectionné' }
|
|
}
|
|
return loadFromEndpoint(`/documents/composant/${componentId}`, { updateStore: options.updateStore ?? false })
|
|
}
|
|
|
|
const loadDocumentsByProduct = async (
|
|
productId: string,
|
|
options: { updateStore?: boolean } = {},
|
|
): Promise<DocumentResult> => {
|
|
if (!productId) {
|
|
return { success: false, error: 'Aucun produit sélectionné' }
|
|
}
|
|
return loadFromEndpoint(`/documents/product/${productId}`, { updateStore: options.updateStore ?? false })
|
|
}
|
|
|
|
const loadDocumentsByPiece = async (
|
|
pieceId: string,
|
|
options: { updateStore?: boolean } = {},
|
|
): Promise<DocumentResult> => {
|
|
if (!pieceId) {
|
|
return { success: false, error: 'Aucune pièce sélectionnée' }
|
|
}
|
|
return loadFromEndpoint(`/documents/piece/${pieceId}`, { updateStore: options.updateStore ?? false })
|
|
}
|
|
|
|
const uploadDocuments = async (
|
|
{ files, context = {} }: { files: File[]; context?: UploadContext },
|
|
{ updateStore = false }: { updateStore?: boolean } = {},
|
|
): Promise<DocumentResult> => {
|
|
if (!files.length) {
|
|
return { success: false, error: 'Aucun fichier sélectionné' }
|
|
}
|
|
|
|
loading.value = true
|
|
const created: Document[] = []
|
|
|
|
try {
|
|
for (const file of files) {
|
|
const formData = new FormData()
|
|
formData.append('file', file)
|
|
formData.append('name', file.name)
|
|
if (context.type) formData.append('type', context.type)
|
|
|
|
if (context.siteId) formData.append('siteId', context.siteId)
|
|
if (context.machineId) formData.append('machineId', context.machineId)
|
|
if (context.composantId) formData.append('composantId', context.composantId)
|
|
if (context.productId) formData.append('productId', context.productId)
|
|
if (context.pieceId) formData.append('pieceId', context.pieceId)
|
|
|
|
const result = await postFormData('/documents', formData)
|
|
if (result.success) {
|
|
created.push(result.data as Document)
|
|
showSuccess(`Document "${file.name}" ajouté`)
|
|
} else if (result.error) {
|
|
showError(`Erreur sur ${file.name} : ${result.error}`)
|
|
}
|
|
}
|
|
|
|
if (created.length) {
|
|
if (updateStore) {
|
|
documents.value = [...created, ...documents.value]
|
|
}
|
|
return { success: true, data: created }
|
|
}
|
|
|
|
return { success: false, error: 'Aucun document ajouté' }
|
|
} catch (error) {
|
|
const err = error as Error
|
|
console.error("Erreur lors de l'upload des documents:", error)
|
|
showError("Échec de l'ajout des documents")
|
|
return { success: false, error: err.message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const deleteDocument = async (
|
|
id: string | number,
|
|
{ updateStore = false }: { updateStore?: boolean } = {},
|
|
): Promise<DocumentResult> => {
|
|
if (!id) {
|
|
return { success: false, error: 'Identifiant manquant' }
|
|
}
|
|
|
|
loading.value = true
|
|
try {
|
|
const result = await del(`/documents/${id}`)
|
|
if (result.success) {
|
|
if (updateStore) {
|
|
documents.value = documents.value.filter((doc) => doc.id !== id)
|
|
}
|
|
showSuccess('Document supprimé')
|
|
}
|
|
return result as DocumentResult
|
|
} catch (error) {
|
|
const err = error as Error
|
|
console.error('Erreur lors de la suppression du document:', error)
|
|
showError('Impossible de supprimer le document')
|
|
return { success: false, error: err.message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const updateDocument = async (
|
|
id: string,
|
|
data: { name?: string; type?: string },
|
|
): Promise<DocumentResult> => {
|
|
loading.value = true
|
|
try {
|
|
const result = await patch(`/documents/${id}`, data)
|
|
if (result.success && result.data) {
|
|
const updated = result.data as Document
|
|
const index = documents.value.findIndex((doc) => doc.id === id)
|
|
if (index !== -1) {
|
|
documents.value[index] = { ...documents.value[index], ...updated }
|
|
}
|
|
showSuccess('Document mis à jour')
|
|
return { success: true, data: updated }
|
|
}
|
|
if (result.error) showError(result.error)
|
|
return result as DocumentResult
|
|
} catch (error) {
|
|
const err = error as Error
|
|
showError('Impossible de mettre à jour le document')
|
|
return { success: false, error: err.message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
return {
|
|
documents,
|
|
total,
|
|
loading,
|
|
loaded,
|
|
loadDocuments,
|
|
loadDocumentsBySite,
|
|
loadDocumentsByMachine,
|
|
loadDocumentsByComponent,
|
|
loadDocumentsByPiece,
|
|
loadDocumentsByProduct,
|
|
uploadDocuments,
|
|
updateDocument,
|
|
deleteDocument,
|
|
}
|
|
}
|