Files
mdm-ordinis/ordinis-admin-ui/src/components/version/WhatsNewButton.tsx
T
Zimin A.N. e46598c47d feat(updateBanner): «Что нового» button открывает modal с changelog
Per user feedback: «по кнопке что нового должно открываться модальное
окно с изменениями».

UpdateBanner кнопка «Что нового» (т9n key `updateBanner.docs`) ранее
открывала `/docs/` в новой вкладке — generic documentation page, не
contextual для «что именно в этом обновлении».

Теперь — открывает `WhatsNewModal` (тот же modal что показывает 🎁
button в TopBar) — public changelog с release notes сразу.

## Refactor

- **`WhatsNewModal.tsx` (NEW)** — extracted controlled-mode modal с
  release notes. Props: `open`, `onClose`. Internal logic читает
  changelog, tracks lastSeen в localStorage, на open mark'ает latest
  как seen.
- **`WhatsNewButton.tsx`** — refactored: trigger button + use
  `WhatsNewModal`. Поведение unchanged (🎁 icon с unread badge → click → modal).
- **`UpdateBanner.tsx`** — replaced `window.open(DOCS_URL)` на
  `setWhatsNewOpen(true)` + render `<WhatsNewModal>`. DOCS_URL constant
  removed (больше не используется).

## UX intent

Когда update detected:
1. User видит orange banner «Доступна новая версия v2.16.0»
2. Hover «Что нового» button → opens modal
3. User читает release notes (что добавлено, исправлено) ДО reload
4. Click «Обновить» → reload

Раньше «Что нового» вёл к generic docs page — user не знал ЧТО
изменилось, только что update есть. Modal даёт structured answer.

## Test plan

- [x] `pnpm tsc --noEmit` clean
- [x] `pnpm build` clean
- [x] `pnpm vitest run useChangelog.test` — 11/11 pass (parser logic
      unchanged)
- [ ] Manual: симулировать update banner (mock useAppVersion
      `updateAvailable=true`), click «Что нового» → modal opens
- [ ] Manual: 🎁 button в TopBar также opens same modal
- [ ] Manual: close modal → red dot disappears на 🎁 button
      (localStorage updated)
2026-05-12 23:28:12 +03:00

70 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { GiftIcon } from '@phosphor-icons/react'
import {
useChangelog,
readLastSeenVersion,
isVersionNewer,
} from './useChangelog'
import { WhatsNewModal } from './WhatsNewModal'
import { cn } from '@/lib/utils'
/**
* «Что нового» button в TopBar.
*
* <p>🎁 GiftIcon с red unread badge counter (entries newer than
* {@code localStorage.ord-whatsnew-last-seen}). Click → {@link WhatsNewModal}.
*
* <p>Modal extracted в own component — также используется в
* {@link UpdateBanner} «Что нового» button (когда новая версия detected).
*/
export function WhatsNewButton() {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const entries = useChangelog()
// useState lastSeen — re-compute unread после modal close. localStorage
// write (внутри WhatsNewModal на open) не trigger'ит React re-render
// отдельно, поэтому tracking через useEffect on open close в modal.
// В button мы читаем initially — на close button перестроится через
// обычный rerender chain.
const lastSeen = useMemo(() => readLastSeenVersion(), [open])
const unreadCount = useMemo(
() => entries.filter((e) => isVersionNewer(e.version, lastSeen)).length,
[entries, lastSeen],
)
const label = t('whatsnew.button', { defaultValue: 'Что нового' })
return (
<>
<button
type="button"
title={label}
aria-label={label}
onClick={() => setOpen(true)}
className={cn(
'relative size-7 rounded-md text-ink-2 hover:text-ink hover:bg-surface-2',
'inline-flex items-center justify-center transition-colors',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
)}
>
<GiftIcon size={14} weight={unreadCount > 0 ? 'fill' : 'regular'} />
{unreadCount > 0 && (
<span
aria-label={t('whatsnew.unread', {
count: unreadCount,
defaultValue: `${unreadCount} новых`,
})}
className="absolute -top-0.5 -right-0.5 inline-flex items-center justify-center min-w-[14px] h-3.5 px-1 rounded-full bg-mars text-on-mars text-[9px] font-semibold leading-none"
>
{unreadCount > 9 ? '9+' : unreadCount}
</span>
)}
</button>
<WhatsNewModal open={open} onClose={() => setOpen(false)} />
</>
)
}