Add CommentSection component for inline comments on entity detail pages (machines, pieces, composants, products, categories, skeleton types). Add dedicated /comments page with filters, pagination and clickable links. Add unresolved count badge on avatar and in profile dropdown. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
185 lines
5.2 KiB
TypeScript
185 lines
5.2 KiB
TypeScript
import { ref } from 'vue'
|
|
import { useApi } from './useApi'
|
|
import { useToast } from './useToast'
|
|
import { extractCollection } from '~/shared/utils/apiHelpers'
|
|
|
|
export interface Comment {
|
|
id: string
|
|
content: string
|
|
entityType: string
|
|
entityId: string
|
|
entityName?: string | null
|
|
authorId: string
|
|
authorName: string
|
|
status: 'open' | 'resolved'
|
|
resolvedById?: string | null
|
|
resolvedByName?: string | null
|
|
resolvedAt?: string | null
|
|
createdAt: string
|
|
updatedAt: string
|
|
}
|
|
|
|
interface CommentResult {
|
|
success: boolean
|
|
data?: Comment | Comment[]
|
|
error?: string
|
|
}
|
|
|
|
interface CommentListResult {
|
|
success: boolean
|
|
data?: Comment[]
|
|
total?: number
|
|
error?: string
|
|
}
|
|
|
|
export function useComments() {
|
|
const { get, post, patch, delete: del } = useApi()
|
|
const { showSuccess, showError } = useToast()
|
|
const loading = ref(false)
|
|
|
|
const fetchComments = async (
|
|
entityType: string,
|
|
entityId: string,
|
|
status: string = 'open',
|
|
): Promise<CommentListResult> => {
|
|
loading.value = true
|
|
try {
|
|
const params = new URLSearchParams({
|
|
entityType,
|
|
entityId,
|
|
status,
|
|
'order[createdAt]': 'desc',
|
|
itemsPerPage: '200',
|
|
})
|
|
const result = await get(`/comments?${params.toString()}`)
|
|
if (result.success) {
|
|
const items = extractCollection<Comment>(result.data)
|
|
return { success: true, data: items }
|
|
}
|
|
return { success: false, error: result.error }
|
|
} catch (error) {
|
|
const err = error as Error
|
|
return { success: false, error: err.message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const fetchAllComments = async (options: {
|
|
status?: string
|
|
entityType?: string
|
|
page?: number
|
|
itemsPerPage?: number
|
|
} = {}): Promise<CommentListResult> => {
|
|
loading.value = true
|
|
try {
|
|
const params = new URLSearchParams()
|
|
if (options.status) params.set('status', options.status)
|
|
if (options.entityType) params.set('entityType', options.entityType)
|
|
params.set('order[createdAt]', 'desc')
|
|
params.set('itemsPerPage', String(options.itemsPerPage || 30))
|
|
params.set('page', String(options.page || 1))
|
|
|
|
const result = await get(`/comments?${params.toString()}`)
|
|
if (result.success) {
|
|
const items = extractCollection<Comment>(result.data)
|
|
const raw = result.data as Record<string, unknown> | null
|
|
const total = Number(raw?.['hydra:totalItems'] ?? raw?.totalItems ?? items.length)
|
|
return { success: true, data: items, total }
|
|
}
|
|
return { success: false, error: result.error }
|
|
} catch (error) {
|
|
const err = error as Error
|
|
return { success: false, error: err.message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const createComment = async (
|
|
entityType: string,
|
|
entityId: string,
|
|
content: string,
|
|
entityName?: string,
|
|
): Promise<CommentResult> => {
|
|
loading.value = true
|
|
try {
|
|
const payload: Record<string, string> = { entityType, entityId, content }
|
|
if (entityName) payload.entityName = entityName
|
|
const result = await post('/comments', payload)
|
|
if (result.success) {
|
|
showSuccess('Commentaire ajouté')
|
|
return { success: true, data: result.data as Comment }
|
|
}
|
|
if (result.error) showError(result.error)
|
|
return { success: false, error: result.error }
|
|
} catch (error) {
|
|
const err = error as Error
|
|
showError('Impossible d\'ajouter le commentaire')
|
|
return { success: false, error: err.message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const resolveComment = async (commentId: string): Promise<CommentResult> => {
|
|
loading.value = true
|
|
try {
|
|
const result = await patch(`/comments/${commentId}/resolve`)
|
|
if (result.success) {
|
|
showSuccess('Commentaire résolu')
|
|
return { success: true, data: result.data as Comment }
|
|
}
|
|
if (result.error) showError(result.error)
|
|
return { success: false, error: result.error }
|
|
} catch (error) {
|
|
const err = error as Error
|
|
showError('Impossible de résoudre le commentaire')
|
|
return { success: false, error: err.message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const deleteComment = async (commentId: string): Promise<CommentResult> => {
|
|
loading.value = true
|
|
try {
|
|
const result = await del(`/comments/${commentId}`)
|
|
if (result.success) {
|
|
showSuccess('Commentaire supprimé')
|
|
return { success: true }
|
|
}
|
|
if (result.error) showError(result.error)
|
|
return { success: false, error: result.error }
|
|
} catch (error) {
|
|
const err = error as Error
|
|
showError('Impossible de supprimer le commentaire')
|
|
return { success: false, error: err.message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const fetchUnresolvedCount = async (): Promise<number> => {
|
|
try {
|
|
const result = await get<{ count: number }>('/comments/stats/unresolved-count')
|
|
if (result.success && result.data) {
|
|
return result.data.count
|
|
}
|
|
return 0
|
|
} catch {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
return {
|
|
loading,
|
|
fetchComments,
|
|
fetchAllComments,
|
|
createComment,
|
|
resolveComment,
|
|
deleteComment,
|
|
fetchUnresolvedCount,
|
|
}
|
|
}
|