@striae-org/striae 5.4.5 → 5.5.1
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/app/components/actions/export-audit-pdf.ts +438 -0
- package/app/components/audit/user-audit-viewer.tsx +155 -97
- package/app/components/audit/user-audit.module.css +17 -0
- package/app/components/audit/viewer/audit-viewer-header.tsx +16 -0
- package/app/components/sidebar/notes/class-details/class-details-modal.tsx +3 -3
- package/app/components/sidebar/notes/notes-editor-form.tsx +244 -12
- package/app/components/sidebar/notes/notes-editor-modal.tsx +181 -2
- package/app/components/sidebar/notes/notes.module.css +84 -1
- package/app/components/user/user.module.css +1 -1
- package/package.json +8 -8
- package/scripts/update-markdown-versions.cjs +58 -1
- package/workers/audit-worker/package.json +17 -18
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/package.json +17 -18
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/package.json +17 -18
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/package.json +18 -19
- package/workers/pdf-worker/src/audit-trail-report.ts +253 -0
- package/workers/pdf-worker/src/formats/format-striae.ts +16 -14
- package/workers/pdf-worker/src/pdf-worker.example.ts +8 -0
- package/workers/pdf-worker/src/report-types.ts +15 -0
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/package.json +17 -18
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { useContext, useMemo } from 'react';
|
|
1
|
+
import { useCallback, useContext, useMemo, useState } from 'react';
|
|
2
2
|
import { AuthContext } from '~/contexts/auth.context';
|
|
3
3
|
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
4
|
+
import { exportAuditPDF } from '~/components/actions/export-audit-pdf';
|
|
5
|
+
import { Toast, type ToastType } from '~/components/toast/toast';
|
|
4
6
|
import { AuditViewerHeader } from './viewer/audit-viewer-header';
|
|
5
7
|
import { AuditUserInfoCard } from './viewer/audit-user-info-card';
|
|
6
8
|
import { AuditActivitySummary } from './viewer/audit-activity-summary';
|
|
@@ -20,6 +22,12 @@ interface UserAuditViewerProps {
|
|
|
20
22
|
|
|
21
23
|
export const UserAuditViewer = ({ isOpen, onClose, caseNumber, title }: UserAuditViewerProps) => {
|
|
22
24
|
const { user } = useContext(AuthContext);
|
|
25
|
+
const [isExportingPdf, setIsExportingPdf] = useState(false);
|
|
26
|
+
const [showToast, setShowToast] = useState(false);
|
|
27
|
+
const [toastType, setToastType] = useState<ToastType>('success');
|
|
28
|
+
const [toastMessage, setToastMessage] = useState('');
|
|
29
|
+
const [toastDuration, setToastDuration] = useState(4000);
|
|
30
|
+
|
|
23
31
|
const {
|
|
24
32
|
filterAction,
|
|
25
33
|
setFilterAction,
|
|
@@ -79,107 +87,157 @@ export const UserAuditViewer = ({ isOpen, onClose, caseNumber, title }: UserAudi
|
|
|
79
87
|
});
|
|
80
88
|
|
|
81
89
|
const userBadgeId = userData?.badgeId?.trim() || '';
|
|
90
|
+
const isCaseScopedViewer = Boolean(effectiveCaseNumber?.trim());
|
|
91
|
+
|
|
92
|
+
const handleExportPdf = useCallback(async () => {
|
|
93
|
+
if (!user || !effectiveCaseNumber || isExportingPdf) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const displayName = user.displayName?.trim() || '';
|
|
98
|
+
const [firstFromDisplayName, ...lastFromDisplayNameParts] = displayName.split(/\s+/).filter(Boolean);
|
|
99
|
+
|
|
100
|
+
await exportAuditPDF({
|
|
101
|
+
user,
|
|
102
|
+
caseNumber: effectiveCaseNumber,
|
|
103
|
+
userCompany: userData?.company,
|
|
104
|
+
userFirstName: firstFromDisplayName || userData?.firstName || '',
|
|
105
|
+
userLastName: lastFromDisplayNameParts.join(' ') || userData?.lastName || '',
|
|
106
|
+
userBadgeId,
|
|
107
|
+
setIsExportingPDF: setIsExportingPdf,
|
|
108
|
+
setToastType,
|
|
109
|
+
setToastMessage,
|
|
110
|
+
setShowToast,
|
|
111
|
+
setToastDuration
|
|
112
|
+
});
|
|
113
|
+
}, [
|
|
114
|
+
effectiveCaseNumber,
|
|
115
|
+
isExportingPdf,
|
|
116
|
+
user,
|
|
117
|
+
userBadgeId,
|
|
118
|
+
userData?.company,
|
|
119
|
+
userData?.firstName,
|
|
120
|
+
userData?.lastName
|
|
121
|
+
]);
|
|
82
122
|
|
|
83
123
|
if (!isOpen) return null;
|
|
84
124
|
|
|
85
125
|
return (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
</
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
126
|
+
<>
|
|
127
|
+
<Toast
|
|
128
|
+
message={toastMessage}
|
|
129
|
+
type={toastType}
|
|
130
|
+
isVisible={showToast}
|
|
131
|
+
onClose={() => setShowToast(false)}
|
|
132
|
+
duration={toastDuration}
|
|
133
|
+
/>
|
|
134
|
+
<div
|
|
135
|
+
className={styles.overlay}
|
|
136
|
+
aria-label="Close audit trail dialog"
|
|
137
|
+
{...overlayProps}
|
|
138
|
+
>
|
|
139
|
+
<div className={styles.modal}>
|
|
140
|
+
<AuditViewerHeader
|
|
141
|
+
title={title || (effectiveCaseNumber ? `Audit Trail - Case ${effectiveCaseNumber}` : 'My Audit Trail')}
|
|
142
|
+
onClose={requestClose}
|
|
143
|
+
onExportPdf={isCaseScopedViewer ? () => void handleExportPdf() : undefined}
|
|
144
|
+
canExportPdf={isCaseScopedViewer && auditEntries.length > 0 && !loading && !error}
|
|
145
|
+
isExportingPdf={isExportingPdf}
|
|
146
|
+
/>
|
|
147
|
+
|
|
148
|
+
<div className={styles.content}>
|
|
149
|
+
{loading && (
|
|
150
|
+
<div className={styles.loading}>
|
|
151
|
+
<div className={styles.spinner}></div>
|
|
152
|
+
<p>Loading your audit trail...this may take a while for longer time ranges</p>
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
|
|
156
|
+
{error && (
|
|
157
|
+
<div className={styles.error}>
|
|
158
|
+
<p>Error: {error}</p>
|
|
159
|
+
<button onClick={loadAuditData} className={styles.retryButton}>
|
|
160
|
+
Retry
|
|
161
|
+
</button>
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{!loading && !error && (
|
|
166
|
+
<>
|
|
167
|
+
{isArchivedReadOnlyCase && (
|
|
168
|
+
<div className={bundledAuditWarning ? styles.archivedWarning : styles.archivedNotice}>
|
|
169
|
+
<p>
|
|
170
|
+
{bundledAuditWarning || 'Viewing bundled audit trail data from this imported archived case package.'}
|
|
171
|
+
</p>
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
{/* User Information Section */}
|
|
176
|
+
{user && (
|
|
177
|
+
<AuditUserInfoCard user={user} userData={userData} userBadgeId={userBadgeId} />
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
{/* Summary Section */}
|
|
181
|
+
<AuditActivitySummary
|
|
182
|
+
caseNumber={caseNumber}
|
|
183
|
+
filterCaseNumber={filterCaseNumber}
|
|
184
|
+
dateRangeDisplay={dateRangeDisplay}
|
|
185
|
+
summary={auditSummary}
|
|
186
|
+
/>
|
|
187
|
+
|
|
188
|
+
{isCaseScopedViewer && (
|
|
189
|
+
<div className={styles.exportScopeNote}>
|
|
190
|
+
Export PDF always includes full case history from case creation through now, regardless of current filters.
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
193
|
+
|
|
194
|
+
{/* Filters */}
|
|
195
|
+
<AuditFiltersPanel
|
|
196
|
+
dateRange={dateRange}
|
|
197
|
+
customStartDate={customStartDate}
|
|
198
|
+
customEndDate={customEndDate}
|
|
199
|
+
customStartDateInput={customStartDateInput}
|
|
200
|
+
customEndDateInput={customEndDateInput}
|
|
201
|
+
caseNumber={caseNumber}
|
|
202
|
+
filterCaseNumber={filterCaseNumber}
|
|
203
|
+
caseNumberInput={caseNumberInput}
|
|
204
|
+
filterBadgeId={filterBadgeId}
|
|
205
|
+
badgeIdInput={badgeIdInput}
|
|
206
|
+
filterAction={filterAction}
|
|
207
|
+
filterResult={filterResult}
|
|
208
|
+
onDateRangeChange={handleDateRangeChange}
|
|
209
|
+
onCustomStartDateInputChange={setCustomStartDateInput}
|
|
210
|
+
onCustomEndDateInputChange={setCustomEndDateInput}
|
|
211
|
+
onApplyCustomDateRange={handleApplyCustomDateRange}
|
|
212
|
+
onClearCustomDateRange={handleClearCustomDateRange}
|
|
213
|
+
onCaseNumberInputChange={setCaseNumberInput}
|
|
214
|
+
onApplyCaseFilter={handleApplyCaseFilter}
|
|
215
|
+
onClearCaseFilter={handleClearCaseFilter}
|
|
216
|
+
onBadgeIdInputChange={setBadgeIdInput}
|
|
217
|
+
onApplyBadgeFilter={handleApplyBadgeFilter}
|
|
218
|
+
onClearBadgeFilter={handleClearBadgeFilter}
|
|
219
|
+
onFilterActionChange={setFilterAction}
|
|
220
|
+
onFilterResultChange={setFilterResult}
|
|
221
|
+
/>
|
|
222
|
+
|
|
223
|
+
{/* Entries List */}
|
|
224
|
+
<AuditEntriesList entries={filteredEntries} />
|
|
225
|
+
|
|
226
|
+
</>
|
|
227
|
+
)}
|
|
228
|
+
|
|
229
|
+
{auditEntries.length === 0 && !loading && !error && (
|
|
230
|
+
<div className={styles.noData}>
|
|
231
|
+
<p>
|
|
232
|
+
{isArchivedReadOnlyCase
|
|
233
|
+
? 'No bundled audit trail entries are available for this imported archived case.'
|
|
234
|
+
: 'No audit trail available. Your activities will appear here as you use Striae.'}
|
|
235
|
+
</p>
|
|
236
|
+
</div>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
181
239
|
</div>
|
|
182
240
|
</div>
|
|
183
|
-
|
|
241
|
+
</>
|
|
184
242
|
);
|
|
185
243
|
};
|
|
@@ -72,6 +72,12 @@
|
|
|
72
72
|
transform: translateY(0);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
.exportButton:disabled {
|
|
76
|
+
opacity: 0.65;
|
|
77
|
+
cursor: not-allowed;
|
|
78
|
+
box-shadow: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
75
81
|
.title {
|
|
76
82
|
margin: 0;
|
|
77
83
|
color: var(--textTitle);
|
|
@@ -178,6 +184,17 @@
|
|
|
178
184
|
border: 1px solid color-mix(in lab, var(--textLight) 20%, transparent);
|
|
179
185
|
}
|
|
180
186
|
|
|
187
|
+
.exportScopeNote {
|
|
188
|
+
margin: -12px 0 16px;
|
|
189
|
+
padding: 10px 12px;
|
|
190
|
+
border-radius: 6px;
|
|
191
|
+
border: 1px solid color-mix(in lab, var(--primary) 30%, transparent);
|
|
192
|
+
background: color-mix(in lab, var(--primary) 10%, transparent);
|
|
193
|
+
color: var(--textBody);
|
|
194
|
+
font-size: 0.85rem;
|
|
195
|
+
line-height: 1.45;
|
|
196
|
+
}
|
|
197
|
+
|
|
181
198
|
/* Common heading styles */
|
|
182
199
|
.summary h3,
|
|
183
200
|
.entriesList h3 {
|
|
@@ -3,16 +3,32 @@ import styles from '../user-audit.module.css';
|
|
|
3
3
|
interface AuditViewerHeaderProps {
|
|
4
4
|
title: string;
|
|
5
5
|
onClose: () => void;
|
|
6
|
+
onExportPdf?: () => void;
|
|
7
|
+
canExportPdf?: boolean;
|
|
8
|
+
isExportingPdf?: boolean;
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
export const AuditViewerHeader = ({
|
|
9
12
|
title,
|
|
10
13
|
onClose,
|
|
14
|
+
onExportPdf,
|
|
15
|
+
canExportPdf = false,
|
|
16
|
+
isExportingPdf = false,
|
|
11
17
|
}: AuditViewerHeaderProps) => {
|
|
12
18
|
return (
|
|
13
19
|
<div className={styles.header}>
|
|
14
20
|
<h2 className={styles.title}>{title}</h2>
|
|
15
21
|
<div className={styles.headerActions}>
|
|
22
|
+
{onExportPdf && (
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
className={styles.exportButton}
|
|
26
|
+
onClick={onExportPdf}
|
|
27
|
+
disabled={!canExportPdf || isExportingPdf}
|
|
28
|
+
>
|
|
29
|
+
{isExportingPdf ? 'Exporting PDF...' : 'Export PDF'}
|
|
30
|
+
</button>
|
|
31
|
+
)}
|
|
16
32
|
<button className={styles.closeButton} onClick={onClose}>
|
|
17
33
|
×
|
|
18
34
|
</button>
|
|
@@ -118,10 +118,10 @@ const ClassDetailsModalContent = ({
|
|
|
118
118
|
/>
|
|
119
119
|
)}
|
|
120
120
|
</div>
|
|
121
|
-
<div className={styles.modalButtons}>
|
|
121
|
+
<div className={`${styles.modalButtons} ${styles.classDetailsModalButtons}`}>
|
|
122
122
|
<button
|
|
123
123
|
onClick={handleSave}
|
|
124
|
-
className={styles.saveButton}
|
|
124
|
+
className={`${styles.saveButton} ${styles.classDetailsModalAction}`}
|
|
125
125
|
disabled={isSaving || isReadOnly}
|
|
126
126
|
aria-busy={isSaving}
|
|
127
127
|
>
|
|
@@ -129,7 +129,7 @@ const ClassDetailsModalContent = ({
|
|
|
129
129
|
</button>
|
|
130
130
|
<button
|
|
131
131
|
onClick={requestClose}
|
|
132
|
-
className={styles.cancelButton}
|
|
132
|
+
className={`${styles.cancelButton} ${styles.classDetailsModalAction}`}
|
|
133
133
|
disabled={isSaving}
|
|
134
134
|
>
|
|
135
135
|
Cancel
|