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,
|
||||
ScrollText,
|
||||
Search,
|
||||
GitBranch,
|
||||
type LucideIcon,
|
||||
} from 'lucide-react'
|
||||
import { useAuth } from 'react-oidc-context'
|
||||
@@ -63,6 +62,9 @@ export function Sidebar() {
|
||||
// sidebar'а — overkill. Skip badge.
|
||||
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[] = [
|
||||
{
|
||||
items: [
|
||||
@@ -73,14 +75,6 @@ export function Sidebar() {
|
||||
icon: Database,
|
||||
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',
|
||||
label: t('nav.myDrafts'),
|
||||
@@ -95,14 +89,10 @@ export function Sidebar() {
|
||||
badge: reviewsCount > 0 ? reviewsCount : undefined,
|
||||
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: '/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 reviewsCount = 0
|
||||
|
||||
// Flat list per redesign — same as desktop Sidebar above.
|
||||
const sections: NavSection[] = [
|
||||
{
|
||||
items: [
|
||||
@@ -319,13 +310,6 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () =>
|
||||
icon: Database,
|
||||
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',
|
||||
label: t('nav.myDrafts'),
|
||||
@@ -340,14 +324,10 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () =>
|
||||
badge: reviewsCount > 0 ? reviewsCount : undefined,
|
||||
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: '/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">
|
||||
{t('dict.col.updated', { defaultValue: 'изменён' })}
|
||||
</th>
|
||||
{/* Chevron col — affordance что row кликабелен (открывает editor) */}
|
||||
<th scope="col" aria-hidden className="w-8" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -441,15 +443,24 @@ function DictRow({ d, t }: { d: DictionaryDefinition; t: TFunc }) {
|
||||
const refBy = useMemo(() => uniqueRefBy(refByRaw ?? []), [refByRaw])
|
||||
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 date = new Date(d.updatedAt)
|
||||
return isNaN(date.getTime())
|
||||
? '—'
|
||||
: date.toLocaleDateString(undefined, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})
|
||||
if (isNaN(date.getTime())) return '—'
|
||||
const diffSec = Math.max(0, Math.floor((Date.now() - date.getTime()) / 1000))
|
||||
if (diffSec < 60) return `${diffSec}с`
|
||||
const diffMin = Math.floor(diffSec / 60)
|
||||
if (diffMin < 60) return `${diffMin}мин`
|
||||
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])
|
||||
|
||||
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">
|
||||
{incomingCount > 0 ? incomingCount : '—'}
|
||||
</td>
|
||||
{/* updated */}
|
||||
{/* updated — relative time */}
|
||||
<td className="px-3 py-2 text-mono text-mute tabular-nums hidden xl:table-cell whitespace-nowrap">
|
||||
{updatedLabel}
|
||||
</td>
|
||||
{/* Chevron — кликабельность row affordance */}
|
||||
<td aria-hidden className="px-2 py-2 text-mute text-mono text-right">
|
||||
›
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user