Merge branch 'feat/catalog-relative-time-flat-sidebar' into 'main'
feat(layout): catalog relative time + chevron + flat sidebar per redesign (4) See merge request 2-6/2-6-4/terravault/ordinis!78
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
|||||||
Webhook,
|
Webhook,
|
||||||
ScrollText,
|
ScrollText,
|
||||||
Search,
|
Search,
|
||||||
GitBranch,
|
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { useAuth } from 'react-oidc-context'
|
import { useAuth } from 'react-oidc-context'
|
||||||
@@ -63,6 +62,9 @@ export function Sidebar() {
|
|||||||
// sidebar'а — overkill. Skip badge.
|
// sidebar'а — overkill. Skip badge.
|
||||||
const reviewsCount = 0
|
const reviewsCount = 0
|
||||||
|
|
||||||
|
// Flat list per redesign/compact.html — no section labels, /graph removed
|
||||||
|
// (now reachable via catalog toolbar "Граф ⇄" button). Order: Главная →
|
||||||
|
// Справочники → Мои черновики → На ревью → Outbox → Webhooks → Аудит → Поиск.
|
||||||
const sections: NavSection[] = [
|
const sections: NavSection[] = [
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
@@ -73,14 +75,6 @@ export function Sidebar() {
|
|||||||
icon: Database,
|
icon: Database,
|
||||||
badge: dictsCount > 0 ? dictsCount : undefined,
|
badge: dictsCount > 0 ? dictsCount : undefined,
|
||||||
},
|
},
|
||||||
{ to: '/search', label: t('nav.search'), icon: Search },
|
|
||||||
// Graph view — Stage 3.3 placeholder. Когда d3-force готов.
|
|
||||||
{ to: '/graph', label: t('nav.graph'), icon: GitBranch },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('nav.section.workflow'),
|
|
||||||
items: [
|
|
||||||
{
|
{
|
||||||
to: '/my-drafts',
|
to: '/my-drafts',
|
||||||
label: t('nav.myDrafts'),
|
label: t('nav.myDrafts'),
|
||||||
@@ -95,14 +89,10 @@ export function Sidebar() {
|
|||||||
badge: reviewsCount > 0 ? reviewsCount : undefined,
|
badge: reviewsCount > 0 ? reviewsCount : undefined,
|
||||||
authRequired: true,
|
authRequired: true,
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('nav.section.admin'),
|
|
||||||
items: [
|
|
||||||
{ to: '/audit', label: t('nav.audit'), icon: ScrollText, authRequired: true },
|
|
||||||
{ to: '/outbox', label: t('nav.outbox'), icon: Inbox, authRequired: true },
|
{ to: '/outbox', label: t('nav.outbox'), icon: Inbox, authRequired: true },
|
||||||
{ to: '/webhooks', label: t('nav.webhooks'), icon: Webhook, authRequired: true },
|
{ to: '/webhooks', label: t('nav.webhooks'), icon: Webhook, authRequired: true },
|
||||||
|
{ to: '/audit', label: t('nav.audit'), icon: ScrollText, authRequired: true },
|
||||||
|
{ to: '/search', label: t('nav.search'), icon: Search },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -309,6 +299,7 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () =>
|
|||||||
const myDraftsCount = myDrafts.data?.totalElements ?? 0
|
const myDraftsCount = myDrafts.data?.totalElements ?? 0
|
||||||
const reviewsCount = 0
|
const reviewsCount = 0
|
||||||
|
|
||||||
|
// Flat list per redesign — same as desktop Sidebar above.
|
||||||
const sections: NavSection[] = [
|
const sections: NavSection[] = [
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
@@ -319,13 +310,6 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () =>
|
|||||||
icon: Database,
|
icon: Database,
|
||||||
badge: dictsCount > 0 ? dictsCount : undefined,
|
badge: dictsCount > 0 ? dictsCount : undefined,
|
||||||
},
|
},
|
||||||
{ to: '/search', label: t('nav.search'), icon: Search },
|
|
||||||
{ to: '/graph', label: t('nav.graph'), icon: GitBranch },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('nav.section.workflow'),
|
|
||||||
items: [
|
|
||||||
{
|
{
|
||||||
to: '/my-drafts',
|
to: '/my-drafts',
|
||||||
label: t('nav.myDrafts'),
|
label: t('nav.myDrafts'),
|
||||||
@@ -340,14 +324,10 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () =>
|
|||||||
badge: reviewsCount > 0 ? reviewsCount : undefined,
|
badge: reviewsCount > 0 ? reviewsCount : undefined,
|
||||||
authRequired: true,
|
authRequired: true,
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('nav.section.admin'),
|
|
||||||
items: [
|
|
||||||
{ to: '/audit', label: t('nav.audit'), icon: ScrollText, authRequired: true },
|
|
||||||
{ to: '/outbox', label: t('nav.outbox'), icon: Inbox, authRequired: true },
|
{ to: '/outbox', label: t('nav.outbox'), icon: Inbox, authRequired: true },
|
||||||
{ to: '/webhooks', label: t('nav.webhooks'), icon: Webhook, authRequired: true },
|
{ to: '/webhooks', label: t('nav.webhooks'), icon: Webhook, authRequired: true },
|
||||||
|
{ to: '/audit', label: t('nav.audit'), icon: ScrollText, authRequired: true },
|
||||||
|
{ to: '/search', label: t('nav.search'), icon: Search },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -423,6 +423,8 @@ function DictionaryListTable({ rows }: { rows: DictionaryDefinition[] }) {
|
|||||||
<th scope="col" className="text-cap text-mute text-left px-3 py-2 hidden xl:table-cell">
|
<th scope="col" className="text-cap text-mute text-left px-3 py-2 hidden xl:table-cell">
|
||||||
{t('dict.col.updated', { defaultValue: 'изменён' })}
|
{t('dict.col.updated', { defaultValue: 'изменён' })}
|
||||||
</th>
|
</th>
|
||||||
|
{/* Chevron col — affordance что row кликабелен (открывает editor) */}
|
||||||
|
<th scope="col" aria-hidden className="w-8" />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -441,15 +443,24 @@ function DictRow({ d, t }: { d: DictionaryDefinition; t: TFunc }) {
|
|||||||
const refBy = useMemo(() => uniqueRefBy(refByRaw ?? []), [refByRaw])
|
const refBy = useMemo(() => uniqueRefBy(refByRaw ?? []), [refByRaw])
|
||||||
const incomingCount = refBy.length
|
const incomingCount = refBy.length
|
||||||
|
|
||||||
|
// Relative time per redesign prototype: 12мин / 2ч / 3д / 1мес.
|
||||||
|
// Stale absolute date (YYYY-MM-DD) скрывал scale (5 минут vs 3 месяца —
|
||||||
|
// в формате 05/06/2026 это одинаково "далёкая дата").
|
||||||
const updatedLabel = useMemo(() => {
|
const updatedLabel = useMemo(() => {
|
||||||
const date = new Date(d.updatedAt)
|
const date = new Date(d.updatedAt)
|
||||||
return isNaN(date.getTime())
|
if (isNaN(date.getTime())) return '—'
|
||||||
? '—'
|
const diffSec = Math.max(0, Math.floor((Date.now() - date.getTime()) / 1000))
|
||||||
: date.toLocaleDateString(undefined, {
|
if (diffSec < 60) return `${diffSec}с`
|
||||||
year: 'numeric',
|
const diffMin = Math.floor(diffSec / 60)
|
||||||
month: '2-digit',
|
if (diffMin < 60) return `${diffMin}мин`
|
||||||
day: '2-digit',
|
const diffH = Math.floor(diffMin / 60)
|
||||||
})
|
if (diffH < 24) return `${diffH}ч`
|
||||||
|
const diffD = Math.floor(diffH / 24)
|
||||||
|
if (diffD < 30) return `${diffD}д`
|
||||||
|
const diffMo = Math.floor(diffD / 30)
|
||||||
|
if (diffMo < 12) return `${diffMo}мес`
|
||||||
|
const diffY = Math.floor(diffMo / 12)
|
||||||
|
return `${diffY}г`
|
||||||
}, [d.updatedAt])
|
}, [d.updatedAt])
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
@@ -509,10 +520,14 @@ function DictRow({ d, t }: { d: DictionaryDefinition; t: TFunc }) {
|
|||||||
<td className="px-3 py-2 text-right text-mono text-mute hidden lg:table-cell">
|
<td className="px-3 py-2 text-right text-mono text-mute hidden lg:table-cell">
|
||||||
{incomingCount > 0 ? incomingCount : '—'}
|
{incomingCount > 0 ? incomingCount : '—'}
|
||||||
</td>
|
</td>
|
||||||
{/* updated */}
|
{/* updated — relative time */}
|
||||||
<td className="px-3 py-2 text-mono text-mute tabular-nums hidden xl:table-cell whitespace-nowrap">
|
<td className="px-3 py-2 text-mono text-mute tabular-nums hidden xl:table-cell whitespace-nowrap">
|
||||||
{updatedLabel}
|
{updatedLabel}
|
||||||
</td>
|
</td>
|
||||||
|
{/* Chevron — кликабельность row affordance */}
|
||||||
|
<td aria-hidden className="px-2 py-2 text-mute text-mono text-right">
|
||||||
|
›
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user