Address field update history review

This commit is contained in:
karilint
2026-06-03 14:26:19 +03:00
parent 3a6e14d3a4
commit 5a167a408c
2 changed files with 94 additions and 34 deletions
@@ -1,6 +1,6 @@
import HistoryIcon from '@mui/icons-material/History'
import { Box, Card, Divider, IconButton, Popover, Tooltip, Typography } from '@mui/material'
import { createContext, useContext, useMemo, useState, type Context, type MouseEvent } from 'react'
import { useContext, useMemo, useState, type Context, type MouseEvent } from 'react'
import { DetailContext, type DetailContextType } from '@/components/DetailView/Context/DetailContext'
import type { AnyReference, UpdateLog } from '@/shared/types'
import { ReferenceList } from './ReferenceList'
@@ -14,15 +14,13 @@ type FieldUpdate = {
container: UpdateContainer
}
const SafeDetailContext =
(DetailContext as unknown as Context<DetailContextType<Record<string, unknown>> | null> | undefined) ??
createContext<DetailContextType<Record<string, unknown>> | null>(null)
const SafeDetailContext = DetailContext as unknown as Context<DetailContextType<Record<string, unknown>> | null>
const isObject = (value: unknown): value is Record<string, unknown> =>
typeof value === 'object' && value !== null && !Array.isArray(value)
const isUpdateLog = (value: unknown): value is UpdateLog =>
isObject(value) && ('column_name' in value || 'old_data' in value || 'new_data' in value || 'log_action' in value)
isObject(value) && 'column_name' in value && 'log_action' in value
const isUpdateContainer = (value: unknown): value is UpdateContainer =>
isObject(value) && Array.isArray(value.updates) && value.updates.every(isUpdateLog)
@@ -53,13 +51,6 @@ const getFieldUpdates = (data: unknown, field: string): FieldUpdate[] =>
}))
)
const formatDate = (value: unknown): string => {
if (!value) return 'No date'
const date = new Date(value as string | number | Date)
if (Number.isNaN(date.getTime())) return formatValue(value)
return date.toISOString().split('T')[0]
}
const formatValue = (value: unknown): string => {
if (value === null || value === undefined) return ''
if (typeof value === 'string') return value
@@ -68,6 +59,13 @@ const formatValue = (value: unknown): string => {
return JSON.stringify(value)
}
const formatDate = (value: unknown): string => {
if (!value) return 'No date'
const date = new Date(value as string | number | Date)
if (Number.isNaN(date.getTime())) return formatValue(value)
return date.toISOString().split('T')[0]
}
const findFirstBySuffix = (container: UpdateContainer, suffix: string): unknown => {
const entry = Object.entries(container).find(([key]) => key.endsWith(suffix))
return entry?.[1]
@@ -149,7 +147,7 @@ export const FieldUpdateHistory = ({ field, label }: { field: string; label: str
{updates.map(({ log, container }, index) => {
const references = collectReferences(container)
return (
<Card key={`${log.log_id ?? index}-${log.column_name ?? field}`} sx={{ p: 1.5 }}>
<Card key={`${index}-${log.log_id ?? ''}-${log.column_name ?? field}`} sx={{ p: 1.5 }}>
<Typography variant="body2">
<b>Date:</b> {formatDate(getDate(container))}
</Typography>
@@ -18,8 +18,7 @@ const reference = {
},
}
const contextValue = {
data: {
const detailData = {
body_mass: 10,
now_sau: [
{
@@ -39,11 +38,18 @@ const contextValue = {
],
},
],
},
}
const contextValue = {
data: detailData,
mode: { read: true, staging: false, new: false, option: 'read' },
} as unknown as ContextType<typeof DetailContext>
describe('FieldUpdateHistory', () => {
beforeEach(() => {
jest.restoreAllMocks()
})
it('shows field-specific update history from detail data', async () => {
const user = userEvent.setup()
@@ -77,4 +83,60 @@ describe('FieldUpdateHistory', () => {
expect(screen.queryByLabelText('Show update history for Diet')).not.toBeInTheDocument()
})
it('does not show the history icon in edit mode', () => {
render(
<MemoryRouter>
<DetailContext.Provider
value={{ ...contextValue, mode: { read: false, staging: false, new: false, option: 'edit' } }}
>
<ArrayToTable array={[['Body mass', <FieldElement key="body_mass" field="body_mass" />]]} />
</DetailContext.Provider>
</MemoryRouter>
)
expect(screen.queryByLabelText('Show update history for Body mass')).not.toBeInTheDocument()
})
it('renders multiple same-field updates without duplicate key warnings', async () => {
const user = userEvent.setup()
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => undefined)
const duplicateLogContext = {
...contextValue,
data: {
...detailData,
now_sau: [
detailData.now_sau[0],
{
...detailData.now_sau[0],
sau_date: '2026-05-30',
sau_comment: 'Updated body mass again',
updates: [
{
log_id: 1,
column_name: 'body_mass',
log_action: 3,
old_data: '12',
new_data: '14',
},
],
},
],
},
} as unknown as ContextType<typeof DetailContext>
render(
<MemoryRouter>
<DetailContext.Provider value={duplicateLogContext}>
<ArrayToTable array={[['Body mass', <FieldElement key="body_mass" field="body_mass" />]]} />
</DetailContext.Provider>
</MemoryRouter>
)
await user.click(screen.getByLabelText('Show update history for Body mass'))
expect(screen.getByText('Updated body mass')).toBeInTheDocument()
expect(screen.getByText('Updated body mass again')).toBeInTheDocument()
expect(consoleError).not.toHaveBeenCalledWith(expect.stringContaining('Encountered two children with the same key'))
})
})