hazo_collab_forms 1.9.1 → 1.9.2
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 +25 -0
- package/README.md +153 -40
- package/dist/components/_internal_form_set.d.ts +13 -16
- package/dist/components/_internal_form_set.d.ts.map +1 -1
- package/dist/components/_internal_form_set.js +18 -2
- package/dist/components/_internal_form_set.js.map +1 -1
- package/dist/components/collab_form_file_upload.d.ts +38 -71
- package/dist/components/collab_form_file_upload.d.ts.map +1 -1
- package/dist/components/collab_form_file_upload.js +185 -280
- package/dist/components/collab_form_file_upload.js.map +1 -1
- package/dist/components/hazo_collab_form_base.d.ts +16 -6
- package/dist/components/hazo_collab_form_base.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_base.js +19 -10
- package/dist/components/hazo_collab_form_base.js.map +1 -1
- package/dist/components/hazo_collab_form_doc.d.ts +8 -11
- package/dist/components/hazo_collab_form_doc.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_doc.js +7 -5
- package/dist/components/hazo_collab_form_doc.js.map +1 -1
- package/dist/components/hazo_collab_form_group.d.ts +4 -3
- package/dist/components/hazo_collab_form_group.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_group.js +3 -3
- package/dist/components/hazo_collab_form_group.js.map +1 -1
- package/dist/components/hazo_collab_form_view/context.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_view/context.js +4 -0
- package/dist/components/hazo_collab_form_view/context.js.map +1 -1
- package/dist/components/hazo_collab_form_view/hooks/use_view_callbacks.d.ts +3 -3
- package/dist/components/hazo_collab_form_view/hooks/use_view_callbacks.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_view/hooks/use_view_callbacks.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.map +1 -1
- package/dist/components/hazo_collab_form_view/types.d.ts +14 -9
- package/dist/components/hazo_collab_form_view/types.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_view/views/edit_view.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_view/views/edit_view.js +3 -1
- package/dist/components/hazo_collab_form_view/views/edit_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 +13 -13
- package/dist/components/hazo_collab_form_view/views/summary_view.js.map +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +2 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/shared/summary_files/summary_files.d.ts +10 -5
- package/dist/components/shared/summary_files/summary_files.d.ts.map +1 -1
- package/dist/components/shared/summary_files/summary_files.js +35 -14
- package/dist/components/shared/summary_files/summary_files.js.map +1 -1
- package/dist/components/shared/summary_utils/file_helpers.d.ts +7 -2
- package/dist/components/shared/summary_utils/file_helpers.d.ts.map +1 -1
- package/dist/components/shared/summary_utils/file_helpers.js +12 -2
- package/dist/components/shared/summary_utils/file_helpers.js.map +1 -1
- package/dist/components/shared/summary_utils/index.d.ts +1 -1
- package/dist/components/shared/summary_utils/index.d.ts.map +1 -1
- package/dist/components/shared/summary_utils/index.js +1 -1
- package/dist/components/shared/summary_utils/index.js.map +1 -1
- package/dist/components/shared/use_base_form_field.d.ts +13 -10
- package/dist/components/shared/use_base_form_field.d.ts.map +1 -1
- package/dist/components/shared/use_base_form_field.js +2 -1
- package/dist/components/shared/use_base_form_field.js.map +1 -1
- package/dist/types/file_attachment.d.ts +55 -0
- package/dist/types/file_attachment.d.ts.map +1 -0
- package/dist/types/file_attachment.js +54 -0
- package/dist/types/file_attachment.js.map +1 -0
- package/dist/types/file_manager.d.ts +60 -0
- package/dist/types/file_manager.d.ts.map +1 -0
- package/dist/types/file_manager.js +8 -0
- package/dist/types/file_manager.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/use_file_status.d.ts +29 -0
- package/dist/utils/use_file_status.d.ts.map +1 -0
- package/dist/utils/use_file_status.js +68 -0
- package/dist/utils/use_file_status.js.map +1 -0
- package/package.json +4 -1
|
@@ -1,36 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* File upload component for collaboration forms
|
|
3
|
-
* Provides drag-and-drop file upload, file list display, and file management
|
|
3
|
+
* Provides drag-and-drop file upload, file list display, and file management.
|
|
4
|
+
*
|
|
5
|
+
* Uses FileManagerCallbacks for upload/remove operations and FormFileAttachment
|
|
6
|
+
* as the data shape stored in form_data.
|
|
4
7
|
*/
|
|
5
8
|
'use client';
|
|
6
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
10
|
import React, { useRef, useState, useCallback } from 'react';
|
|
8
11
|
import { cn } from '../utils/cn.js';
|
|
9
12
|
import { use_logger } from '../logger/context.js';
|
|
10
|
-
import {
|
|
11
|
-
import { HiTrash, HiPaperClip, HiPhotograph, HiDocument, HiDocumentText, HiCheckCircle, HiLockClosed, HiLockOpen } from 'react-icons/hi';
|
|
12
|
-
import { FaSpinner } from 'react-icons/fa';
|
|
13
|
+
import { HiTrash, HiPaperClip, HiPhotograph, HiDocument, HiDocumentText, HiLockClosed, HiLockOpen, HiRefresh } from 'react-icons/hi';
|
|
13
14
|
/**
|
|
14
|
-
* Get file icon based on
|
|
15
|
+
* Get file icon based on MIME type and filename
|
|
15
16
|
*/
|
|
16
|
-
function get_file_icon(
|
|
17
|
-
const extension = file_name.split('.').pop()?.toLowerCase() || '';
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
if (mime_type.startsWith('image/') || ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(extension)) {
|
|
17
|
+
function get_file_icon(mime_type, file_name) {
|
|
18
|
+
const extension = (file_name || '').split('.').pop()?.toLowerCase() || '';
|
|
19
|
+
const mime = (mime_type || '').toLowerCase();
|
|
20
|
+
if (mime.startsWith('image/') || ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(extension)) {
|
|
21
21
|
return _jsx(HiPhotograph, { className: "h-8 w-8 text-blue-500" });
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
if (mime_type === 'application/pdf' || extension === 'pdf') {
|
|
23
|
+
if (mime === 'application/pdf' || extension === 'pdf') {
|
|
25
24
|
return _jsx(HiDocument, { className: "h-8 w-8 text-red-500" });
|
|
26
25
|
}
|
|
27
|
-
|
|
28
|
-
if (mime_type.includes('document') ||
|
|
29
|
-
mime_type.includes('text') ||
|
|
30
|
-
['doc', 'docx', 'txt', 'rtf', 'odt'].includes(extension)) {
|
|
26
|
+
if (mime.includes('document') || mime.includes('text') || ['doc', 'docx', 'txt', 'rtf', 'odt'].includes(extension)) {
|
|
31
27
|
return _jsx(HiDocumentText, { className: "h-8 w-8 text-green-500" });
|
|
32
28
|
}
|
|
33
|
-
// Default
|
|
34
29
|
return _jsx(HiPaperClip, { className: "h-8 w-8 text-gray-500" });
|
|
35
30
|
}
|
|
36
31
|
/**
|
|
@@ -44,10 +39,26 @@ function format_file_size(bytes) {
|
|
|
44
39
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
45
40
|
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
46
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Get status badge for a file
|
|
44
|
+
*/
|
|
45
|
+
function StatusBadge({ status }) {
|
|
46
|
+
if (!status || status === 'active')
|
|
47
|
+
return null;
|
|
48
|
+
const config = {
|
|
49
|
+
missing: { label: 'Missing', className: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300' },
|
|
50
|
+
soft_deleted: { label: 'Deleted', className: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300' },
|
|
51
|
+
orphaned: { label: 'Orphaned', className: 'bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300' },
|
|
52
|
+
};
|
|
53
|
+
const cfg = config[status];
|
|
54
|
+
if (!cfg)
|
|
55
|
+
return null;
|
|
56
|
+
return (_jsx("span", { className: cn('text-[10px] px-1 py-0.5 rounded font-medium', cfg.className), children: cfg.label }));
|
|
57
|
+
}
|
|
47
58
|
/**
|
|
48
59
|
* Collaboration form file upload component
|
|
49
60
|
*/
|
|
50
|
-
export function CollabFormFileUpload({ field_id_final, accept_files_public = false, accept_files_private = false, files_dir, max_size, min_files, max_files = 10, file_accept,
|
|
61
|
+
export function CollabFormFileUpload({ field_id_final, accept_files_public = false, accept_files_private = false, files_dir, max_size, min_files, max_files = 10, file_accept, file_validator, file_manager, files: controlled_files, on_files_change, private_files: controlled_private_files, on_private_files_change, private_files_permission, can_access_private_files: can_access_private_files_prop, show_file_status = false, allow_reupload = false, file_statuses = {}, scope_id, uploaded_by, }) {
|
|
51
62
|
const logger = use_logger();
|
|
52
63
|
const file_input_ref = useRef(null);
|
|
53
64
|
const private_file_input_ref = useRef(null);
|
|
@@ -57,125 +68,64 @@ export function CollabFormFileUpload({ field_id_final, accept_files_public = fal
|
|
|
57
68
|
const [is_dragging_private, set_is_dragging_private] = useState(false);
|
|
58
69
|
const [upload_errors, set_upload_errors] = useState({});
|
|
59
70
|
const [uploading_files, set_uploading_files] = useState(new Set());
|
|
60
|
-
const [processing_files, set_processing_files] = useState(new Set());
|
|
61
|
-
const [processed_files, set_processed_files] = useState(new Set());
|
|
62
71
|
const [has_permission, set_has_permission] = useState(false);
|
|
63
72
|
const [permission_loading, set_permission_loading] = useState(true);
|
|
64
73
|
// Use controlled files if provided, otherwise use internal state
|
|
65
74
|
const files = controlled_files !== undefined ? controlled_files : internal_files;
|
|
66
75
|
const is_controlled = controlled_files !== undefined;
|
|
67
|
-
// Use controlled private files if provided, otherwise use internal state
|
|
68
76
|
const private_files = controlled_private_files !== undefined ? controlled_private_files : internal_private_files;
|
|
69
77
|
const is_private_controlled = controlled_private_files !== undefined;
|
|
70
|
-
// Check permission
|
|
78
|
+
// Check permission for private files
|
|
71
79
|
React.useEffect(() => {
|
|
72
80
|
if (!accept_files_private) {
|
|
73
81
|
set_permission_loading(false);
|
|
74
82
|
set_has_permission(false);
|
|
75
83
|
return;
|
|
76
84
|
}
|
|
77
|
-
// If permission is provided via prop (e.g. from role-based resolution), use that
|
|
78
85
|
if (can_access_private_files_prop !== undefined) {
|
|
79
86
|
set_has_permission(can_access_private_files_prop);
|
|
80
87
|
set_permission_loading(false);
|
|
81
88
|
return;
|
|
82
89
|
}
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
// Otherwise, check via hazo_auth API
|
|
90
|
-
const check_permission = async () => {
|
|
91
|
-
try {
|
|
92
|
-
set_permission_loading(true);
|
|
93
|
-
const response = await fetch(API_ENDPOINTS.auth_get, {
|
|
94
|
-
credentials: 'include',
|
|
95
|
-
});
|
|
96
|
-
if (response.ok) {
|
|
97
|
-
const auth_data = await response.json();
|
|
98
|
-
if (auth_data.authenticated && auth_data.permissions) {
|
|
99
|
-
const has_perm = auth_data.permissions.includes(private_files_permission);
|
|
100
|
-
set_has_permission(has_perm);
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
set_has_permission(false);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
set_has_permission(false);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
logger.warn('[CollabFormFileUpload] Error checking permission', {
|
|
112
|
-
permission: private_files_permission,
|
|
113
|
-
error: error instanceof Error ? error.message : String(error),
|
|
114
|
-
});
|
|
115
|
-
set_has_permission(false);
|
|
116
|
-
}
|
|
117
|
-
finally {
|
|
118
|
-
set_permission_loading(false);
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
check_permission();
|
|
122
|
-
}, [accept_files_private, private_files_permission, can_access_private_files_prop]);
|
|
123
|
-
// Determine if private files section should be shown
|
|
90
|
+
// Without explicit prop, default to false for private files
|
|
91
|
+
set_permission_loading(false);
|
|
92
|
+
set_has_permission(false);
|
|
93
|
+
}, [accept_files_private, can_access_private_files_prop]);
|
|
124
94
|
const show_private_section = !!(accept_files_private && has_permission && !permission_loading);
|
|
125
95
|
/**
|
|
126
96
|
* Update files state (public files)
|
|
127
97
|
*/
|
|
128
98
|
const update_files = useCallback((new_files) => {
|
|
129
|
-
// Ensure all files have visibility set to 'public'
|
|
130
|
-
const files_with_visibility = new_files.map(f => ({
|
|
131
|
-
...f,
|
|
132
|
-
visibility: f.visibility || 'public',
|
|
133
|
-
}));
|
|
134
99
|
if (!is_controlled) {
|
|
135
|
-
set_internal_files(
|
|
136
|
-
}
|
|
137
|
-
if (on_files_change) {
|
|
138
|
-
on_files_change(files_with_visibility);
|
|
100
|
+
set_internal_files(new_files);
|
|
139
101
|
}
|
|
102
|
+
on_files_change?.(new_files);
|
|
140
103
|
}, [is_controlled, on_files_change]);
|
|
141
104
|
/**
|
|
142
105
|
* Update private files state
|
|
143
106
|
*/
|
|
144
107
|
const update_private_files = useCallback((new_files) => {
|
|
145
|
-
// Ensure all files have visibility set to 'private'
|
|
146
|
-
const files_with_visibility = new_files.map(f => ({
|
|
147
|
-
...f,
|
|
148
|
-
visibility: 'private',
|
|
149
|
-
}));
|
|
150
108
|
if (!is_private_controlled) {
|
|
151
|
-
set_internal_private_files(
|
|
152
|
-
}
|
|
153
|
-
if (on_private_files_change) {
|
|
154
|
-
on_private_files_change(files_with_visibility);
|
|
109
|
+
set_internal_private_files(new_files);
|
|
155
110
|
}
|
|
111
|
+
on_private_files_change?.(new_files);
|
|
156
112
|
}, [is_private_controlled, on_private_files_change]);
|
|
157
113
|
/**
|
|
158
114
|
* Toggle file visibility (move between public and private)
|
|
159
115
|
*/
|
|
160
116
|
const toggle_file_visibility = useCallback((file_id, current_visibility) => {
|
|
161
117
|
if (current_visibility === 'public') {
|
|
162
|
-
// Move from public to private
|
|
163
118
|
const file_to_move = files.find(f => f.file_id === file_id);
|
|
164
119
|
if (file_to_move) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
update_files(new_public_files);
|
|
168
|
-
update_private_files([...private_files, moved_file]);
|
|
120
|
+
update_files(files.filter(f => f.file_id !== file_id));
|
|
121
|
+
update_private_files([...private_files, { ...file_to_move, visibility: 'private' }]);
|
|
169
122
|
}
|
|
170
123
|
}
|
|
171
124
|
else {
|
|
172
|
-
// Move from private to public
|
|
173
125
|
const file_to_move = private_files.find(f => f.file_id === file_id);
|
|
174
126
|
if (file_to_move) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
update_private_files(new_private_files);
|
|
178
|
-
update_files([...files, moved_file]);
|
|
127
|
+
update_private_files(private_files.filter(f => f.file_id !== file_id));
|
|
128
|
+
update_files([...files, { ...file_to_move, visibility: 'public' }]);
|
|
179
129
|
}
|
|
180
130
|
}
|
|
181
131
|
}, [files, private_files, update_files, update_private_files]);
|
|
@@ -183,124 +133,68 @@ export function CollabFormFileUpload({ field_id_final, accept_files_public = fal
|
|
|
183
133
|
* Validate file before upload
|
|
184
134
|
*/
|
|
185
135
|
const validate_file = useCallback((file) => {
|
|
186
|
-
// Run custom validator first (if provided)
|
|
187
136
|
if (file_validator) {
|
|
188
137
|
const custom_error = file_validator(file);
|
|
189
|
-
if (custom_error)
|
|
138
|
+
if (custom_error)
|
|
190
139
|
return custom_error;
|
|
191
|
-
}
|
|
192
140
|
}
|
|
193
|
-
// Check file count
|
|
194
141
|
if (files.length >= max_files) {
|
|
195
142
|
return `Maximum ${max_files} files allowed`;
|
|
196
143
|
}
|
|
197
|
-
// Check file size
|
|
198
144
|
if (max_size && file.size > max_size) {
|
|
199
145
|
return `File size exceeds maximum of ${format_file_size(max_size)}`;
|
|
200
146
|
}
|
|
201
|
-
// Check file type (if file_accept is specified)
|
|
202
147
|
if (file_accept) {
|
|
203
|
-
const accept_patterns = file_accept.split(',').map(
|
|
148
|
+
const accept_patterns = file_accept.split(',').map(p => p.trim());
|
|
204
149
|
const file_extension = '.' + file.name.split('.').pop()?.toLowerCase();
|
|
205
150
|
const file_mime = file.type;
|
|
206
|
-
const matches = accept_patterns.some(
|
|
207
|
-
if (pattern.startsWith('.'))
|
|
208
|
-
// Extension match
|
|
151
|
+
const matches = accept_patterns.some(pattern => {
|
|
152
|
+
if (pattern.startsWith('.'))
|
|
209
153
|
return file_extension === pattern.toLowerCase();
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const base_type = pattern.split('/')[0];
|
|
214
|
-
return file_mime.startsWith(base_type + '/');
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
// Exact MIME type match
|
|
218
|
-
return file_mime === pattern;
|
|
219
|
-
}
|
|
154
|
+
if (pattern.includes('/*'))
|
|
155
|
+
return file_mime.startsWith(pattern.split('/')[0] + '/');
|
|
156
|
+
return file_mime === pattern;
|
|
220
157
|
});
|
|
221
|
-
if (!matches)
|
|
158
|
+
if (!matches)
|
|
222
159
|
return `File type not allowed. Accepted: ${file_accept}`;
|
|
223
|
-
}
|
|
224
160
|
}
|
|
225
161
|
return null;
|
|
226
162
|
}, [files.length, max_files, max_size, file_accept, file_validator]);
|
|
227
163
|
/**
|
|
228
|
-
* Upload file
|
|
164
|
+
* Upload file via file_manager.upload callback
|
|
229
165
|
*/
|
|
230
166
|
const upload_file = useCallback(async (file, visibility = 'public') => {
|
|
231
|
-
const
|
|
232
|
-
const uploading_id = `${file_id}-${file.name}`;
|
|
167
|
+
const uploading_id = `${Date.now()}-${file.name}`;
|
|
233
168
|
try {
|
|
234
|
-
set_uploading_files(
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (files_dir) {
|
|
239
|
-
form_data.append('files_dir', files_dir);
|
|
240
|
-
}
|
|
241
|
-
// Add visibility to the form data
|
|
242
|
-
form_data.append('visibility', visibility);
|
|
243
|
-
// Add required permission for private files (server-side validation)
|
|
244
|
-
if (visibility === 'private' && private_files_permission) {
|
|
245
|
-
form_data.append('required_permission', private_files_permission);
|
|
246
|
-
}
|
|
247
|
-
const response = await fetch(upload_endpoint, {
|
|
248
|
-
method: 'POST',
|
|
249
|
-
body: form_data,
|
|
250
|
-
credentials: 'include',
|
|
251
|
-
});
|
|
252
|
-
if (!response.ok) {
|
|
253
|
-
const error_data = await response.json().catch(() => ({ error: 'Upload failed' }));
|
|
254
|
-
throw new Error(error_data.error || `Upload failed: ${response.statusText}`);
|
|
255
|
-
}
|
|
256
|
-
const result = await response.json();
|
|
257
|
-
const file_data = {
|
|
258
|
-
file_path: result.file_path || result.path || '',
|
|
259
|
-
file_name: file.name,
|
|
260
|
-
file_size: file.size,
|
|
261
|
-
file_type: file.type,
|
|
262
|
-
file_id,
|
|
263
|
-
uploaded_at: new Date(),
|
|
169
|
+
set_uploading_files(prev => new Set(prev).add(uploading_id));
|
|
170
|
+
const attachment = await file_manager.upload(file, {
|
|
171
|
+
entity_type: 'form_field',
|
|
172
|
+
entity_id: field_id_final,
|
|
264
173
|
visibility,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
await file_processor(file_data, component_ref);
|
|
271
|
-
set_processed_files((prev) => new Set(prev).add(file_data.file_id));
|
|
272
|
-
}
|
|
273
|
-
finally {
|
|
274
|
-
set_processing_files((prev) => {
|
|
275
|
-
const next = new Set(prev);
|
|
276
|
-
next.delete(file_data.file_id);
|
|
277
|
-
return next;
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
set_upload_errors((prev) => {
|
|
174
|
+
scope_id,
|
|
175
|
+
uploaded_by,
|
|
176
|
+
metadata: files_dir ? { files_dir } : undefined,
|
|
177
|
+
});
|
|
178
|
+
set_upload_errors(prev => {
|
|
282
179
|
const next = { ...prev };
|
|
283
180
|
delete next[uploading_id];
|
|
284
181
|
return next;
|
|
285
182
|
});
|
|
286
|
-
return
|
|
183
|
+
return attachment;
|
|
287
184
|
}
|
|
288
185
|
catch (error) {
|
|
289
186
|
const error_message = error instanceof Error ? error.message : 'Upload failed';
|
|
290
|
-
set_upload_errors(
|
|
291
|
-
...prev,
|
|
292
|
-
[uploading_id]: error_message,
|
|
293
|
-
}));
|
|
187
|
+
set_upload_errors(prev => ({ ...prev, [uploading_id]: error_message }));
|
|
294
188
|
return null;
|
|
295
189
|
}
|
|
296
190
|
finally {
|
|
297
|
-
set_uploading_files(
|
|
191
|
+
set_uploading_files(prev => {
|
|
298
192
|
const next = new Set(prev);
|
|
299
193
|
next.delete(uploading_id);
|
|
300
194
|
return next;
|
|
301
195
|
});
|
|
302
196
|
}
|
|
303
|
-
}, [field_id_final,
|
|
197
|
+
}, [field_id_final, file_manager, files_dir, scope_id, uploaded_by]);
|
|
304
198
|
/**
|
|
305
199
|
* Handle file selection (public files)
|
|
306
200
|
*/
|
|
@@ -309,8 +203,7 @@ export function CollabFormFileUpload({ field_id_final, accept_files_public = fal
|
|
|
309
203
|
return;
|
|
310
204
|
const files_to_upload = [];
|
|
311
205
|
const errors = {};
|
|
312
|
-
|
|
313
|
-
Array.from(selected_files).forEach((file) => {
|
|
206
|
+
Array.from(selected_files).forEach(file => {
|
|
314
207
|
const error = validate_file(file);
|
|
315
208
|
if (error) {
|
|
316
209
|
errors[file.name] = error;
|
|
@@ -319,16 +212,14 @@ export function CollabFormFileUpload({ field_id_final, accept_files_public = fal
|
|
|
319
212
|
files_to_upload.push(file);
|
|
320
213
|
}
|
|
321
214
|
});
|
|
322
|
-
// Set validation errors
|
|
323
215
|
if (Object.keys(errors).length > 0) {
|
|
324
|
-
set_upload_errors(
|
|
216
|
+
set_upload_errors(prev => ({ ...prev, ...errors }));
|
|
325
217
|
}
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
update_files([...files, ...successful_uploads]);
|
|
218
|
+
const upload_promises = files_to_upload.map(file => upload_file(file, 'public'));
|
|
219
|
+
const uploaded = await Promise.all(upload_promises);
|
|
220
|
+
const successful = uploaded.filter((f) => f !== null);
|
|
221
|
+
if (successful.length > 0) {
|
|
222
|
+
update_files([...files, ...successful]);
|
|
332
223
|
}
|
|
333
224
|
}, [files, validate_file, upload_file, update_files]);
|
|
334
225
|
/**
|
|
@@ -339,8 +230,7 @@ export function CollabFormFileUpload({ field_id_final, accept_files_public = fal
|
|
|
339
230
|
return;
|
|
340
231
|
const files_to_upload = [];
|
|
341
232
|
const errors = {};
|
|
342
|
-
|
|
343
|
-
Array.from(selected_files).forEach((file) => {
|
|
233
|
+
Array.from(selected_files).forEach(file => {
|
|
344
234
|
const error = validate_file(file);
|
|
345
235
|
if (error) {
|
|
346
236
|
errors[file.name] = error;
|
|
@@ -349,21 +239,17 @@ export function CollabFormFileUpload({ field_id_final, accept_files_public = fal
|
|
|
349
239
|
files_to_upload.push(file);
|
|
350
240
|
}
|
|
351
241
|
});
|
|
352
|
-
// Set validation errors
|
|
353
242
|
if (Object.keys(errors).length > 0) {
|
|
354
|
-
set_upload_errors(
|
|
243
|
+
set_upload_errors(prev => ({ ...prev, ...errors }));
|
|
355
244
|
}
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
update_private_files([...private_files, ...successful_uploads]);
|
|
245
|
+
const upload_promises = files_to_upload.map(file => upload_file(file, 'private'));
|
|
246
|
+
const uploaded = await Promise.all(upload_promises);
|
|
247
|
+
const successful = uploaded.filter((f) => f !== null);
|
|
248
|
+
if (successful.length > 0) {
|
|
249
|
+
update_private_files([...private_files, ...successful]);
|
|
362
250
|
}
|
|
363
251
|
}, [private_files, validate_file, upload_file, update_private_files]);
|
|
364
|
-
|
|
365
|
-
* Handle drag and drop
|
|
366
|
-
*/
|
|
252
|
+
// Drag handlers (public)
|
|
367
253
|
const handle_drag_enter = useCallback((e) => {
|
|
368
254
|
e.preventDefault();
|
|
369
255
|
e.stopPropagation();
|
|
@@ -384,9 +270,7 @@ export function CollabFormFileUpload({ field_id_final, accept_files_public = fal
|
|
|
384
270
|
set_is_dragging(false);
|
|
385
271
|
handle_files_selected(e.dataTransfer.files);
|
|
386
272
|
}, [handle_files_selected]);
|
|
387
|
-
|
|
388
|
-
* Handle private drag and drop
|
|
389
|
-
*/
|
|
273
|
+
// Drag handlers (private)
|
|
390
274
|
const handle_private_drag_enter = useCallback((e) => {
|
|
391
275
|
e.preventDefault();
|
|
392
276
|
e.stopPropagation();
|
|
@@ -403,104 +287,136 @@ export function CollabFormFileUpload({ field_id_final, accept_files_public = fal
|
|
|
403
287
|
set_is_dragging_private(false);
|
|
404
288
|
handle_private_files_selected(e.dataTransfer.files);
|
|
405
289
|
}, [handle_private_files_selected]);
|
|
406
|
-
|
|
407
|
-
* Handle file input change
|
|
408
|
-
*/
|
|
290
|
+
// Input handlers
|
|
409
291
|
const handle_input_change = useCallback((e) => {
|
|
410
292
|
handle_files_selected(e.target.files);
|
|
411
|
-
|
|
412
|
-
if (file_input_ref.current) {
|
|
293
|
+
if (file_input_ref.current)
|
|
413
294
|
file_input_ref.current.value = '';
|
|
414
|
-
}
|
|
415
295
|
}, [handle_files_selected]);
|
|
416
|
-
/**
|
|
417
|
-
* Handle private file input change
|
|
418
|
-
*/
|
|
419
296
|
const handle_private_input_change = useCallback((e) => {
|
|
420
297
|
handle_private_files_selected(e.target.files);
|
|
421
|
-
|
|
422
|
-
if (private_file_input_ref.current) {
|
|
298
|
+
if (private_file_input_ref.current)
|
|
423
299
|
private_file_input_ref.current.value = '';
|
|
424
|
-
}
|
|
425
300
|
}, [handle_private_files_selected]);
|
|
426
301
|
/**
|
|
427
|
-
* Handle file delete
|
|
302
|
+
* Handle file delete — calls file_manager.remove then updates state
|
|
428
303
|
*/
|
|
429
|
-
const handle_delete_file = useCallback((file_id) => {
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
delete_error: `Minimum ${min_files} files required`,
|
|
436
|
-
}));
|
|
304
|
+
const handle_delete_file = useCallback(async (file_id) => {
|
|
305
|
+
const file = files.find(f => f.file_id === file_id);
|
|
306
|
+
if (!file)
|
|
307
|
+
return;
|
|
308
|
+
if (min_files !== undefined && files.length <= min_files) {
|
|
309
|
+
set_upload_errors(prev => ({ ...prev, delete_error: `Minimum ${min_files} files required` }));
|
|
437
310
|
return;
|
|
438
311
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
312
|
+
try {
|
|
313
|
+
await file_manager.remove(file.file_id, file.ref_id);
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
logger.warn('[CollabFormFileUpload] Error removing file reference', {
|
|
317
|
+
file_id, ref_id: file.ref_id,
|
|
318
|
+
error: error instanceof Error ? error.message : String(error),
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
update_files(files.filter(f => f.file_id !== file_id));
|
|
322
|
+
set_upload_errors(prev => { const next = { ...prev }; delete next.delete_error; return next; });
|
|
323
|
+
}, [files, min_files, update_files, file_manager, logger]);
|
|
446
324
|
/**
|
|
447
325
|
* Handle private file delete
|
|
448
326
|
*/
|
|
449
|
-
const handle_delete_private_file = useCallback((file_id) => {
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
*/
|
|
456
|
-
const get_file_url = useCallback((file_path, visibility) => {
|
|
457
|
-
if (file_path.startsWith('/')) {
|
|
458
|
-
return file_path;
|
|
327
|
+
const handle_delete_private_file = useCallback(async (file_id) => {
|
|
328
|
+
const file = private_files.find(f => f.file_id === file_id);
|
|
329
|
+
if (!file)
|
|
330
|
+
return;
|
|
331
|
+
try {
|
|
332
|
+
await file_manager.remove(file.file_id, file.ref_id);
|
|
459
333
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
334
|
+
catch (error) {
|
|
335
|
+
logger.warn('[CollabFormFileUpload] Error removing private file reference', {
|
|
336
|
+
file_id, ref_id: file.ref_id,
|
|
337
|
+
error: error instanceof Error ? error.message : String(error),
|
|
338
|
+
});
|
|
463
339
|
}
|
|
464
|
-
|
|
465
|
-
}, [
|
|
466
|
-
|
|
340
|
+
update_private_files(private_files.filter(f => f.file_id !== file_id));
|
|
341
|
+
}, [private_files, update_private_files, file_manager, logger]);
|
|
342
|
+
/**
|
|
343
|
+
* Handle re-upload for a missing file
|
|
344
|
+
*/
|
|
345
|
+
const handle_reupload = useCallback(async (original_file_id, visibility) => {
|
|
346
|
+
if (!file_manager.reupload)
|
|
347
|
+
return;
|
|
348
|
+
// Create a file input and trigger selection
|
|
349
|
+
const input = document.createElement('input');
|
|
350
|
+
input.type = 'file';
|
|
351
|
+
if (file_accept)
|
|
352
|
+
input.accept = file_accept;
|
|
353
|
+
input.onchange = async () => {
|
|
354
|
+
const file = input.files?.[0];
|
|
355
|
+
if (!file)
|
|
356
|
+
return;
|
|
357
|
+
const uploading_id = `reupload-${original_file_id}`;
|
|
358
|
+
set_uploading_files(prev => new Set(prev).add(uploading_id));
|
|
359
|
+
try {
|
|
360
|
+
const new_attachment = await file_manager.reupload(file, original_file_id, {
|
|
361
|
+
entity_type: 'form_field',
|
|
362
|
+
entity_id: field_id_final,
|
|
363
|
+
visibility,
|
|
364
|
+
scope_id,
|
|
365
|
+
uploaded_by,
|
|
366
|
+
});
|
|
367
|
+
// Replace the old attachment with the new one
|
|
368
|
+
if (visibility === 'public') {
|
|
369
|
+
update_files(files.map(f => f.file_id === original_file_id ? new_attachment : f));
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
update_private_files(private_files.map(f => f.file_id === original_file_id ? new_attachment : f));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
set_upload_errors(prev => ({
|
|
377
|
+
...prev,
|
|
378
|
+
[uploading_id]: error instanceof Error ? error.message : 'Re-upload failed',
|
|
379
|
+
}));
|
|
380
|
+
}
|
|
381
|
+
finally {
|
|
382
|
+
set_uploading_files(prev => {
|
|
383
|
+
const next = new Set(prev);
|
|
384
|
+
next.delete(uploading_id);
|
|
385
|
+
return next;
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
input.click();
|
|
390
|
+
}, [file_manager, field_id_final, file_accept, scope_id, uploaded_by, files, private_files, update_files, update_private_files]);
|
|
391
|
+
/**
|
|
392
|
+
* Get download URL for a file
|
|
393
|
+
*/
|
|
394
|
+
const get_file_url = useCallback((attachment) => {
|
|
395
|
+
return file_manager.get_download_url(attachment.file_id, attachment.visibility);
|
|
396
|
+
}, [file_manager]);
|
|
397
|
+
// Build accordion title
|
|
467
398
|
const accordion_title = accept_files_public && show_private_section
|
|
468
399
|
? `Files (${files.length} public, ${private_files.length} private)`
|
|
469
400
|
: show_private_section && !accept_files_public
|
|
470
401
|
? private_files.length > 0 ? `Private Files (${private_files.length})` : 'Private Files'
|
|
471
402
|
: files.length > 0 ? `Files (${files.length})` : 'Files';
|
|
472
|
-
|
|
473
|
-
// This prevents hydration mismatches when file settings change between server and client
|
|
474
|
-
return (_jsx("div", { className: "cls_collab_file_upload space-y-2", suppressHydrationWarning: true, children: (accept_files_public || accept_files_private) ? (
|
|
475
|
-
/* Dynamic import for Accordion - will be loaded by consuming app */
|
|
476
|
-
_jsx(FileUploadAccordion, { field_id_final: field_id_final, title: accordion_title, is_dragging: is_dragging, on_drag_enter: handle_drag_enter, on_drag_leave: handle_drag_leave, on_drag_over: handle_drag_over, on_drop: handle_drop, file_input_ref: file_input_ref, file_accept: file_accept, on_input_change: handle_input_change, files: files, uploading_files: uploading_files, processing_files: processing_files, processed_files: processed_files, upload_errors: upload_errors, get_file_url: get_file_url, on_file_delete: handle_delete_file, get_file_icon: get_file_icon, format_file_size: format_file_size, max_files: max_files, min_files: min_files,
|
|
477
|
-
// File mode props
|
|
478
|
-
accept_files_public: accept_files_public, show_private_section: show_private_section, private_files: private_files, private_file_input_ref: private_file_input_ref, is_dragging_private: is_dragging_private, on_private_drag_enter: handle_private_drag_enter, on_private_drag_leave: handle_private_drag_leave, on_private_drop: handle_private_drop, on_private_input_change: handle_private_input_change, on_private_file_delete: handle_delete_private_file, on_toggle_visibility: toggle_file_visibility })) : null }));
|
|
403
|
+
return (_jsx("div", { className: "cls_collab_file_upload space-y-2", suppressHydrationWarning: true, children: (accept_files_public || accept_files_private) ? (_jsx(FileUploadAccordion, { field_id_final: field_id_final, title: accordion_title, is_dragging: is_dragging, on_drag_enter: handle_drag_enter, on_drag_leave: handle_drag_leave, on_drag_over: handle_drag_over, on_drop: handle_drop, file_input_ref: file_input_ref, file_accept: file_accept, on_input_change: handle_input_change, files: files, uploading_files: uploading_files, upload_errors: upload_errors, get_file_url: get_file_url, on_file_delete: handle_delete_file, format_file_size: format_file_size, max_files: max_files, min_files: min_files, accept_files_public: accept_files_public, show_private_section: show_private_section, private_files: private_files, private_file_input_ref: private_file_input_ref, is_dragging_private: is_dragging_private, on_private_drag_enter: handle_private_drag_enter, on_private_drag_leave: handle_private_drag_leave, on_private_drop: handle_private_drop, on_private_input_change: handle_private_input_change, on_private_file_delete: handle_delete_private_file, on_toggle_visibility: toggle_file_visibility, show_file_status: show_file_status, allow_reupload: allow_reupload, file_statuses: file_statuses, on_reupload: handle_reupload })) : null }));
|
|
479
404
|
}
|
|
480
405
|
/**
|
|
481
406
|
* File upload accordion component with dynamic imports
|
|
482
407
|
*/
|
|
483
|
-
function FileUploadAccordion({ field_id_final, title, is_dragging, on_drag_enter, on_drag_leave, on_drag_over, on_drop, file_input_ref, file_accept, on_input_change, files, uploading_files,
|
|
484
|
-
// File mode props
|
|
485
|
-
accept_files_public = false, show_private_section = false, private_files = [], private_file_input_ref, is_dragging_private = false, on_private_drag_enter, on_private_drag_leave, on_private_drop, on_private_input_change, on_private_file_delete, on_toggle_visibility, }) {
|
|
408
|
+
function FileUploadAccordion({ field_id_final, title, is_dragging, on_drag_enter, on_drag_leave, on_drag_over, on_drop, file_input_ref, file_accept, on_input_change, files, uploading_files, upload_errors, get_file_url, on_file_delete, format_file_size, max_files, min_files, accept_files_public = false, show_private_section = false, private_files = [], private_file_input_ref, is_dragging_private = false, on_private_drag_enter, on_private_drag_leave, on_private_drop, on_private_input_change, on_private_file_delete, on_toggle_visibility, show_file_status = false, allow_reupload = false, file_statuses = {}, on_reupload, }) {
|
|
486
409
|
const logger = use_logger();
|
|
487
410
|
const [AccordionComponents, set_accordion_components] = React.useState(null);
|
|
488
411
|
const [is_loading, set_is_loading] = React.useState(true);
|
|
489
412
|
const [is_mounted, set_is_mounted] = React.useState(false);
|
|
490
|
-
|
|
491
|
-
React.useEffect(() => {
|
|
492
|
-
set_is_mounted(true);
|
|
493
|
-
}, []);
|
|
413
|
+
React.useEffect(() => { set_is_mounted(true); }, []);
|
|
494
414
|
React.useEffect(() => {
|
|
495
|
-
// Only load accordion on client side
|
|
496
415
|
if (!is_mounted)
|
|
497
416
|
return;
|
|
498
|
-
// Dynamic import - components will be resolved by consuming app's bundler
|
|
499
417
|
const load_accordion = async () => {
|
|
500
418
|
try {
|
|
501
419
|
set_is_loading(true);
|
|
502
|
-
// Try to import from consuming app's components directory
|
|
503
|
-
// This path will be resolved by Next.js/webpack in the consuming app
|
|
504
420
|
// @ts-expect-error - These modules are provided by the consuming application
|
|
505
421
|
const accordion_module = await import('@/components/ui/accordion').catch(() => null);
|
|
506
422
|
if (accordion_module) {
|
|
@@ -528,42 +444,31 @@ accept_files_public = false, show_private_section = false, private_files = [], p
|
|
|
528
444
|
};
|
|
529
445
|
load_accordion();
|
|
530
446
|
}, [is_mounted]);
|
|
531
|
-
// Always render consistent structure to prevent hydration mismatch
|
|
532
|
-
// During SSR and initial client render, show empty placeholder
|
|
533
|
-
// After mount and accordion load, show actual content
|
|
534
447
|
if (!is_mounted || is_loading || !AccordionComponents) {
|
|
535
448
|
return (_jsx("div", { className: "cls_collab_file_upload_loading text-sm text-muted-foreground", suppressHydrationWarning: true }));
|
|
536
449
|
}
|
|
537
450
|
const { Accordion, AccordionItem, AccordionTrigger, AccordionContent } = AccordionComponents;
|
|
538
|
-
// Default to open if files exist
|
|
539
451
|
const default_value = files.length > 0 ? 'files' : undefined;
|
|
540
|
-
// Determine if files are required (min_files >= 1)
|
|
541
452
|
const is_files_required = min_files !== undefined && min_files >= 1;
|
|
542
453
|
/**
|
|
543
|
-
* Render a file item
|
|
454
|
+
* Render a single file item
|
|
544
455
|
*/
|
|
545
|
-
const render_file_item = (
|
|
546
|
-
const is_uploading = Array.from(uploading_files).some(
|
|
547
|
-
const
|
|
548
|
-
const
|
|
549
|
-
return (_jsxs("div", { className: "cls_collab_file_item flex-shrink-0 w-24 flex flex-col items-center gap-1 p-2 border rounded-md hover:bg-muted transition-colors relative", children: [_jsxs("a", { href: get_file_url(
|
|
456
|
+
const render_file_item = (attachment, visibility, delete_handler) => {
|
|
457
|
+
const is_uploading = Array.from(uploading_files).some(id => id.includes(attachment.file_id) || id.includes(attachment.file_name));
|
|
458
|
+
const file_status = file_statuses[attachment.file_id] ?? attachment.status;
|
|
459
|
+
const is_missing = file_status === 'missing';
|
|
460
|
+
return (_jsxs("div", { className: "cls_collab_file_item flex-shrink-0 w-24 flex flex-col items-center gap-1 p-2 border rounded-md hover:bg-muted transition-colors relative", children: [_jsxs("a", { href: get_file_url(attachment), target: "_blank", rel: "noopener noreferrer", className: cn("flex flex-col items-center gap-1 w-full", (is_uploading || is_missing) && "pointer-events-none opacity-50"), "aria-disabled": is_uploading || is_missing, children: [_jsx("div", { className: "relative", children: get_file_icon(attachment.mime_type, attachment.file_name) }), _jsx("span", { className: "text-xs text-center truncate w-full", title: attachment.file_name, children: attachment.file_name }), _jsx("span", { className: "text-xs text-muted-foreground", children: format_file_size(attachment.file_size) })] }), show_file_status && file_status && file_status !== 'active' && (_jsx(StatusBadge, { status: file_status })), _jsxs("div", { className: "flex items-center gap-1", children: [allow_reupload && is_missing && on_reupload && (_jsx("button", { type: "button", onClick: () => on_reupload(attachment.file_id, visibility), className: "text-blue-600 hover:text-blue-700 p-1", title: "Re-upload file", "aria-label": `Re-upload ${attachment.file_name}`, children: _jsx(HiRefresh, { className: "h-4 w-4" }) })), accept_files_public && show_private_section && on_toggle_visibility && (_jsx("button", { type: "button", onClick: () => on_toggle_visibility(attachment.file_id, visibility), className: cn("p-1 transition-colors", visibility === 'private'
|
|
550
461
|
? "text-amber-600 hover:text-amber-700"
|
|
551
|
-
: "text-muted-foreground hover:text-foreground"), disabled: is_uploading, "aria-label": visibility === 'private' ? `Make ${
|
|
462
|
+
: "text-muted-foreground hover:text-foreground"), disabled: is_uploading, "aria-label": visibility === 'private' ? `Make ${attachment.file_name} public` : `Make ${attachment.file_name} private`, title: visibility === 'private' ? 'Make public' : 'Make private', children: visibility === 'private' ? (_jsx(HiLockClosed, { className: "h-4 w-4" })) : (_jsx(HiLockOpen, { className: "h-4 w-4" })) })), _jsx("button", { type: "button", onClick: () => delete_handler(attachment.file_id), className: "text-destructive hover:text-destructive/80 p-1", disabled: is_uploading, "aria-label": `Delete ${attachment.file_name}`, children: _jsx(HiTrash, { className: "h-4 w-4" }) })] }), is_uploading && (_jsx("div", { className: "absolute inset-0 bg-background/50 flex items-center justify-center rounded-md", children: _jsx("div", { className: "text-xs text-muted-foreground", children: "Uploading..." }) }))] }, attachment.file_id));
|
|
552
463
|
};
|
|
553
464
|
/**
|
|
554
465
|
* Render file upload section (drop zone + file list)
|
|
555
466
|
*/
|
|
556
467
|
const render_file_section = (section_type, section_files, section_is_dragging, section_on_drag_enter, section_on_drag_leave, section_on_drop, section_file_input_ref, section_on_input_change, section_on_delete, input_id_suffix) => (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: cn('cls_collab_file_drop_zone border-2 border-dashed rounded-md p-4 text-center transition-colors', section_is_dragging
|
|
557
468
|
? 'border-primary bg-primary/5'
|
|
558
|
-
: 'border-input hover:border-primary/50 hover:bg-muted/50', section_files.length >= max_files && 'opacity-50 cursor-not-allowed'), onDragEnter: section_on_drag_enter, onDragLeave: section_on_drag_leave, onDragOver: on_drag_over, onDrop: section_on_drop, children: [_jsx("input", { ref: section_file_input_ref, type: "file", id: `${field_id_final}-${input_id_suffix}`, className: "hidden", accept: file_accept, multiple: true, onChange: section_on_input_change, disabled: section_files.length >= max_files }), _jsxs("label", { htmlFor: `${field_id_final}-${input_id_suffix}`, className: cn('cursor-pointer block', section_files.length >= max_files && 'cursor-not-allowed opacity-50'), children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Drag and drop files here, or click to select" }), file_accept && (_jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: ["Accepted: ", file_accept] }))] })] }), section_files.length > 0 && (_jsx("div", { className: "cls_collab_file_list", children: _jsx("div", { className: "flex overflow-x-auto gap-2 pb-2", children: section_files.map(
|
|
559
|
-
|
|
560
|
-
// The Accordion content may differ between server and client due to dynamic imports
|
|
561
|
-
return (_jsx("div", { suppressHydrationWarning: true, children: _jsx(Accordion, { type: "single", collapsible: true, defaultValue: default_value, className: "w-full", children: _jsxs(AccordionItem, { value: "files", children: [_jsxs(AccordionTrigger, { className: "text-sm font-medium", children: [title, is_files_required && _jsx("span", { className: "text-destructive ml-1", children: "*" })] }), _jsx(AccordionContent, { children: _jsxs("div", { className: "space-y-4", children: [Object.keys(upload_errors).length > 0 && (_jsx("div", { className: "space-y-1", children: Object.entries(upload_errors).map(([key, error]) => (_jsx("p", { className: "text-sm text-destructive", children: error }, key))) })), accept_files_public && show_private_section ? (_jsxs(Accordion, { type: "multiple", defaultValue: ['public-files', 'private-files'], className: "w-full", children: [_jsxs(AccordionItem, { value: "public-files", className: "border-b-0", children: [_jsx(AccordionTrigger, { className: "text-sm font-medium py-2", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(HiLockOpen, { className: "h-4 w-4 text-muted-foreground" }), _jsxs("span", { children: ["Public Files (", files.length, ")"] })] }) }), _jsx(AccordionContent, { className: "pb-4", children: render_file_section('public', files, is_dragging, on_drag_enter, on_drag_leave, on_drop, file_input_ref, on_input_change, on_file_delete, 'public-file-input') })] }), _jsxs(AccordionItem, { value: "private-files", className: "border-b-0", children: [_jsx(AccordionTrigger, { className: "text-sm font-medium py-2", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(HiLockClosed, { className: "h-4 w-4 text-amber-600" }), _jsxs("span", { children: ["Private Files (", private_files.length, ")"] })] }) }), _jsx(AccordionContent, { className: "pb-4", children: private_file_input_ref && on_private_drag_enter && on_private_drag_leave && on_private_drop && on_private_input_change && on_private_file_delete && render_file_section('private', private_files, is_dragging_private, on_private_drag_enter, on_private_drag_leave, on_private_drop, private_file_input_ref, on_private_input_change, on_private_file_delete, 'private-file-input') })] })] })) : show_private_section && !accept_files_public ? (
|
|
562
|
-
/* Private-only section UI */
|
|
563
|
-
_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm text-amber-600 mb-2", children: [_jsx(HiLockClosed, { className: "h-4 w-4" }), _jsx("span", { className: "font-medium", children: "Private Files" })] }), private_file_input_ref && on_private_drag_enter && on_private_drag_leave && on_private_drop && on_private_input_change && on_private_file_delete && render_file_section('private', private_files, is_dragging_private, on_private_drag_enter, on_private_drag_leave, on_private_drop, private_file_input_ref, on_private_input_change, on_private_file_delete, 'private-file-input')] })) : accept_files_public ? (
|
|
564
|
-
/* Public-only section UI (original behavior) */
|
|
565
|
-
_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: cn('cls_collab_file_drop_zone border-2 border-dashed rounded-md p-6 text-center transition-colors', is_dragging
|
|
469
|
+
: 'border-input hover:border-primary/50 hover:bg-muted/50', section_files.length >= max_files && 'opacity-50 cursor-not-allowed'), onDragEnter: section_on_drag_enter, onDragLeave: section_on_drag_leave, onDragOver: on_drag_over, onDrop: section_on_drop, children: [_jsx("input", { ref: section_file_input_ref, type: "file", id: `${field_id_final}-${input_id_suffix}`, className: "hidden", accept: file_accept, multiple: true, onChange: section_on_input_change, disabled: section_files.length >= max_files }), _jsxs("label", { htmlFor: `${field_id_final}-${input_id_suffix}`, className: cn('cursor-pointer block', section_files.length >= max_files && 'cursor-not-allowed opacity-50'), children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Drag and drop files here, or click to select" }), file_accept && (_jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: ["Accepted: ", file_accept] }))] })] }), section_files.length > 0 && (_jsx("div", { className: "cls_collab_file_list", children: _jsx("div", { className: "flex overflow-x-auto gap-2 pb-2", children: section_files.map(attachment => render_file_item(attachment, section_type, section_on_delete)) }) }))] }));
|
|
470
|
+
return (_jsx("div", { suppressHydrationWarning: true, children: _jsx(Accordion, { type: "single", collapsible: true, defaultValue: default_value, className: "w-full", children: _jsxs(AccordionItem, { value: "files", children: [_jsxs(AccordionTrigger, { className: "text-sm font-medium", children: [title, is_files_required && _jsx("span", { className: "text-destructive ml-1", children: "*" })] }), _jsx(AccordionContent, { children: _jsxs("div", { className: "space-y-4", children: [Object.keys(upload_errors).length > 0 && (_jsx("div", { className: "space-y-1", children: Object.entries(upload_errors).map(([key, error]) => (_jsx("p", { className: "text-sm text-destructive", children: error }, key))) })), accept_files_public && show_private_section ? (_jsxs(Accordion, { type: "multiple", defaultValue: ['public-files', 'private-files'], className: "w-full", children: [_jsxs(AccordionItem, { value: "public-files", className: "border-b-0", children: [_jsx(AccordionTrigger, { className: "text-sm font-medium py-2", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(HiLockOpen, { className: "h-4 w-4 text-muted-foreground" }), _jsxs("span", { children: ["Public Files (", files.length, ")"] })] }) }), _jsx(AccordionContent, { className: "pb-4", children: render_file_section('public', files, is_dragging, on_drag_enter, on_drag_leave, on_drop, file_input_ref, on_input_change, on_file_delete, 'public-file-input') })] }), _jsxs(AccordionItem, { value: "private-files", className: "border-b-0", children: [_jsx(AccordionTrigger, { className: "text-sm font-medium py-2", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(HiLockClosed, { className: "h-4 w-4 text-amber-600" }), _jsxs("span", { children: ["Private Files (", private_files.length, ")"] })] }) }), _jsx(AccordionContent, { className: "pb-4", children: private_file_input_ref && on_private_drag_enter && on_private_drag_leave && on_private_drop && on_private_input_change && on_private_file_delete && render_file_section('private', private_files, is_dragging_private, on_private_drag_enter, on_private_drag_leave, on_private_drop, private_file_input_ref, on_private_input_change, on_private_file_delete, 'private-file-input') })] })] })) : show_private_section && !accept_files_public ? (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm text-amber-600 mb-2", children: [_jsx(HiLockClosed, { className: "h-4 w-4" }), _jsx("span", { className: "font-medium", children: "Private Files" })] }), private_file_input_ref && on_private_drag_enter && on_private_drag_leave && on_private_drop && on_private_input_change && on_private_file_delete && render_file_section('private', private_files, is_dragging_private, on_private_drag_enter, on_private_drag_leave, on_private_drop, private_file_input_ref, on_private_input_change, on_private_file_delete, 'private-file-input')] })) : accept_files_public ? (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: cn('cls_collab_file_drop_zone border-2 border-dashed rounded-md p-6 text-center transition-colors', is_dragging
|
|
566
471
|
? 'border-primary bg-primary/5'
|
|
567
|
-
: 'border-input hover:border-primary/50 hover:bg-muted/50', files.length >= max_files && 'opacity-50 cursor-not-allowed'), onDragEnter: on_drag_enter, onDragLeave: on_drag_leave, onDragOver: on_drag_over, onDrop: on_drop, children: [_jsx("input", { ref: file_input_ref, type: "file", id: `${field_id_final}-file-input`, className: "hidden", accept: file_accept, multiple: true, onChange: on_input_change, disabled: files.length >= max_files }), _jsx("label", { htmlFor: `${field_id_final}-file-input`, className: cn('cursor-pointer block', files.length >= max_files && 'cursor-not-allowed opacity-50'), children: _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Drag and drop files here, or click to select" }), file_accept && (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Accepted: ", file_accept] }))] }) })] }), files.length > 0 && (_jsx("div", { className: "cls_collab_file_list space-y-2", children: _jsx("div", { className: "flex overflow-x-auto gap-2 pb-2", children: files.map(
|
|
472
|
+
: 'border-input hover:border-primary/50 hover:bg-muted/50', files.length >= max_files && 'opacity-50 cursor-not-allowed'), onDragEnter: on_drag_enter, onDragLeave: on_drag_leave, onDragOver: on_drag_over, onDrop: on_drop, children: [_jsx("input", { ref: file_input_ref, type: "file", id: `${field_id_final}-file-input`, className: "hidden", accept: file_accept, multiple: true, onChange: on_input_change, disabled: files.length >= max_files }), _jsx("label", { htmlFor: `${field_id_final}-file-input`, className: cn('cursor-pointer block', files.length >= max_files && 'cursor-not-allowed opacity-50'), children: _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Drag and drop files here, or click to select" }), file_accept && (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Accepted: ", file_accept] }))] }) })] }), files.length > 0 && (_jsx("div", { className: "cls_collab_file_list space-y-2", children: _jsx("div", { className: "flex overflow-x-auto gap-2 pb-2", children: files.map(attachment => render_file_item(attachment, 'public', on_file_delete)) }) }))] })) : null] }) })] }) }) }));
|
|
568
473
|
}
|
|
569
474
|
//# sourceMappingURL=collab_form_file_upload.js.map
|