feat(comments): add comment/ticket system across all entity pages
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>
This commit is contained in:
184
app/composables/useComments.ts
Normal file
184
app/composables/useComments.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user