@byline/host-tanstack-start 3.9.0 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { useTranslation } from '@byline/i18n/react'
10
+ import { Container, Section, Table } from '@byline/ui/react'
11
+ import cx from 'classnames'
12
+
13
+ import styles from './document-history.module.css'
14
+
15
+ /**
16
+ * One serialised audit-log entry as it reaches the admin UI. Mirrors
17
+ * `@byline/client`'s `AuditLogEntry` but with `occurredAt` as an ISO string —
18
+ * the value crosses the TanStack server-fn boundary through `serialise()`,
19
+ * which turns Dates into strings (docs/AUDIT.md — Workstream 3).
20
+ */
21
+ export interface AuditLogEntryView {
22
+ id: string
23
+ documentId: string | null
24
+ collectionId: string | null
25
+ actorId: string | null
26
+ actorRealm: string
27
+ action: string
28
+ field: string | null
29
+ before: unknown
30
+ after: unknown
31
+ occurredAt: string
32
+ }
33
+
34
+ /**
35
+ * The document-history payload attached to the history loader: the page of
36
+ * audit entries plus the admin-side resolved actor labels (`actors`, keyed by
37
+ * actor id — ids absent from the map belong to deleted users; system/tooling
38
+ * rows carry a NULL actorId).
39
+ */
40
+ export interface DocumentHistoryData {
41
+ entries: AuditLogEntryView[]
42
+ meta: { total: number; page: number; pageSize: number; totalPages: number }
43
+ actors?: Record<string, { label: string }>
44
+ }
45
+
46
+ /**
47
+ * Maps namespaced audit `action` values to their i18n label keys. Unknown
48
+ * actions fall back to the raw value rather than a missing-key warning.
49
+ */
50
+ const ACTION_KEYS: Record<string, string> = {
51
+ 'document.path.changed': 'collections.documentHistory.actionPathChanged',
52
+ 'document.locales.changed': 'collections.documentHistory.actionLocalesChanged',
53
+ 'document.status.changed': 'collections.documentHistory.actionStatusChanged',
54
+ 'document.deleted': 'collections.documentHistory.actionDeleted',
55
+ }
56
+
57
+ /** Render an audit before/after value inline: arrays comma-join, nullish → em-dash. */
58
+ function formatAuditValue(value: unknown): string {
59
+ if (value == null) return '—'
60
+ if (Array.isArray(value)) return value.length > 0 ? value.join(', ') : '—'
61
+ return String(value)
62
+ }
63
+
64
+ /**
65
+ * Document-grain audit log for a single document (docs/AUDIT.md — Workstream
66
+ * 3, "Document history" tab): a chronological, newest-first list of the
67
+ * non-versioned changes the version stream does not record — path /
68
+ * available-locales writes, in-place status transitions, and the deletion
69
+ * event. No diff viewer; before/after render inline.
70
+ */
71
+ export const DocumentHistoryView = ({ data }: { data: DocumentHistoryData }) => {
72
+ const { t } = useTranslation('byline-admin')
73
+ const entries = data?.entries ?? []
74
+
75
+ if (entries.length === 0) {
76
+ return (
77
+ <Section>
78
+ <Container>
79
+ <p className={cx('byline-coll-dochistory-empty', styles.empty)}>
80
+ {t('collections.documentHistory.empty')}
81
+ </p>
82
+ </Container>
83
+ </Section>
84
+ )
85
+ }
86
+
87
+ return (
88
+ <Section>
89
+ <Container>
90
+ <Table.Container className={cx('byline-coll-dochistory-table-wrap', styles.tableWrap)}>
91
+ <Table>
92
+ <Table.Header>
93
+ <Table.Row>
94
+ <Table.HeadingCell scope="col">
95
+ {t('collections.documentHistory.colWhen')}
96
+ </Table.HeadingCell>
97
+ <Table.HeadingCell scope="col">
98
+ {t('collections.documentHistory.colAction')}
99
+ </Table.HeadingCell>
100
+ <Table.HeadingCell scope="col">
101
+ {t('collections.documentHistory.colActor')}
102
+ </Table.HeadingCell>
103
+ <Table.HeadingCell scope="col">
104
+ {t('collections.documentHistory.colChange')}
105
+ </Table.HeadingCell>
106
+ </Table.Row>
107
+ </Table.Header>
108
+ <Table.Body>
109
+ {entries.map((entry) => {
110
+ const actionKey = ACTION_KEYS[entry.action]
111
+ const actionLabel = actionKey ? t(actionKey) : entry.action
112
+ // System/tooling write (NULL actor or 'system' realm) → the
113
+ // system label; an unresolved id is a deleted user; otherwise
114
+ // the admin-resolved label.
115
+ const actorLabel =
116
+ entry.actorId == null || entry.actorRealm === 'system'
117
+ ? t('collections.documentHistory.systemActor')
118
+ : (data.actors?.[entry.actorId]?.label ??
119
+ t('collections.history.audit.formerUser'))
120
+ // The deletion event carries no before/after; everything else
121
+ // renders "before → after".
122
+ const hasChange = entry.before != null || entry.after != null
123
+ return (
124
+ <Table.Row key={entry.id}>
125
+ <Table.Cell className={cx('byline-coll-dochistory-when', styles.when)}>
126
+ {new Date(entry.occurredAt).toLocaleString()}
127
+ </Table.Cell>
128
+ <Table.Cell>{actionLabel}</Table.Cell>
129
+ <Table.Cell>{actorLabel}</Table.Cell>
130
+ <Table.Cell className={cx('byline-coll-dochistory-change', styles.change)}>
131
+ {hasChange ? (
132
+ <>
133
+ <span className={cx('byline-coll-dochistory-before', styles.before)}>
134
+ {formatAuditValue(entry.before)}
135
+ </span>
136
+ <span className={cx('byline-coll-dochistory-arrow', styles.arrow)}>
137
+ {' → '}
138
+ </span>
139
+ <span className={cx('byline-coll-dochistory-after', styles.after)}>
140
+ {formatAuditValue(entry.after)}
141
+ </span>
142
+ </>
143
+ ) : (
144
+ '—'
145
+ )}
146
+ </Table.Cell>
147
+ </Table.Row>
148
+ )
149
+ })}
150
+ </Table.Body>
151
+ </Table>
152
+ </Table.Container>
153
+ </Container>
154
+ </Section>
155
+ )
156
+ }
@@ -67,6 +67,12 @@
67
67
  background-color: var(--canvas-700);
68
68
  }
69
69
 
70
+ /* Sub-view tab bar (Content versions / Document history — docs/AUDIT.md W3). */
71
+ .tabs,
72
+ :global(.byline-coll-history-tabs) {
73
+ margin-top: 0.25rem;
74
+ }
75
+
70
76
  .options,
71
77
  :global(.byline-coll-history-options) {
72
78
  display: flex;