major: Rename legalconsenthub to gremiumhub
This commit is contained in:
@@ -1,335 +0,0 @@
|
||||
<template>
|
||||
<UModal :open="open" :title="$t('versions.compare')" @update:open="$emit('update:open', $event)">
|
||||
<template #header>
|
||||
<h3 class="text-lg font-semibold">{{ $t('versions.comparisonTitle', { number: versionNumber }) }}</h3>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<div v-if="loading" class="flex justify-center py-8">
|
||||
<UIcon name="i-lucide-loader-circle" class="animate-spin h-8 w-8" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="text-red-500">{{ $t('versions.comparisonError') }}: {{ error }}</div>
|
||||
|
||||
<div v-else-if="valueDiff && hasChanges" class="space-y-4">
|
||||
<!-- Summary Alert -->
|
||||
<UAlert
|
||||
:title="$t('versions.changesSummary', { count: totalChanges })"
|
||||
:icon="totalChanges > 0 ? 'i-lucide-file-diff' : 'i-lucide-check'"
|
||||
color="info"
|
||||
variant="subtle"
|
||||
/>
|
||||
|
||||
<!-- Changes grouped by section -->
|
||||
<UAccordion
|
||||
v-if="sectionChanges.length > 0"
|
||||
:items="accordionItems"
|
||||
type="multiple"
|
||||
:default-value="accordionItems.map((_, i) => String(i))"
|
||||
>
|
||||
<template #body="{ item }">
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="(change, changeIdx) in item.changes"
|
||||
:key="changeIdx"
|
||||
class="rounded-lg border border-default bg-elevated/50 p-3"
|
||||
>
|
||||
<!-- Element title with type indicator -->
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<UIcon v-if="change.elementType === 'TABLE'" name="i-lucide-table" class="h-4 w-4 text-muted" />
|
||||
<span class="font-medium text-sm">{{
|
||||
change.elementTitle || $t('versions.elementWithoutTitle')
|
||||
}}</span>
|
||||
|
||||
<!-- Change type badge -->
|
||||
<UBadge v-if="isNewAnswer(change)" color="success" variant="subtle" size="xs">
|
||||
{{ $t('versions.newAnswer') }}
|
||||
</UBadge>
|
||||
<UBadge v-else-if="isClearedAnswer(change)" color="warning" variant="subtle" size="xs">
|
||||
{{ $t('versions.clearedAnswer') }}
|
||||
</UBadge>
|
||||
<UBadge v-else color="info" variant="subtle" size="xs">
|
||||
{{ $t('versions.changedAnswer') }}
|
||||
</UBadge>
|
||||
</div>
|
||||
|
||||
<!-- Table diff display -->
|
||||
<div v-if="change.elementType === 'TABLE' && change.tableDiff" class="mt-3">
|
||||
<!-- Table summary -->
|
||||
<div class="flex items-center gap-4 text-xs text-muted mb-2">
|
||||
<span v-if="change.tableDiff.addedCount > 0" class="text-success">
|
||||
+{{ change.tableDiff.addedCount }} {{ $t('versions.tableRowsAdded') }}
|
||||
</span>
|
||||
<span v-if="change.tableDiff.removedCount > 0" class="text-error">
|
||||
-{{ change.tableDiff.removedCount }} {{ $t('versions.tableRowsRemoved') }}
|
||||
</span>
|
||||
<span v-if="change.tableDiff.modifiedCount > 0" class="text-warning">
|
||||
~{{ change.tableDiff.modifiedCount }} {{ $t('versions.tableRowsModified') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable table container -->
|
||||
<div class="overflow-x-auto border border-default rounded-lg">
|
||||
<table class="min-w-full text-xs">
|
||||
<thead class="bg-elevated">
|
||||
<tr>
|
||||
<th
|
||||
class="px-3 py-2 text-left font-medium text-muted whitespace-nowrap left-0 bg-elevated z-10"
|
||||
>
|
||||
#
|
||||
</th>
|
||||
<th
|
||||
class="px-3 py-2 text-left font-medium text-muted whitespace-nowrap left-8 bg-elevated z-10"
|
||||
>
|
||||
{{ $t('versions.tableStatus') }}
|
||||
</th>
|
||||
<th
|
||||
v-for="col in change.tableDiff.columns"
|
||||
:key="col"
|
||||
class="px-3 py-2 text-left font-medium text-muted whitespace-nowrap"
|
||||
>
|
||||
{{ col }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-default">
|
||||
<template v-for="row in change.tableDiff.rows" :key="row.rowIndex">
|
||||
<!-- Show only changed rows (added, removed, modified) -->
|
||||
<tr v-if="row.changeType !== 'unchanged'" :class="getRowClass(row.changeType)">
|
||||
<td
|
||||
class="px-3 py-2 text-muted whitespace-nowrap left-0 z-10"
|
||||
:class="getRowBgClass(row.changeType)"
|
||||
>
|
||||
{{ row.rowIndex + 1 }}
|
||||
</td>
|
||||
<td class="px-3 py-2 whitespace-nowrap left-8 z-10" :class="getRowBgClass(row.changeType)">
|
||||
<UBadge :color="getStatusBadgeColor(row.changeType)" variant="subtle" size="xs">
|
||||
{{ getStatusLabel(row.changeType) }}
|
||||
</UBadge>
|
||||
</td>
|
||||
<td v-for="col in change.tableDiff.columns" :key="col" class="px-3 py-2 whitespace-nowrap">
|
||||
<template v-if="row.changeType === 'added'">
|
||||
<span class="text-success">{{ row.currentValues[col] || '-' }}</span>
|
||||
</template>
|
||||
<template v-else-if="row.changeType === 'removed'">
|
||||
<span class="text-error line-through">{{ row.previousValues[col] || '-' }}</span>
|
||||
</template>
|
||||
<template v-else-if="row.changeType === 'modified'">
|
||||
<div v-if="row.previousValues[col] !== row.currentValues[col]" class="space-y-0.5">
|
||||
<div class="text-error line-through text-[10px]">
|
||||
{{ row.previousValues[col] || '-' }}
|
||||
</div>
|
||||
<div class="text-success">{{ row.currentValues[col] || '-' }}</div>
|
||||
</div>
|
||||
<span v-else class="text-muted">{{ row.currentValues[col] || '-' }}</span>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Non-table Before/After display -->
|
||||
<div v-else class="space-y-2 text-sm">
|
||||
<!-- Previous value -->
|
||||
<div class="flex items-start gap-2">
|
||||
<div class="flex items-center gap-1.5 text-muted min-w-[60px] shrink-0">
|
||||
<UIcon name="i-lucide-circle" class="h-3 w-3 text-muted" />
|
||||
<span>{{ $t('versions.before') }}:</span>
|
||||
</div>
|
||||
<span :class="change.previousLabel ? 'text-default' : 'text-muted italic'">
|
||||
{{ change.previousLabel || $t('versions.noValue') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Arrow indicator -->
|
||||
<div class="flex items-center gap-2 pl-1">
|
||||
<UIcon name="i-lucide-arrow-down" class="h-3 w-3 text-muted" />
|
||||
</div>
|
||||
|
||||
<!-- Current value -->
|
||||
<div class="flex items-start gap-2">
|
||||
<div class="flex items-center gap-1.5 text-primary min-w-[60px] shrink-0">
|
||||
<UIcon name="i-lucide-circle-dot" class="h-3 w-3" />
|
||||
<span>{{ $t('versions.after') }}:</span>
|
||||
</div>
|
||||
<span :class="change.currentLabel ? 'text-default font-medium' : 'text-muted italic'">
|
||||
{{ change.currentLabel || $t('versions.noValue') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UAccordion>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-center py-8">
|
||||
<UIcon name="i-lucide-check-circle" class="h-12 w-12 text-success mx-auto mb-3" />
|
||||
<p class="text-muted">{{ $t('versions.noChanges') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<UButton :label="$t('common.close')" color="neutral" variant="outline" @click="$emit('update:open', false)" />
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AccordionItem } from '@nuxt/ui'
|
||||
import type { ApplicationFormDto, ApplicationFormVersionDto } from '~~/.api-client'
|
||||
import type { FormValueDiff, ValueChange, SectionChanges, TableRowDiff } from '~~/types/formSnapshotComparison'
|
||||
import { compareApplicationFormValues, groupChangesBySection } from '~/utils/formSnapshotComparison'
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
currentForm: ApplicationFormDto
|
||||
versionNumber: number
|
||||
applicationFormId: string
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'update:open', value: boolean): void
|
||||
}>()
|
||||
|
||||
const { getVersion } = useApplicationFormVersion()
|
||||
const { t: $t } = useI18n()
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const valueDiff = ref<FormValueDiff | null>(null)
|
||||
const versionData = ref<ApplicationFormVersionDto | null>(null)
|
||||
|
||||
const sectionChanges = computed<SectionChanges[]>(() => {
|
||||
if (!valueDiff.value) return []
|
||||
return groupChangesBySection(valueDiff.value)
|
||||
})
|
||||
|
||||
const totalChanges = computed(() => {
|
||||
if (!valueDiff.value) return 0
|
||||
return (
|
||||
valueDiff.value.newAnswers.length + valueDiff.value.changedAnswers.length + valueDiff.value.clearedAnswers.length
|
||||
)
|
||||
})
|
||||
|
||||
const hasChanges = computed(() => totalChanges.value > 0)
|
||||
|
||||
interface AccordionItemWithChanges extends AccordionItem {
|
||||
changes: ValueChange[]
|
||||
}
|
||||
|
||||
const accordionItems = computed<AccordionItemWithChanges[]>(() => {
|
||||
return sectionChanges.value.map((section, index) => ({
|
||||
label: `${section.sectionTitle} (${$t('versions.changesCount', { count: section.changes.length })})`,
|
||||
icon: 'i-lucide-folder',
|
||||
value: String(index),
|
||||
changes: section.changes
|
||||
}))
|
||||
})
|
||||
|
||||
function isNewAnswer(change: ValueChange): boolean {
|
||||
return !change.previousLabel && !!change.currentLabel
|
||||
}
|
||||
|
||||
function isClearedAnswer(change: ValueChange): boolean {
|
||||
return !!change.previousLabel && !change.currentLabel
|
||||
}
|
||||
|
||||
function getRowClass(changeType: TableRowDiff['changeType']): string {
|
||||
switch (changeType) {
|
||||
case 'added':
|
||||
return 'bg-success/5'
|
||||
case 'removed':
|
||||
return 'bg-error/5'
|
||||
case 'modified':
|
||||
return 'bg-warning/5'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function getRowBgClass(changeType: TableRowDiff['changeType']): string {
|
||||
switch (changeType) {
|
||||
case 'added':
|
||||
return 'bg-success/10'
|
||||
case 'removed':
|
||||
return 'bg-error/10'
|
||||
case 'modified':
|
||||
return 'bg-warning/10'
|
||||
default:
|
||||
return 'bg-elevated'
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusBadgeColor(changeType: TableRowDiff['changeType']): 'success' | 'error' | 'warning' | 'neutral' {
|
||||
switch (changeType) {
|
||||
case 'added':
|
||||
return 'success'
|
||||
case 'removed':
|
||||
return 'error'
|
||||
case 'modified':
|
||||
return 'warning'
|
||||
default:
|
||||
return 'neutral'
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusLabel(changeType: TableRowDiff['changeType']): string {
|
||||
switch (changeType) {
|
||||
case 'added':
|
||||
return $t('versions.rowAdded')
|
||||
case 'removed':
|
||||
return $t('versions.rowRemoved')
|
||||
case 'modified':
|
||||
return $t('versions.rowModified')
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// Track which version was loaded to detect when we need to reload
|
||||
const loadedVersionNumber = ref<number | null>(null)
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
async (isOpen) => {
|
||||
if (isOpen) {
|
||||
// Always load if version changed or no data loaded yet
|
||||
if (loadedVersionNumber.value !== props.versionNumber || !versionData.value) {
|
||||
await loadVersionAndCompare()
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// Also reload when version number changes while modal is open
|
||||
watch(
|
||||
() => props.versionNumber,
|
||||
async (newVersion, oldVersion) => {
|
||||
if (newVersion !== oldVersion && props.open) {
|
||||
await loadVersionAndCompare()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
async function loadVersionAndCompare() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
valueDiff.value = null // Reset diff while loading
|
||||
try {
|
||||
versionData.value = await getVersion(props.applicationFormId, props.versionNumber)
|
||||
valueDiff.value = compareApplicationFormValues(props.currentForm, versionData.value.snapshot)
|
||||
loadedVersionNumber.value = props.versionNumber
|
||||
} catch (e: unknown) {
|
||||
error.value = e instanceof Error ? e.message : $t('versions.unknownError')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user