226 lines
8.6 KiB
TypeScript
226 lines
8.6 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { type CreateCommentDto, type CommentDto, ResponseError, type CursorPagedCommentDto } from '~~/.api-client'
|
|
import { useCommentApi } from '~/composables/comment/useCommentApi'
|
|
import { useLogger } from '../app/composables/useLogger'
|
|
|
|
type ApplicationFormId = string
|
|
type FormElementId = string
|
|
|
|
export const useCommentStore = defineStore('Comment', () => {
|
|
const nextCursorByApplicationFormId = ref<
|
|
Record<
|
|
ApplicationFormId,
|
|
Record<FormElementId, { nextCursorCreatedAt: Date | null; hasMore: boolean; isLoading: boolean }>
|
|
>
|
|
>({})
|
|
const commentsByApplicationFormId = ref<Record<ApplicationFormId, Record<FormElementId, CommentDto[]>>>({})
|
|
const countsByApplicationFormId = ref<Record<ApplicationFormId, Record<FormElementId, number>>>({})
|
|
|
|
const commentApi = useCommentApi()
|
|
const loadedFormElementComments = ref(new Set<string>())
|
|
const logger = useLogger().withTag('commentStore')
|
|
|
|
async function loadInitial(applicationFormId: ApplicationFormId, formElementId: FormElementId) {
|
|
initializeCursorState(applicationFormId, formElementId)
|
|
|
|
const cacheKey = `${applicationFormId}:${formElementId}`
|
|
if (loadedFormElementComments.value.has(cacheKey)) return
|
|
|
|
nextCursorByApplicationFormId.value[applicationFormId]![formElementId]!.isLoading = true
|
|
try {
|
|
const page = await commentApi.getCommentsByApplicationFormId(applicationFormId, formElementId, undefined, 10)
|
|
upsertComments(applicationFormId, formElementId, page, { prepend: false })
|
|
} catch (e) {
|
|
nextCursorByApplicationFormId.value[applicationFormId]![formElementId]!.isLoading = false
|
|
logger.error('Failed loading initial comments:', e)
|
|
return
|
|
}
|
|
loadedFormElementComments.value.add(cacheKey)
|
|
}
|
|
|
|
async function loadMore(applicationFormId: ApplicationFormId, formElementId: FormElementId) {
|
|
initializeCursorState(applicationFormId, formElementId)
|
|
const state = nextCursorByApplicationFormId.value[applicationFormId]![formElementId]!
|
|
if (state.isLoading || !state.hasMore) return
|
|
|
|
state.isLoading = true
|
|
const cursor = state.nextCursorCreatedAt ?? undefined
|
|
|
|
try {
|
|
const page = await commentApi.getCommentsByApplicationFormId(applicationFormId, formElementId, cursor, 10)
|
|
// Prepend older comments when loading more (scroll up)
|
|
upsertComments(applicationFormId, formElementId, page, { prepend: true })
|
|
} catch (e) {
|
|
state.isLoading = false
|
|
logger.error('Failed loading more comments:', e)
|
|
return Promise.reject(e)
|
|
}
|
|
}
|
|
|
|
async function createComment(
|
|
applicationFormId: ApplicationFormId,
|
|
formElementId: string,
|
|
createCommentDto: CreateCommentDto
|
|
): Promise<CommentDto> {
|
|
try {
|
|
const newComment = await commentApi.createComment(applicationFormId, formElementId, createCommentDto)
|
|
const commentsByFormElement = commentsByApplicationFormId.value[applicationFormId] ?? {}
|
|
if (!commentsByFormElement[formElementId]) {
|
|
commentsByFormElement[formElementId] = []
|
|
}
|
|
commentsByFormElement[formElementId].push(newComment)
|
|
commentsByApplicationFormId.value[applicationFormId] = commentsByFormElement
|
|
|
|
const currentCounts = countsByApplicationFormId.value[applicationFormId] ?? {}
|
|
currentCounts[formElementId] = (currentCounts[formElementId] ?? 0) + 1
|
|
countsByApplicationFormId.value[applicationFormId] = currentCounts
|
|
|
|
return newComment
|
|
} catch (e: unknown) {
|
|
if (e instanceof ResponseError) {
|
|
logger.error('Failed creating comment:', e.response)
|
|
} else {
|
|
logger.error('Failed creating comment:', e)
|
|
}
|
|
return Promise.reject(e)
|
|
}
|
|
}
|
|
|
|
async function updateComment(id?: string, commentDto?: CommentDto): Promise<CommentDto> {
|
|
if (!id || !commentDto) {
|
|
return Promise.reject(new Error('ID or comment DTO missing'))
|
|
}
|
|
|
|
try {
|
|
const updatedComment = await commentApi.updateComment(id, commentDto)
|
|
|
|
const commentsByFormElement = commentsByApplicationFormId.value[updatedComment.applicationFormId]
|
|
const formElementComments = commentsByFormElement?.[updatedComment.formElementId]
|
|
|
|
// Update the comment in the store
|
|
if (formElementComments) {
|
|
const index = formElementComments.findIndex((comment) => comment.id === id)
|
|
if (index !== -1 && formElementComments[index]) {
|
|
formElementComments[index] = updatedComment
|
|
}
|
|
}
|
|
|
|
return updatedComment
|
|
} catch (e: unknown) {
|
|
if (e instanceof ResponseError) {
|
|
logger.error(`Failed updating comment with ID ${id}:`, e.response)
|
|
} else {
|
|
logger.error(`Failed updating comment with ID ${id}:`, e)
|
|
}
|
|
return Promise.reject(e)
|
|
}
|
|
}
|
|
|
|
async function deleteCommentById(id: string): Promise<void> {
|
|
try {
|
|
await commentApi.deleteCommentById(id)
|
|
|
|
// Remove the comment from the store
|
|
for (const applicationFormId in commentsByApplicationFormId.value) {
|
|
const commentsByFormElement = commentsByApplicationFormId.value[applicationFormId]
|
|
|
|
if (!commentsByFormElement) continue
|
|
|
|
for (const formElementId in commentsByFormElement) {
|
|
const formElementComments = commentsByFormElement[formElementId]
|
|
|
|
if (formElementComments) {
|
|
const index = formElementComments.findIndex((comment) => comment.id === id)
|
|
|
|
if (index !== -1) {
|
|
// Remove the comment from the array
|
|
formElementComments.splice(index, 1)
|
|
const commentCountsByFormElement = countsByApplicationFormId.value[applicationFormId]
|
|
|
|
// Decrement the comment count for the form element
|
|
if (commentCountsByFormElement && commentCountsByFormElement[formElementId] != null) {
|
|
commentCountsByFormElement[formElementId] = Math.max(
|
|
0,
|
|
(commentCountsByFormElement[formElementId] ?? 0) - 1
|
|
)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e: unknown) {
|
|
if (e instanceof ResponseError) {
|
|
logger.error(`Failed deleting comment with ID ${id}:`, e.response)
|
|
} else {
|
|
logger.error(`Failed deleting comment with ID ${id}:`, e)
|
|
}
|
|
return Promise.reject(e)
|
|
}
|
|
}
|
|
|
|
function upsertComments(
|
|
applicationFormId: ApplicationFormId,
|
|
formElementId: FormElementId,
|
|
page: CursorPagedCommentDto,
|
|
options: { prepend: boolean }
|
|
) {
|
|
const applicationFormComments = commentsByApplicationFormId.value[applicationFormId] ?? {}
|
|
const formElementComments = applicationFormComments[formElementId] ?? (applicationFormComments[formElementId] = [])
|
|
|
|
const newComments = page.content.filter((newComment) => !formElementComments.some((c) => c.id === newComment.id))
|
|
|
|
if (options.prepend) {
|
|
// Prepend older comments at the beginning (they come in DESC order, so reverse to maintain chronological order)
|
|
formElementComments.unshift(...newComments.reverse())
|
|
} else {
|
|
// Initial load: comments come in DESC order (newest first), reverse for display (oldest at top, newest at bottom)
|
|
formElementComments.push(...newComments.reverse())
|
|
}
|
|
|
|
commentsByApplicationFormId.value[applicationFormId] = applicationFormComments
|
|
|
|
nextCursorByApplicationFormId.value[applicationFormId]![formElementId] = {
|
|
nextCursorCreatedAt: page.nextCursorCreatedAt ?? null,
|
|
hasMore: page.hasMore,
|
|
isLoading: false
|
|
}
|
|
}
|
|
|
|
function initializeCursorState(applicationFormId: ApplicationFormId, formElementId: FormElementId) {
|
|
if (!nextCursorByApplicationFormId.value[applicationFormId]) {
|
|
nextCursorByApplicationFormId.value[applicationFormId] = {}
|
|
}
|
|
if (!nextCursorByApplicationFormId.value[applicationFormId]![formElementId]) {
|
|
nextCursorByApplicationFormId.value[applicationFormId]![formElementId] = {
|
|
nextCursorCreatedAt: null,
|
|
hasMore: true,
|
|
isLoading: false
|
|
}
|
|
}
|
|
}
|
|
|
|
async function loadCounts(applicationFormId: ApplicationFormId) {
|
|
try {
|
|
const result = await commentApi.getGroupedCommentCountByApplicationFromId(applicationFormId)
|
|
countsByApplicationFormId.value[applicationFormId] = Object.fromEntries(
|
|
Object.entries(result.counts ?? {}).map(([formElementId, count]) => [formElementId, Number(count)])
|
|
)
|
|
} catch (e) {
|
|
logger.error('Failed loading comment counts:', e)
|
|
}
|
|
}
|
|
|
|
return {
|
|
loadCounts,
|
|
loadInitial,
|
|
loadMore,
|
|
createComment,
|
|
updateComment,
|
|
deleteCommentById,
|
|
commentsByApplicationFormId,
|
|
nextCursorByApplicationFormId,
|
|
countsByApplicationFormId
|
|
}
|
|
})
|