hazo_collab_forms 5.7.0 → 5.9.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.
- package/CHANGE_LOG.md +67 -0
- package/README.md +118 -0
- package/dist/components/field_audit/auditor.d.ts +30 -0
- package/dist/components/field_audit/auditor.d.ts.map +1 -0
- package/dist/components/field_audit/auditor.js +91 -0
- package/dist/components/field_audit/auditor.js.map +1 -0
- package/dist/components/field_audit/context.d.ts +29 -0
- package/dist/components/field_audit/context.d.ts.map +1 -0
- package/dist/components/field_audit/context.js +123 -0
- package/dist/components/field_audit/context.js.map +1 -0
- package/dist/components/field_audit/field_audit_icon.d.ts +12 -0
- package/dist/components/field_audit/field_audit_icon.d.ts.map +1 -0
- package/dist/components/field_audit/field_audit_icon.js +23 -0
- package/dist/components/field_audit/field_audit_icon.js.map +1 -0
- package/dist/components/field_audit/field_audit_panel.d.ts +9 -0
- package/dist/components/field_audit/field_audit_panel.d.ts.map +1 -0
- package/dist/components/field_audit/field_audit_panel.js +54 -0
- package/dist/components/field_audit/field_audit_panel.js.map +1 -0
- package/dist/components/field_audit/index.d.ts +28 -0
- package/dist/components/field_audit/index.d.ts.map +1 -0
- package/dist/components/field_audit/index.js +24 -0
- package/dist/components/field_audit/index.js.map +1 -0
- package/dist/components/field_audit/types.d.ts +75 -0
- package/dist/components/field_audit/types.d.ts.map +1 -0
- package/dist/components/field_audit/types.js +10 -0
- package/dist/components/field_audit/types.js.map +1 -0
- package/dist/components/field_audit/with_field_audit.d.ts +32 -0
- package/dist/components/field_audit/with_field_audit.d.ts.map +1 -0
- package/dist/components/field_audit/with_field_audit.js +42 -0
- package/dist/components/field_audit/with_field_audit.js.map +1 -0
- package/dist/components/hazo_collab_form_checkbox.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_checkbox.js +3 -1
- package/dist/components/hazo_collab_form_checkbox.js.map +1 -1
- package/dist/components/hazo_collab_form_doc.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_doc.js +4 -1
- package/dist/components/hazo_collab_form_doc.js.map +1 -1
- package/dist/components/hazo_collab_form_radio.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_radio.js +4 -2
- package/dist/components/hazo_collab_form_radio.js.map +1 -1
- package/dist/components/hazo_collab_form_view/context.d.ts +7 -0
- package/dist/components/hazo_collab_form_view/context.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_view/context.js +38 -0
- package/dist/components/hazo_collab_form_view/context.js.map +1 -1
- package/dist/components/hazo_collab_form_view/index.d.ts +1 -1
- package/dist/components/hazo_collab_form_view/index.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_view/index.js +13 -1
- package/dist/components/hazo_collab_form_view/index.js.map +1 -1
- package/dist/components/hazo_collab_form_view/types.d.ts +98 -0
- package/dist/components/hazo_collab_form_view/types.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_view/views/approval_view.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_view/views/approval_view.js +3 -2
- package/dist/components/hazo_collab_form_view/views/approval_view.js.map +1 -1
- package/dist/components/hazo_collab_form_view/views/print_view.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_view/views/print_view.js +3 -2
- package/dist/components/hazo_collab_form_view/views/print_view.js.map +1 -1
- package/dist/components/hazo_collab_form_view/views/summary_view.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_view/views/summary_view.js +3 -2
- package/dist/components/hazo_collab_form_view/views/summary_view.js.map +1 -1
- package/dist/components/hazo_data_form/group_renderer.d.ts +6 -2
- package/dist/components/hazo_data_form/group_renderer.d.ts.map +1 -1
- package/dist/components/hazo_data_form/group_renderer.js +3 -3
- package/dist/components/hazo_data_form/group_renderer.js.map +1 -1
- package/dist/components/hazo_data_form/hazo_data_form.d.ts +2 -1
- package/dist/components/hazo_data_form/hazo_data_form.d.ts.map +1 -1
- package/dist/components/hazo_data_form/hazo_data_form.js +13 -4
- package/dist/components/hazo_data_form/hazo_data_form.js.map +1 -1
- package/dist/components/hazo_data_form/section_renderer.d.ts +3 -2
- package/dist/components/hazo_data_form/section_renderer.d.ts.map +1 -1
- package/dist/components/hazo_data_form/section_renderer.js +2 -2
- package/dist/components/hazo_data_form/section_renderer.js.map +1 -1
- package/dist/components/hazo_data_form/shared/data_form_field_layout.d.ts +1 -1
- package/dist/components/hazo_data_form/shared/data_form_field_layout.d.ts.map +1 -1
- package/dist/components/hazo_data_form/shared/data_form_field_layout.js +19 -8
- package/dist/components/hazo_data_form/shared/data_form_field_layout.js.map +1 -1
- package/dist/components/hazo_data_form/types.d.ts +31 -1
- package/dist/components/hazo_data_form/types.d.ts.map +1 -1
- package/dist/components/index.d.ts +5 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/shared/base_field_layout.d.ts.map +1 -1
- package/dist/components/shared/base_field_layout.js +5 -1
- package/dist/components/shared/base_field_layout.js.map +1 -1
- package/dist/components/shared/field_action_slot.d.ts +22 -0
- package/dist/components/shared/field_action_slot.d.ts.map +1 -0
- package/dist/components/shared/field_action_slot.js +20 -0
- package/dist/components/shared/field_action_slot.js.map +1 -0
- package/dist/components/shared/use_field_action_slot.d.ts +32 -0
- package/dist/components/shared/use_field_action_slot.d.ts.map +1 -0
- package/dist/components/shared/use_field_action_slot.js +52 -0
- package/dist/components/shared/use_field_action_slot.js.map +1 -0
- package/dist/components/thread_form/components/key_info_drawer.d.ts +7 -1
- package/dist/components/thread_form/components/key_info_drawer.d.ts.map +1 -1
- package/dist/components/thread_form/components/key_info_drawer.js +2 -2
- package/dist/components/thread_form/components/key_info_drawer.js.map +1 -1
- package/dist/components/thread_form/index.d.ts +1 -1
- package/dist/components/thread_form/index.d.ts.map +1 -1
- package/dist/components/thread_form/index.js.map +1 -1
- package/dist/components/thread_form/thread_form.d.ts.map +1 -1
- package/dist/components/thread_form/thread_form.js +2 -2
- package/dist/components/thread_form/thread_form.js.map +1 -1
- package/dist/components/thread_form/types.d.ts +29 -1
- package/dist/components/thread_form/types.d.ts.map +1 -1
- package/dist/components/thread_form/types.js.map +1 -1
- package/package.json +1 -1
package/CHANGE_LOG.md
CHANGED
|
@@ -7,6 +7,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## 5.9.0
|
|
11
|
+
|
|
12
|
+
- **NEW**: Built-in `FieldAudit` per-field change history.
|
|
13
|
+
- New `enable_field_audit?: boolean` convenience flag on
|
|
14
|
+
`HazoCollabFormView`, `HazoDataForm` (collab variant), and `ThreadForm`
|
|
15
|
+
(forwarded to the Key-Info drawer's embedded HazoCollabFormView).
|
|
16
|
+
When `true`, the form mounts a `FieldAuditProvider`, auto-records each
|
|
17
|
+
`form_data` change as a `manual` entry, and overlays an inline clock
|
|
18
|
+
icon (`row_end`) + history panel (`row_below`) into every scalar field
|
|
19
|
+
row. Consumer-supplied `field_action_components` win on collision.
|
|
20
|
+
- **Per-field debounce (default 750ms).** Controlled inputs fire on
|
|
21
|
+
every keystroke; the watcher coalesces a typing burst into ONE entry
|
|
22
|
+
whose `prev_value` is the value at the start of the burst and
|
|
23
|
+
`new_value` is the value when typing stopped. Reverting to the
|
|
24
|
+
baseline value within the window cancels the pending entry.
|
|
25
|
+
Tune via `audit_debounce_ms?: number` on each form view.
|
|
26
|
+
- Imperative API for non-manual sources: `useFieldAudit().record_change(
|
|
27
|
+
{ field_id, source: 'clarification' | 'manual', new_value, prev_value, user })`.
|
|
28
|
+
When `record_change` is called within ~500ms of an auto-recorded
|
|
29
|
+
`manual` entry for the same value, the existing entry is promoted to
|
|
30
|
+
the more specific source instead of duplicated.
|
|
31
|
+
- In-session storage only — no persistence on reload. Pair with a
|
|
32
|
+
backend write inside `on_field_change` if you need durability.
|
|
33
|
+
- Intended for the tax-agent surface; omit the flag on the client
|
|
34
|
+
portal to hide audit history.
|
|
35
|
+
- New exports from the package root:
|
|
36
|
+
`FieldAuditProvider`, `FieldAuditIcon`, `FieldAuditPanel`,
|
|
37
|
+
`FormDataAuditor`, `useFieldAudit`, `useFieldAuditOptional`,
|
|
38
|
+
plus the `AuditEntry`, `AuditSource`, `RecordChangeInput`,
|
|
39
|
+
`FieldAuditContextValue` types.
|
|
40
|
+
- **NEW**: `field_action_components?: FieldActionComponents` and
|
|
41
|
+
`enable_field_audit?: boolean` on `ThreadFormProps`. Both forward
|
|
42
|
+
through to the embedded Key-Info `HazoCollabFormView`. Without the
|
|
43
|
+
Key-Info drawer (`key_info_sections` omitted), neither prop has any
|
|
44
|
+
effect.
|
|
45
|
+
- Backward-compatible: when neither flag is set, the rendered output is
|
|
46
|
+
byte-identical to 5.8.0.
|
|
47
|
+
|
|
48
|
+
## 5.8.0
|
|
49
|
+
|
|
50
|
+
- **NEW**: `field_action_components?: FieldActionComponents` prop on
|
|
51
|
+
`HazoCollabFormView` and `HazoDataForm`. Lets consumers render per-field
|
|
52
|
+
React components (audit icon, RFI button, custom popover, inline audit
|
|
53
|
+
log) next to every scalar field row.
|
|
54
|
+
- **Two slot positions, independently or together:**
|
|
55
|
+
- `row_end` — inline at the end of the row, after the value column,
|
|
56
|
+
file column, and `UnifiedFieldControls`.
|
|
57
|
+
- `row_below` — full-width block immediately below the row, before the
|
|
58
|
+
next field begins. Use for inline audit logs, validation
|
|
59
|
+
explanations, expandable detail panels.
|
|
60
|
+
- Each slot receives a `FieldActionContext` with `field`, `value`, `mode`,
|
|
61
|
+
`section_id`, and `group_id`. `mode` is widened to
|
|
62
|
+
`ViewMode | 'edit' | 'view'` so the same component handles both forms.
|
|
63
|
+
- Fires in every view mode of `HazoCollabFormView`
|
|
64
|
+
(`edit | summary | print | approval`) and both modes of `HazoDataForm`
|
|
65
|
+
(`edit | view`).
|
|
66
|
+
- Scalar fields only — inputbox, textarea, currency, percentage,
|
|
67
|
+
computed, date, combo, radio, checkbox, masked, static text, summary
|
|
68
|
+
row, and `HazoCollabFormDoc`. Group header rows, data table fields,
|
|
69
|
+
per-cell positions inside tables, and `HazoCollabFormSplitAmount`
|
|
70
|
+
do not fire the slot in this release. The consumer can return `null`
|
|
71
|
+
from either slot to opt out per-field.
|
|
72
|
+
- New helper `<FieldActionSlot />` exported from the package root —
|
|
73
|
+
consumers normally don't import it directly; it's the internal renderer.
|
|
74
|
+
- Backward-compatible: when the prop is omitted the rendered DOM is
|
|
75
|
+
byte-identical to 5.7.0.
|
|
76
|
+
|
|
10
77
|
## 5.7.0
|
|
11
78
|
|
|
12
79
|
- **NEW**: `AutofillDropzone` accepts an optional `upload_manager?: ExternalUploadManager` prop. When set, file drops are delegated to the external manager instead of running the inline `upload → extract → conflict` pipeline. `on_autofill_complete` is not called in this mode; the consuming app handles merging via its own `job:completed` subscription. The `OverwriteConfirmDialog` is not rendered in this mode.
|
package/README.md
CHANGED
|
@@ -790,6 +790,124 @@ const normalized = normalize_field_metadata_input(input);
|
|
|
790
790
|
// Result: { visibility: 'hidden', data_ok: 'ok', locked: true }
|
|
791
791
|
```
|
|
792
792
|
|
|
793
|
+
### Per-field action slots (`field_action_components`)
|
|
794
|
+
|
|
795
|
+
Render consumer-owned components inline next to every scalar field row. Use
|
|
796
|
+
this for audit-history icons, RFI buttons, validation explanations, or any
|
|
797
|
+
per-field detail the form itself does not own.
|
|
798
|
+
|
|
799
|
+
Available on both `HazoCollabFormView` (all four modes — `edit | summary |
|
|
800
|
+
print | approval`) and `HazoDataForm` (`edit | view`).
|
|
801
|
+
|
|
802
|
+
Two slot positions, used independently or together:
|
|
803
|
+
|
|
804
|
+
| Slot | Position |
|
|
805
|
+
|---|---|
|
|
806
|
+
| `row_end` | Inline at the end of the row, after the field's value column, file column, and `UnifiedFieldControls`. Right place for a small icon or pill. |
|
|
807
|
+
| `row_below` | Full-width block, immediately below the row, before the next field starts. Right place for an inline audit log, validation panel, or expandable detail. |
|
|
808
|
+
|
|
809
|
+
```typescript
|
|
810
|
+
'use client';
|
|
811
|
+
|
|
812
|
+
import { HazoCollabFormView, type FieldActionContext } from 'hazo_collab_forms';
|
|
813
|
+
import { FieldAuditIcon } from '@/lib/audit/ui/FieldAuditIcon';
|
|
814
|
+
import { FieldAuditHistory } from '@/lib/audit/ui/FieldAuditHistory';
|
|
815
|
+
|
|
816
|
+
const field_actions = {
|
|
817
|
+
// Clock icon at the end of every row
|
|
818
|
+
row_end: ({ field }: FieldActionContext) => (
|
|
819
|
+
<FieldAuditIcon
|
|
820
|
+
subjectKind="client_folder"
|
|
821
|
+
subjectId={folderId}
|
|
822
|
+
fieldPath={field.id}
|
|
823
|
+
/>
|
|
824
|
+
),
|
|
825
|
+
// Inline history panel below every row (returns null when there's no
|
|
826
|
+
// history for the given field)
|
|
827
|
+
row_below: ({ field }: FieldActionContext) => (
|
|
828
|
+
<FieldAuditHistory
|
|
829
|
+
subjectKind="client_folder"
|
|
830
|
+
subjectId={folderId}
|
|
831
|
+
fieldPath={field.id}
|
|
832
|
+
/>
|
|
833
|
+
),
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
<HazoCollabFormView
|
|
837
|
+
mode="edit"
|
|
838
|
+
sections={sections}
|
|
839
|
+
form_data={form_data}
|
|
840
|
+
field_action_components={field_actions}
|
|
841
|
+
/>
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
The `FieldActionContext` passed to `row_end` contains:
|
|
845
|
+
|
|
846
|
+
| Field | Type | Notes |
|
|
847
|
+
|---|---|---|
|
|
848
|
+
| `field` | `ViewFieldConfig` | The same field config rendered in the row. |
|
|
849
|
+
| `value` | `unknown` | The current `form_data[field.id]`. |
|
|
850
|
+
| `mode` | `ViewMode \| 'edit' \| 'view'` | Active render mode. |
|
|
851
|
+
| `section_id` | `string?` | `section.section_name` when available. |
|
|
852
|
+
| `group_id` | `string?` | Id of the nearest enclosing group, when available. |
|
|
853
|
+
|
|
854
|
+
Return `null` from `row_end` to render nothing for a given field. When the
|
|
855
|
+
prop is omitted entirely the rendered DOM is byte-identical to the previous
|
|
856
|
+
version.
|
|
857
|
+
|
|
858
|
+
**Coverage note:** the slot fires for scalar fields only — inputbox,
|
|
859
|
+
textarea, currency, percentage, computed, date, combo, radio, checkbox,
|
|
860
|
+
masked, static text, summary row, and `HazoCollabFormDoc`. Group header
|
|
861
|
+
rows, data table fields (`HazoCollabFormDataTable` / `table`), per-cell
|
|
862
|
+
positions inside tables, and `HazoCollabFormSplitAmount` are not slotted in
|
|
863
|
+
this release. `HazoFbForm` is out of scope for this prop. `ThreadForm`
|
|
864
|
+
forwards both `field_action_components` and `enable_field_audit` to its
|
|
865
|
+
embedded Key-Info `HazoCollabFormView` since v5.9.0.
|
|
866
|
+
|
|
867
|
+
### Built-in FieldAudit (`enable_field_audit`)
|
|
868
|
+
|
|
869
|
+
Since v5.9.0, the package ships a ready-made audit history feature you can
|
|
870
|
+
turn on with a single flag.
|
|
871
|
+
|
|
872
|
+
```typescript
|
|
873
|
+
<HazoCollabFormView
|
|
874
|
+
mode="edit"
|
|
875
|
+
sections={sections}
|
|
876
|
+
form_data={form_data}
|
|
877
|
+
on_form_data_change={set_form_data}
|
|
878
|
+
current_user={current_user}
|
|
879
|
+
enable_field_audit // <-- that's the whole feature
|
|
880
|
+
/>
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
When `true`:
|
|
884
|
+
|
|
885
|
+
- a `FieldAuditProvider` is mounted around the form,
|
|
886
|
+
- a `FormDataAuditor` auto-records each value change as a `source: 'manual'`
|
|
887
|
+
entry, **debounced per field at 750ms by default** (tune with
|
|
888
|
+
`audit_debounce_ms`) so a typing burst becomes one entry, not one per
|
|
889
|
+
keystroke,
|
|
890
|
+
- a small clock icon is overlaid into `row_end` and an inline history
|
|
891
|
+
panel into `row_below` for every scalar field.
|
|
892
|
+
|
|
893
|
+
Any consumer-supplied `field_action_components` wins on collision, so you
|
|
894
|
+
can mix the built-in audit with your own custom slot in the other
|
|
895
|
+
position.
|
|
896
|
+
|
|
897
|
+
For non-manual sources (e.g. a clarification flow that resolves into a
|
|
898
|
+
value update), call `useFieldAudit().record_change({ field_id,
|
|
899
|
+
source: 'clarification', new_value, prev_value, user })` from your
|
|
900
|
+
handler. If the auto-watcher then sees the same value within ~500ms it
|
|
901
|
+
upgrades its existing `manual` entry to `'clarification'` instead of
|
|
902
|
+
duplicating it.
|
|
903
|
+
|
|
904
|
+
The audit lives entirely in-session — there is no persistence on reload.
|
|
905
|
+
If you need durable audit logs, pair the flag with your own write inside
|
|
906
|
+
`on_field_change`.
|
|
907
|
+
|
|
908
|
+
**Visibility:** intended for the tax-agent surface of a workflow. Omit
|
|
909
|
+
the flag on the client portal to hide audit history.
|
|
910
|
+
|
|
793
911
|
### Confirm-required Field State (H.7)
|
|
794
912
|
|
|
795
913
|
Use `confirm_state` in `field_metadata` to mark prefilled fields that a user must review and confirm. Three states: `'prefilled'` (AI/system-filled, needs review), `'confirmed'` (user approved), `'edited'` (user changed the value).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FormDataAuditor — invisible component that diffs `form_data` between
|
|
3
|
+
* renders and records each changed field as a `manual` audit entry.
|
|
4
|
+
*
|
|
5
|
+
* Coalescing strategy: controlled inputs fire on every keystroke, which
|
|
6
|
+
* would produce one audit entry per character. Instead, we debounce per
|
|
7
|
+
* field — when a field changes we (re)start a per-field timer, and once
|
|
8
|
+
* the user has been idle for `debounce_ms` we record ONE entry whose
|
|
9
|
+
* `prev_value` is the last committed value and whose `new_value` is the
|
|
10
|
+
* latest current value. The result is one entry per typing burst.
|
|
11
|
+
*
|
|
12
|
+
* On unmount any pending timers are flushed so we don't lose unfinished
|
|
13
|
+
* edits. Internal keys prefixed with `__` (file attachments,
|
|
14
|
+
* clarification storage, etc.) are ignored.
|
|
15
|
+
*
|
|
16
|
+
* Renders nothing.
|
|
17
|
+
*/
|
|
18
|
+
export interface FormDataAuditorProps {
|
|
19
|
+
form_data: Record<string, unknown> | undefined;
|
|
20
|
+
current_user?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Milliseconds of idle time after the last change before an entry is
|
|
23
|
+
* recorded. Keystrokes within the window are coalesced into a single
|
|
24
|
+
* audit entry. Defaults to 750ms — enough for normal typing pauses but
|
|
25
|
+
* short enough that tabbing out feels immediate.
|
|
26
|
+
*/
|
|
27
|
+
debounce_ms?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function FormDataAuditor({ form_data, current_user, debounce_ms, }: FormDataAuditorProps): null;
|
|
30
|
+
//# sourceMappingURL=auditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auditor.d.ts","sourceRoot":"","sources":["../../../src/components/field_audit/auditor.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AASD,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,YAAY,EACZ,WAAiB,GAClB,EAAE,oBAAoB,GAAG,IAAI,CAgF7B"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FormDataAuditor — invisible component that diffs `form_data` between
|
|
3
|
+
* renders and records each changed field as a `manual` audit entry.
|
|
4
|
+
*
|
|
5
|
+
* Coalescing strategy: controlled inputs fire on every keystroke, which
|
|
6
|
+
* would produce one audit entry per character. Instead, we debounce per
|
|
7
|
+
* field — when a field changes we (re)start a per-field timer, and once
|
|
8
|
+
* the user has been idle for `debounce_ms` we record ONE entry whose
|
|
9
|
+
* `prev_value` is the last committed value and whose `new_value` is the
|
|
10
|
+
* latest current value. The result is one entry per typing burst.
|
|
11
|
+
*
|
|
12
|
+
* On unmount any pending timers are flushed so we don't lose unfinished
|
|
13
|
+
* edits. Internal keys prefixed with `__` (file attachments,
|
|
14
|
+
* clarification storage, etc.) are ignored.
|
|
15
|
+
*
|
|
16
|
+
* Renders nothing.
|
|
17
|
+
*/
|
|
18
|
+
'use client';
|
|
19
|
+
import { useEffect, useRef } from 'react';
|
|
20
|
+
import { useFieldAuditOptional } from './context.js';
|
|
21
|
+
export function FormDataAuditor({ form_data, current_user, debounce_ms = 750, }) {
|
|
22
|
+
const audit = useFieldAuditOptional();
|
|
23
|
+
// Last committed values — what we'll use as `prev_value` when the next
|
|
24
|
+
// debounced entry fires. Starts at the initial form_data so we don't
|
|
25
|
+
// emit a synthetic "everything changed" entry on first mount.
|
|
26
|
+
const committed_ref = useRef({ ...(form_data ?? {}) });
|
|
27
|
+
// Live form_data, kept fresh via render so the debounced timer reads
|
|
28
|
+
// the latest value at fire time (not the value at the time we set the timer).
|
|
29
|
+
const live_ref = useRef(form_data ?? {});
|
|
30
|
+
live_ref.current = form_data ?? {};
|
|
31
|
+
// Per-field debounce state. `current_user` is captured into the closure.
|
|
32
|
+
const pending_ref = useRef(new Map());
|
|
33
|
+
// Diff each render and schedule audit entries.
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!audit)
|
|
36
|
+
return;
|
|
37
|
+
const prev = committed_ref.current;
|
|
38
|
+
const curr = form_data ?? {};
|
|
39
|
+
const pending = pending_ref.current;
|
|
40
|
+
const keys = new Set([...Object.keys(prev), ...Object.keys(curr)]);
|
|
41
|
+
for (const key of keys) {
|
|
42
|
+
if (key.startsWith('__'))
|
|
43
|
+
continue;
|
|
44
|
+
const baseline = pending.get(key)?.baseline ?? prev[key];
|
|
45
|
+
const after = curr[key];
|
|
46
|
+
// If the value is back at the baseline (e.g. user typed then erased),
|
|
47
|
+
// cancel any pending entry and forget the field.
|
|
48
|
+
if (Object.is(baseline, after)) {
|
|
49
|
+
const existing = pending.get(key);
|
|
50
|
+
if (existing) {
|
|
51
|
+
clearTimeout(existing.timer);
|
|
52
|
+
pending.delete(key);
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// Restart the per-field debounce.
|
|
57
|
+
const existing = pending.get(key);
|
|
58
|
+
if (existing) {
|
|
59
|
+
clearTimeout(existing.timer);
|
|
60
|
+
}
|
|
61
|
+
const timer = setTimeout(() => {
|
|
62
|
+
const latest = live_ref.current[key];
|
|
63
|
+
const prev_value = pending.get(key)?.baseline ?? prev[key];
|
|
64
|
+
pending.delete(key);
|
|
65
|
+
if (Object.is(prev_value, latest))
|
|
66
|
+
return;
|
|
67
|
+
audit.record_change({
|
|
68
|
+
field_id: key,
|
|
69
|
+
source: 'manual',
|
|
70
|
+
prev_value,
|
|
71
|
+
new_value: latest,
|
|
72
|
+
user: current_user,
|
|
73
|
+
});
|
|
74
|
+
committed_ref.current = { ...committed_ref.current, [key]: latest };
|
|
75
|
+
}, debounce_ms);
|
|
76
|
+
pending.set(key, { baseline, timer });
|
|
77
|
+
}
|
|
78
|
+
}, [form_data, audit, current_user, debounce_ms]);
|
|
79
|
+
// Flush any pending timers on unmount.
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
return () => {
|
|
82
|
+
const pending = pending_ref.current;
|
|
83
|
+
for (const [, entry] of pending) {
|
|
84
|
+
clearTimeout(entry.timer);
|
|
85
|
+
}
|
|
86
|
+
pending.clear();
|
|
87
|
+
};
|
|
88
|
+
}, []);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=auditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auditor.js","sourceRoot":"","sources":["../../../src/components/field_audit/auditor.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FieldAuditProvider — owns the in-session audit history.
|
|
3
|
+
*
|
|
4
|
+
* State lives in React state, so it dies with the component tree. Persistence
|
|
5
|
+
* is intentionally out of scope for this release; consuming apps that need
|
|
6
|
+
* durable audit logs can keep their own copy by subscribing to record_change
|
|
7
|
+
* via the imperative API.
|
|
8
|
+
*/
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import type { ReactNode } from 'react';
|
|
11
|
+
import type { FieldAuditContextValue } from './types.js';
|
|
12
|
+
declare const FieldAuditContext: React.Context<FieldAuditContextValue | undefined>;
|
|
13
|
+
/**
|
|
14
|
+
* Hook for consumers that *must* run inside a provider. Throws otherwise.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useFieldAudit(): FieldAuditContextValue;
|
|
17
|
+
/**
|
|
18
|
+
* Hook for components that may render with or without an audit provider.
|
|
19
|
+
* `FieldAuditIcon` and `FieldAuditPanel` use this so they can be dropped
|
|
20
|
+
* into a slot whether or not audit is enabled — when no provider is mounted
|
|
21
|
+
* they silently render nothing.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useFieldAuditOptional(): FieldAuditContextValue | undefined;
|
|
24
|
+
export interface FieldAuditProviderProps {
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
export declare function FieldAuditProvider({ children }: FieldAuditProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export { FieldAuditContext };
|
|
29
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/components/field_audit/context.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAON,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,YAAY,CAAC;AAEpB,QAAA,MAAM,iBAAiB,mDAEtB,CAAC;AAEF;;GAEG;AACH,wBAAgB,aAAa,IAAI,sBAAsB,CAMtD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,IAAI,sBAAsB,GAAG,SAAS,CAE1E;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAMD,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE,uBAAuB,2CAuGvE;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FieldAuditProvider — owns the in-session audit history.
|
|
3
|
+
*
|
|
4
|
+
* State lives in React state, so it dies with the component tree. Persistence
|
|
5
|
+
* is intentionally out of scope for this release; consuming apps that need
|
|
6
|
+
* durable audit logs can keep their own copy by subscribing to record_change
|
|
7
|
+
* via the imperative API.
|
|
8
|
+
*/
|
|
9
|
+
'use client';
|
|
10
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
+
import { createContext, useCallback, useContext, useMemo, useRef, useState, } from 'react';
|
|
12
|
+
const FieldAuditContext = createContext(undefined);
|
|
13
|
+
/**
|
|
14
|
+
* Hook for consumers that *must* run inside a provider. Throws otherwise.
|
|
15
|
+
*/
|
|
16
|
+
export function useFieldAudit() {
|
|
17
|
+
const ctx = useContext(FieldAuditContext);
|
|
18
|
+
if (!ctx) {
|
|
19
|
+
throw new Error('useFieldAudit must be used inside <FieldAuditProvider>');
|
|
20
|
+
}
|
|
21
|
+
return ctx;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Hook for components that may render with or without an audit provider.
|
|
25
|
+
* `FieldAuditIcon` and `FieldAuditPanel` use this so they can be dropped
|
|
26
|
+
* into a slot whether or not audit is enabled — when no provider is mounted
|
|
27
|
+
* they silently render nothing.
|
|
28
|
+
*/
|
|
29
|
+
export function useFieldAuditOptional() {
|
|
30
|
+
return useContext(FieldAuditContext);
|
|
31
|
+
}
|
|
32
|
+
function make_id(field_id) {
|
|
33
|
+
return `${field_id}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
34
|
+
}
|
|
35
|
+
export function FieldAuditProvider({ children }) {
|
|
36
|
+
const [entries, set_entries] = useState(() => new Map());
|
|
37
|
+
const [expanded, set_expanded] = useState(() => new Set());
|
|
38
|
+
// Mirror entries in a ref so the auto-watcher (FormDataAuditor) can read
|
|
39
|
+
// the live state without re-binding to setState and creating stale closures.
|
|
40
|
+
const entries_ref = useRef(entries);
|
|
41
|
+
entries_ref.current = entries;
|
|
42
|
+
const record_change = useCallback((input) => {
|
|
43
|
+
set_entries((prev) => {
|
|
44
|
+
const existing = prev.get(input.field_id) ?? [];
|
|
45
|
+
const last = existing[0];
|
|
46
|
+
const window_ms = input.dedup_window_ms ?? 500;
|
|
47
|
+
// Dedup against a recent entry with the same new value.
|
|
48
|
+
if (last &&
|
|
49
|
+
Object.is(last.new_value, input.new_value) &&
|
|
50
|
+
Date.now() - last.at < window_ms) {
|
|
51
|
+
// The auto-watcher fires `manual` on every form_data change; an
|
|
52
|
+
// explicit caller may classify the same change as `clarification`
|
|
53
|
+
// before/around the watcher fires. Always promote the existing
|
|
54
|
+
// entry to the more specific source.
|
|
55
|
+
if (last.source === 'manual' && input.source !== 'manual') {
|
|
56
|
+
const promoted = {
|
|
57
|
+
...last,
|
|
58
|
+
source: input.source,
|
|
59
|
+
user: input.user ?? last.user,
|
|
60
|
+
};
|
|
61
|
+
const next = new Map(prev);
|
|
62
|
+
next.set(input.field_id, [promoted, ...existing.slice(1)]);
|
|
63
|
+
return next;
|
|
64
|
+
}
|
|
65
|
+
// Otherwise treat as a duplicate and keep the existing entry.
|
|
66
|
+
return prev;
|
|
67
|
+
}
|
|
68
|
+
const entry = {
|
|
69
|
+
id: make_id(input.field_id),
|
|
70
|
+
field_id: input.field_id,
|
|
71
|
+
source: input.source,
|
|
72
|
+
at: Date.now(),
|
|
73
|
+
prev_value: input.prev_value !== undefined ? input.prev_value : last?.new_value,
|
|
74
|
+
new_value: input.new_value,
|
|
75
|
+
user: input.user,
|
|
76
|
+
};
|
|
77
|
+
const next = new Map(prev);
|
|
78
|
+
next.set(input.field_id, [entry, ...existing]);
|
|
79
|
+
return next;
|
|
80
|
+
});
|
|
81
|
+
}, []);
|
|
82
|
+
const toggle_expanded = useCallback((field_id) => {
|
|
83
|
+
set_expanded((prev) => {
|
|
84
|
+
const next = new Set(prev);
|
|
85
|
+
if (next.has(field_id))
|
|
86
|
+
next.delete(field_id);
|
|
87
|
+
else
|
|
88
|
+
next.add(field_id);
|
|
89
|
+
return next;
|
|
90
|
+
});
|
|
91
|
+
}, []);
|
|
92
|
+
const clear_field = useCallback((field_id) => {
|
|
93
|
+
set_entries((prev) => {
|
|
94
|
+
if (!prev.has(field_id))
|
|
95
|
+
return prev;
|
|
96
|
+
const next = new Map(prev);
|
|
97
|
+
next.delete(field_id);
|
|
98
|
+
return next;
|
|
99
|
+
});
|
|
100
|
+
set_expanded((prev) => {
|
|
101
|
+
if (!prev.has(field_id))
|
|
102
|
+
return prev;
|
|
103
|
+
const next = new Set(prev);
|
|
104
|
+
next.delete(field_id);
|
|
105
|
+
return next;
|
|
106
|
+
});
|
|
107
|
+
}, []);
|
|
108
|
+
const clear_all = useCallback(() => {
|
|
109
|
+
set_entries(new Map());
|
|
110
|
+
set_expanded(new Set());
|
|
111
|
+
}, []);
|
|
112
|
+
const value = useMemo(() => ({
|
|
113
|
+
entries,
|
|
114
|
+
expanded,
|
|
115
|
+
record_change,
|
|
116
|
+
toggle_expanded,
|
|
117
|
+
clear_field,
|
|
118
|
+
clear_all,
|
|
119
|
+
}), [entries, expanded, record_change, toggle_expanded, clear_field, clear_all]);
|
|
120
|
+
return (_jsx(FieldAuditContext.Provider, { value: value, children: children }));
|
|
121
|
+
}
|
|
122
|
+
export { FieldAuditContext };
|
|
123
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/components/field_audit/context.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FieldAuditIcon — designed to be dropped into the
|
|
3
|
+
* `field_action_components.row_end` slot. Renders a small clock toggle that
|
|
4
|
+
* opens/closes the audit history panel for the field.
|
|
5
|
+
*
|
|
6
|
+
* Visible whenever the field has at least one recorded entry. Hidden when
|
|
7
|
+
* the audit provider is absent (so it can sit safely in a shared slot).
|
|
8
|
+
*/
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import type { FieldActionContext } from '../hazo_collab_form_view/types.js';
|
|
11
|
+
export declare function FieldAuditIcon({ field }: FieldActionContext): React.ReactElement | null;
|
|
12
|
+
//# sourceMappingURL=field_audit_icon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field_audit_icon.d.ts","sourceRoot":"","sources":["../../../src/components/field_audit/field_audit_icon.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAE5E,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE,kBAAkB,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAiDvF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FieldAuditIcon — designed to be dropped into the
|
|
3
|
+
* `field_action_components.row_end` slot. Renders a small clock toggle that
|
|
4
|
+
* opens/closes the audit history panel for the field.
|
|
5
|
+
*
|
|
6
|
+
* Visible whenever the field has at least one recorded entry. Hidden when
|
|
7
|
+
* the audit provider is absent (so it can sit safely in a shared slot).
|
|
8
|
+
*/
|
|
9
|
+
'use client';
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { useFieldAuditOptional } from './context.js';
|
|
12
|
+
export function FieldAuditIcon({ field }) {
|
|
13
|
+
const audit = useFieldAuditOptional();
|
|
14
|
+
if (!audit)
|
|
15
|
+
return null;
|
|
16
|
+
const list = audit.entries.get(field.id) ?? [];
|
|
17
|
+
if (list.length === 0)
|
|
18
|
+
return null;
|
|
19
|
+
const is_open = audit.expanded.has(field.id);
|
|
20
|
+
const count = list.length;
|
|
21
|
+
return (_jsxs("button", { type: "button", "data-testid": `field_audit_icon_${field.id}`, "data-state": is_open ? 'open' : 'closed', "aria-expanded": is_open, "aria-label": `Show ${count} audit ${count === 1 ? 'entry' : 'entries'} for ${field.id}`, title: `${count} change${count === 1 ? '' : 's'} recorded`, onClick: () => audit.toggle_expanded(field.id), className: "cls_field_audit_icon inline-flex items-center justify-center gap-1 h-7 px-2 rounded-md text-[11px] font-medium text-amber-700 hover:bg-amber-50 hover:text-amber-900 transition-colors", children: [_jsxs("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "h-3.5 w-3.5", children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("polyline", { points: "12 7 12 12 15 14" })] }), _jsx("span", { className: "font-mono", children: count }), _jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: `h-3 w-3 transition-transform ${is_open ? 'rotate-180' : ''}`, children: _jsx("polyline", { points: "6 9 12 15 18 9" }) })] }));
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=field_audit_icon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field_audit_icon.js","sourceRoot":"","sources":["../../../src/components/field_audit/field_audit_icon.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,YAAY,CAAC;;AAGb,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAGrD,MAAM,UAAU,cAAc,CAAC,EAAE,KAAK,EAAsB;IAC1D,MAAM,KAAK,GAAG,qBAAqB,EAAE,CAAC;IACtC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;IAC/C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;IAE1B,OAAO,CACL,kBACE,IAAI,EAAC,QAAQ,iBACA,oBAAoB,KAAK,CAAC,EAAE,EAAE,gBAC/B,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,mBACxB,OAAO,gBACV,QAAQ,KAAK,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,QAAQ,KAAK,CAAC,EAAE,EAAE,EACtF,KAAK,EAAE,GAAG,KAAK,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,WAAW,EAC1D,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,EAC9C,SAAS,EAAC,wLAAwL,aAElM,8BACc,MAAM,EAClB,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,SAAS,EAAC,aAAa,aAEvB,iBAAQ,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,CAAC,EAAC,GAAG,GAAG,EAChC,mBAAU,MAAM,EAAC,kBAAkB,GAAG,IAClC,EACN,eAAM,SAAS,EAAC,WAAW,YAAE,KAAK,GAAQ,EAC1C,6BACc,MAAM,EAClB,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,SAAS,EAAE,gCAAgC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,YAExE,mBAAU,MAAM,EAAC,gBAAgB,GAAG,GAChC,IACC,CACV,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FieldAuditPanel — designed to be dropped into the
|
|
3
|
+
* `field_action_components.row_below` slot. Renders the per-field history
|
|
4
|
+
* list when the icon is toggled open.
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import type { FieldActionContext } from '../hazo_collab_form_view/types.js';
|
|
8
|
+
export declare function FieldAuditPanel({ field }: FieldActionContext): React.ReactElement | null;
|
|
9
|
+
//# sourceMappingURL=field_audit_panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field_audit_panel.d.ts","sourceRoot":"","sources":["../../../src/components/field_audit/field_audit_panel.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAsD5E,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,EAAE,kBAAkB,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAiCxF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FieldAuditPanel — designed to be dropped into the
|
|
3
|
+
* `field_action_components.row_below` slot. Renders the per-field history
|
|
4
|
+
* list when the icon is toggled open.
|
|
5
|
+
*/
|
|
6
|
+
'use client';
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { useFieldAuditOptional } from './context.js';
|
|
9
|
+
function format_value(value) {
|
|
10
|
+
if (value === undefined)
|
|
11
|
+
return '—';
|
|
12
|
+
if (value === null)
|
|
13
|
+
return 'null';
|
|
14
|
+
if (typeof value === 'string')
|
|
15
|
+
return value === '' ? '""' : `"${value}"`;
|
|
16
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
17
|
+
return String(value);
|
|
18
|
+
try {
|
|
19
|
+
return JSON.stringify(value);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return String(value);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function format_time(at) {
|
|
26
|
+
const date = new Date(at);
|
|
27
|
+
const hh = String(date.getHours()).padStart(2, '0');
|
|
28
|
+
const mm = String(date.getMinutes()).padStart(2, '0');
|
|
29
|
+
const ss = String(date.getSeconds()).padStart(2, '0');
|
|
30
|
+
return `${hh}:${mm}:${ss}`;
|
|
31
|
+
}
|
|
32
|
+
function source_label(source) {
|
|
33
|
+
switch (source) {
|
|
34
|
+
case 'manual':
|
|
35
|
+
return 'edit';
|
|
36
|
+
case 'clarification':
|
|
37
|
+
return 'clarification';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function EntryRow({ entry }) {
|
|
41
|
+
return (_jsxs("li", { className: "cls_field_audit_entry flex items-baseline gap-2 px-2 py-1 text-[11px] font-mono", children: [_jsx("span", { className: "text-amber-700/70 shrink-0", children: format_time(entry.at) }), _jsx("span", { className: "text-amber-900 shrink-0 uppercase tracking-wide text-[10px]", children: source_label(entry.source) }), entry.user && (_jsx("span", { className: "text-slate-600 shrink-0", children: entry.user })), _jsxs("span", { className: "text-slate-500 truncate", children: [format_value(entry.prev_value), " ", _jsx("span", { "aria-hidden": true, children: "\u2192" }), ' ', _jsx("span", { className: "text-emerald-700", children: format_value(entry.new_value) })] })] }));
|
|
42
|
+
}
|
|
43
|
+
export function FieldAuditPanel({ field }) {
|
|
44
|
+
const audit = useFieldAuditOptional();
|
|
45
|
+
if (!audit)
|
|
46
|
+
return null;
|
|
47
|
+
if (!audit.expanded.has(field.id))
|
|
48
|
+
return null;
|
|
49
|
+
const list = audit.entries.get(field.id) ?? [];
|
|
50
|
+
if (list.length === 0)
|
|
51
|
+
return null;
|
|
52
|
+
return (_jsxs("div", { "data-testid": `field_audit_panel_${field.id}`, className: "cls_field_audit_panel mx-3 mb-2 rounded-md border border-amber-200 bg-amber-50/60", children: [_jsxs("div", { className: "flex items-center justify-between px-2 py-1 border-b border-amber-200/60", children: [_jsxs("span", { className: "text-[10px] uppercase tracking-wide text-amber-800", children: ["Field audit \u00B7 ", list.length, " change", list.length === 1 ? '' : 's'] }), _jsx("button", { type: "button", onClick: () => audit.clear_field(field.id), "aria-label": `Clear audit history for ${field.id}`, className: "text-[10px] text-amber-700/70 hover:text-red-600", children: "Clear" })] }), _jsx("ol", { className: "divide-y divide-amber-100", children: list.map((entry) => (_jsx(EntryRow, { entry: entry }, entry.id))) })] }));
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=field_audit_panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field_audit_panel.js","sourceRoot":"","sources":["../../../src/components/field_audit/field_audit_panel.tsx"],"names":[],"mappings":"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"}
|