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:
Matthieu
2026-03-02 14:06:06 +01:00
parent e22463874c
commit a98ab8c275
12 changed files with 848 additions and 3 deletions

View File

@@ -65,6 +65,9 @@
:class="childLinkClass(child)"
>
{{ child.label }}
<span v-if="child.to === '/comments' && unresolvedCount > 0" class="badge badge-warning badge-xs ml-1">
{{ unresolvedCount }}
</span>
</NuxtLink>
</li>
</ul>
@@ -142,6 +145,9 @@
:class="childLinkClass(child)"
>
{{ child.label }}
<span v-if="child.to === '/comments' && unresolvedCount > 0" class="badge badge-warning badge-xs ml-1">
{{ unresolvedCount }}
</span>
</NuxtLink>
</li>
</ul>
@@ -166,8 +172,14 @@
<div
tabindex="0"
role="button"
class="btn btn-ghost btn-circle avatar placeholder"
class="btn btn-ghost btn-circle avatar placeholder indicator"
>
<span
v-if="unresolvedCount > 0"
class="indicator-item badge badge-warning badge-xs"
>
{{ unresolvedCount }}
</span>
<div
class="bg-secondary text-secondary-content rounded-full w-10 h-10 grid place-items-center"
>
@@ -193,6 +205,15 @@
<IconLucideChevronRight class="w-4 h-4" aria-hidden="true" />
</NuxtLink>
</li>
<li>
<NuxtLink to="/comments" class="justify-between">
Commentaires
<span v-if="unresolvedCount > 0" class="badge badge-warning badge-xs">
{{ unresolvedCount }}
</span>
<IconLucideChevronRight v-else class="w-4 h-4" aria-hidden="true" />
</NuxtLink>
</li>
<li>
<button
type="button"
@@ -212,11 +233,12 @@
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { useRoute } from '#imports'
import { useNavDropdown } from '~/composables/useNavDropdown'
import { usePermissions } from '~/composables/usePermissions'
import { useProfileSession } from '~/composables/useProfileSession'
import { useComments } from '~/composables/useComments'
import IconLucideMenu from '~icons/lucide/menu'
import IconLucideSettings from '~icons/lucide/settings'
import IconLucideChevronRight from '~icons/lucide/chevron-right'
@@ -277,11 +299,12 @@ const navGroups: NavGroup[] = [
{
id: 'resources',
label: 'Ressources liées',
activePaths: ['/sites', '/documents', '/constructeurs', '/activity-log'],
activePaths: ['/sites', '/documents', '/constructeurs', '/activity-log', '/comments'],
children: [
{ to: '/sites', label: 'Sites' },
{ to: '/documents', label: 'Documents' },
{ to: '/constructeurs', label: 'Fournisseurs' },
{ to: '/comments', label: 'Commentaires' },
{ to: '/activity-log', label: 'Journal d\'activité' },
],
},
@@ -291,6 +314,24 @@ const route = useRoute()
const { openDropdown, setDropdown, scheduleDropdownClose, toggleDropdown } = useNavDropdown()
const { activeProfile } = useProfileSession()
const { isAdmin, canEdit } = usePermissions()
const { fetchUnresolvedCount } = useComments()
const unresolvedCount = ref(0)
let pollInterval: ReturnType<typeof setInterval> | null = null
const refreshUnresolvedCount = async () => {
if (!activeProfile.value) return
unresolvedCount.value = await fetchUnresolvedCount()
}
onMounted(() => {
refreshUnresolvedCount()
pollInterval = setInterval(refreshUnresolvedCount, 60_000)
})
onBeforeUnmount(() => {
if (pollInterval) clearInterval(pollInterval)
})
const isActive = (path: string) => {
if (path === '/') {