hazo_collab_forms 1.9.0 → 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 +157 -44
- package/dist/components/_internal_form_set.d.ts +30 -30
- package/dist/components/_internal_form_set.d.ts.map +1 -1
- package/dist/components/_internal_form_set.js +31 -12
- package/dist/components/_internal_form_set.js.map +1 -1
- package/dist/components/collab_form_file_upload.d.ts +40 -74
- package/dist/components/collab_form_file_upload.d.ts.map +1 -1
- package/dist/components/collab_form_file_upload.js +191 -275
- package/dist/components/collab_form_file_upload.js.map +1 -1
- package/dist/components/hazo_add_field_dialog/components/pending_field_item.d.ts +1 -1
- package/dist/components/hazo_add_field_dialog/components/pending_field_item.js +2 -2
- package/dist/components/hazo_add_field_dialog/components/pending_field_item.js.map +1 -1
- package/dist/components/hazo_add_field_dialog/components/pending_field_list.d.ts +1 -1
- package/dist/components/hazo_add_field_dialog/hazo_add_field_dialog.js +7 -7
- package/dist/components/hazo_add_field_dialog/hazo_add_field_dialog.js.map +1 -1
- package/dist/components/hazo_add_field_dialog/types.d.ts +4 -4
- package/dist/components/hazo_add_field_dialog/types.d.ts.map +1 -1
- package/dist/components/hazo_add_field_dialog/utils/library_to_pending.js +2 -2
- package/dist/components/hazo_add_field_dialog/utils/library_to_pending.js.map +1 -1
- package/dist/components/hazo_add_group_dialog/hazo_add_group_dialog.js +6 -6
- package/dist/components/hazo_add_group_dialog/hazo_add_group_dialog.js.map +1 -1
- package/dist/components/hazo_collab_form_base.d.ts +26 -20
- package/dist/components/hazo_collab_form_base.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_base.js +22 -13
- package/dist/components/hazo_collab_form_base.js.map +1 -1
- package/dist/components/hazo_collab_form_checkbox.d.ts +1 -24
- package/dist/components/hazo_collab_form_checkbox.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_checkbox.js +15 -141
- package/dist/components/hazo_collab_form_checkbox.js.map +1 -1
- package/dist/components/hazo_collab_form_combo.d.ts +0 -44
- package/dist/components/hazo_collab_form_combo.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_combo.js +24 -199
- package/dist/components/hazo_collab_form_combo.js.map +1 -1
- package/dist/components/hazo_collab_form_data_table.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_data_table.js +2 -1
- package/dist/components/hazo_collab_form_data_table.js.map +1 -1
- package/dist/components/hazo_collab_form_date.d.ts +1 -59
- package/dist/components/hazo_collab_form_date.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_date.js +34 -194
- package/dist/components/hazo_collab_form_date.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 +15 -12
- 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 +5 -5
- package/dist/components/hazo_collab_form_group.js.map +1 -1
- package/dist/components/hazo_collab_form_inputbox.d.ts +0 -17
- package/dist/components/hazo_collab_form_inputbox.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_inputbox.js +25 -148
- package/dist/components/hazo_collab_form_inputbox.js.map +1 -1
- package/dist/components/hazo_collab_form_radio.d.ts +1 -36
- package/dist/components/hazo_collab_form_radio.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_radio.js +18 -143
- package/dist/components/hazo_collab_form_radio.js.map +1 -1
- package/dist/components/hazo_collab_form_textarea.d.ts +0 -17
- package/dist/components/hazo_collab_form_textarea.d.ts.map +1 -1
- package/dist/components/hazo_collab_form_textarea.js +14 -141
- package/dist/components/hazo_collab_form_textarea.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 +36 -0
- package/dist/components/hazo_collab_form_view/hooks/use_view_callbacks.d.ts.map +1 -0
- package/dist/components/hazo_collab_form_view/hooks/use_view_callbacks.js +82 -0
- package/dist/components/hazo_collab_form_view/hooks/use_view_callbacks.js.map +1 -0
- 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 +18 -12
- 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 +11 -81
- 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 +58 -278
- package/dist/components/hazo_collab_form_view/views/summary_view.js.map +1 -1
- package/dist/components/hazo_edit_field_dialog/hazo_edit_field_dialog.js +8 -8
- package/dist/components/hazo_edit_field_dialog/hazo_edit_field_dialog.js.map +1 -1
- package/dist/components/hazo_edit_field_dialog/types.d.ts +4 -4
- package/dist/components/hazo_edit_field_dialog/types.d.ts.map +1 -1
- package/dist/components/hazo_edit_group_dialog/hazo_edit_group_dialog.js +15 -15
- package/dist/components/hazo_edit_group_dialog/hazo_edit_group_dialog.js.map +1 -1
- package/dist/components/hazo_edit_group_dialog/types.d.ts +4 -4
- package/dist/components/hazo_edit_group_dialog/types.d.ts.map +1 -1
- package/dist/components/hazo_field_library/hooks/use_elements.d.ts.map +1 -1
- package/dist/components/hazo_field_library/hooks/use_elements.js +2 -1
- package/dist/components/hazo_field_library/hooks/use_elements.js.map +1 -1
- package/dist/components/hazo_field_selector_dialog/utils.d.ts.map +1 -1
- package/dist/components/hazo_field_selector_dialog/utils.js +2 -1
- package/dist/components/hazo_field_selector_dialog/utils.js.map +1 -1
- package/dist/components/hazo_template_generator/components/section_tree.js +1 -1
- package/dist/components/hazo_template_generator/components/section_tree.js.map +1 -1
- package/dist/components/hazo_template_generator/components/tabs/basic_tab.d.ts.map +1 -1
- package/dist/components/hazo_template_generator/components/tabs/basic_tab.js +6 -3
- package/dist/components/hazo_template_generator/components/tabs/basic_tab.js.map +1 -1
- package/dist/components/hazo_template_generator/components/tabs/section_tab.d.ts.map +1 -1
- package/dist/components/hazo_template_generator/components/tabs/section_tab.js +8 -4
- package/dist/components/hazo_template_generator/components/tabs/section_tab.js.map +1 -1
- package/dist/components/hazo_template_generator/types.d.ts.map +1 -1
- package/dist/components/hazo_template_generator/types.js +6 -3
- package/dist/components/hazo_template_generator/types.js.map +1 -1
- package/dist/components/hazo_template_generator/utils/validation.js +1 -1
- package/dist/components/hazo_template_generator/utils/validation.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/base_field_layout.d.ts +40 -0
- package/dist/components/shared/base_field_layout.d.ts.map +1 -0
- package/dist/components/shared/base_field_layout.js +34 -0
- package/dist/components/shared/base_field_layout.js.map +1 -0
- package/dist/components/shared/file_settings/file_settings_checkboxes.d.ts +13 -13
- package/dist/components/shared/file_settings/file_settings_checkboxes.d.ts.map +1 -1
- package/dist/components/shared/file_settings/file_settings_checkboxes.js +8 -25
- package/dist/components/shared/file_settings/file_settings_checkboxes.js.map +1 -1
- package/dist/components/shared/row_color_utils.d.ts +16 -0
- package/dist/components/shared/row_color_utils.d.ts.map +1 -0
- package/dist/components/shared/row_color_utils.js +21 -0
- package/dist/components/shared/row_color_utils.js.map +1 -0
- package/dist/components/shared/strip_base_props.d.ts +16 -0
- package/dist/components/shared/strip_base_props.d.ts.map +1 -0
- package/dist/components/shared/strip_base_props.js +91 -0
- package/dist/components/shared/strip_base_props.js.map +1 -0
- 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 +16 -0
- package/dist/components/shared/summary_utils/file_helpers.d.ts.map +1 -0
- package/dist/components/shared/summary_utils/file_helpers.js +26 -0
- package/dist/components/shared/summary_utils/file_helpers.js.map +1 -0
- package/dist/components/shared/summary_utils/index.d.ts +1 -0
- package/dist/components/shared/summary_utils/index.d.ts.map +1 -1
- package/dist/components/shared/summary_utils/index.js +1 -0
- package/dist/components/shared/summary_utils/index.js.map +1 -1
- package/dist/components/shared/unified_field_controls/index.d.ts +1 -0
- package/dist/components/shared/unified_field_controls/index.d.ts.map +1 -1
- package/dist/components/shared/unified_field_controls/index.js +2 -1
- package/dist/components/shared/unified_field_controls/index.js.map +1 -1
- package/dist/components/shared/unified_field_controls/use_field_control_props.d.ts +68 -0
- package/dist/components/shared/unified_field_controls/use_field_control_props.d.ts.map +1 -0
- package/dist/components/shared/unified_field_controls/use_field_control_props.js +105 -0
- package/dist/components/shared/unified_field_controls/use_field_control_props.js.map +1 -0
- package/dist/components/shared/use_base_form_field.d.ts +193 -0
- package/dist/components/shared/use_base_form_field.d.ts.map +1 -0
- package/dist/components/shared/use_base_form_field.js +265 -0
- package/dist/components/shared/use_base_form_field.js.map +1 -0
- package/dist/config/api_endpoints.d.ts +19 -0
- package/dist/config/api_endpoints.d.ts.map +1 -0
- package/dist/config/api_endpoints.js +19 -0
- package/dist/config/api_endpoints.js.map +1 -0
- package/dist/config/defaults.d.ts +17 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +17 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- 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/filter_dom_props.d.ts.map +1 -1
- package/dist/utils/filter_dom_props.js +2 -1
- package/dist/utils/filter_dom_props.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_chat_messages_check.d.ts.map +1 -1
- package/dist/utils/use_chat_messages_check.js +4 -2
- package/dist/utils/use_chat_messages_check.js.map +1 -1
- package/dist/utils/use_collab_chat.d.ts.map +1 -1
- package/dist/utils/use_collab_chat.js +2 -1
- package/dist/utils/use_collab_chat.js.map +1 -1
- package/dist/utils/use_field_chat_status.d.ts.map +1 -1
- package/dist/utils/use_field_chat_status.js +5 -3
- package/dist/utils/use_field_chat_status.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,35 +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 { HiTrash, HiPaperClip, HiPhotograph, HiDocument, HiDocumentText,
|
|
11
|
-
import { FaSpinner } from 'react-icons/fa';
|
|
13
|
+
import { HiTrash, HiPaperClip, HiPhotograph, HiDocument, HiDocumentText, HiLockClosed, HiLockOpen, HiRefresh } from 'react-icons/hi';
|
|
12
14
|
/**
|
|
13
|
-
* Get file icon based on
|
|
15
|
+
* Get file icon based on MIME type and filename
|
|
14
16
|
*/
|
|
15
|
-
function get_file_icon(
|
|
16
|
-
const extension = file_name.split('.').pop()?.toLowerCase() || '';
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
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)) {
|
|
20
21
|
return _jsx(HiPhotograph, { className: "h-8 w-8 text-blue-500" });
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
if (mime_type === 'application/pdf' || extension === 'pdf') {
|
|
23
|
+
if (mime === 'application/pdf' || extension === 'pdf') {
|
|
24
24
|
return _jsx(HiDocument, { className: "h-8 w-8 text-red-500" });
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
if (mime_type.includes('document') ||
|
|
28
|
-
mime_type.includes('text') ||
|
|
29
|
-
['doc', 'docx', 'txt', 'rtf', 'odt'].includes(extension)) {
|
|
26
|
+
if (mime.includes('document') || mime.includes('text') || ['doc', 'docx', 'txt', 'rtf', 'odt'].includes(extension)) {
|
|
30
27
|
return _jsx(HiDocumentText, { className: "h-8 w-8 text-green-500" });
|
|
31
28
|
}
|
|
32
|
-
// Default
|
|
33
29
|
return _jsx(HiPaperClip, { className: "h-8 w-8 text-gray-500" });
|
|
34
30
|
}
|
|
35
31
|
/**
|
|
@@ -43,10 +39,26 @@ function format_file_size(bytes) {
|
|
|
43
39
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
44
40
|
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
45
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
|
+
}
|
|
46
58
|
/**
|
|
47
59
|
* Collaboration form file upload component
|
|
48
60
|
*/
|
|
49
|
-
export function CollabFormFileUpload({ field_id_final,
|
|
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, }) {
|
|
50
62
|
const logger = use_logger();
|
|
51
63
|
const file_input_ref = useRef(null);
|
|
52
64
|
const private_file_input_ref = useRef(null);
|
|
@@ -56,119 +68,64 @@ export function CollabFormFileUpload({ field_id_final, accept_files, files_dir,
|
|
|
56
68
|
const [is_dragging_private, set_is_dragging_private] = useState(false);
|
|
57
69
|
const [upload_errors, set_upload_errors] = useState({});
|
|
58
70
|
const [uploading_files, set_uploading_files] = useState(new Set());
|
|
59
|
-
const [processing_files, set_processing_files] = useState(new Set());
|
|
60
|
-
const [processed_files, set_processed_files] = useState(new Set());
|
|
61
71
|
const [has_permission, set_has_permission] = useState(false);
|
|
62
72
|
const [permission_loading, set_permission_loading] = useState(true);
|
|
63
73
|
// Use controlled files if provided, otherwise use internal state
|
|
64
74
|
const files = controlled_files !== undefined ? controlled_files : internal_files;
|
|
65
75
|
const is_controlled = controlled_files !== undefined;
|
|
66
|
-
// Use controlled private files if provided, otherwise use internal state
|
|
67
76
|
const private_files = controlled_private_files !== undefined ? controlled_private_files : internal_private_files;
|
|
68
77
|
const is_private_controlled = controlled_private_files !== undefined;
|
|
69
|
-
// Check permission
|
|
78
|
+
// Check permission for private files
|
|
70
79
|
React.useEffect(() => {
|
|
71
|
-
if (!
|
|
80
|
+
if (!accept_files_private) {
|
|
72
81
|
set_permission_loading(false);
|
|
73
82
|
set_has_permission(false);
|
|
74
83
|
return;
|
|
75
84
|
}
|
|
76
|
-
// If permission is provided via prop, use that
|
|
77
85
|
if (can_access_private_files_prop !== undefined) {
|
|
78
86
|
set_has_permission(can_access_private_files_prop);
|
|
79
87
|
set_permission_loading(false);
|
|
80
88
|
return;
|
|
81
89
|
}
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
credentials: 'include',
|
|
88
|
-
});
|
|
89
|
-
if (response.ok) {
|
|
90
|
-
const auth_data = await response.json();
|
|
91
|
-
if (auth_data.authenticated && auth_data.permissions) {
|
|
92
|
-
const has_perm = auth_data.permissions.includes(private_files_permission);
|
|
93
|
-
set_has_permission(has_perm);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
set_has_permission(false);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
set_has_permission(false);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
logger.warn('[CollabFormFileUpload] Error checking permission', {
|
|
105
|
-
permission: private_files_permission,
|
|
106
|
-
error: error instanceof Error ? error.message : String(error),
|
|
107
|
-
});
|
|
108
|
-
set_has_permission(false);
|
|
109
|
-
}
|
|
110
|
-
finally {
|
|
111
|
-
set_permission_loading(false);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
check_permission();
|
|
115
|
-
}, [enable_file_visibility, private_files_permission, can_access_private_files_prop]);
|
|
116
|
-
// Determine if private files section should be shown
|
|
117
|
-
const show_private_section = !!(enable_file_visibility && private_files_permission && has_permission && !permission_loading);
|
|
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]);
|
|
94
|
+
const show_private_section = !!(accept_files_private && has_permission && !permission_loading);
|
|
118
95
|
/**
|
|
119
96
|
* Update files state (public files)
|
|
120
97
|
*/
|
|
121
98
|
const update_files = useCallback((new_files) => {
|
|
122
|
-
// Ensure all files have visibility set to 'public'
|
|
123
|
-
const files_with_visibility = new_files.map(f => ({
|
|
124
|
-
...f,
|
|
125
|
-
visibility: f.visibility || 'public',
|
|
126
|
-
}));
|
|
127
99
|
if (!is_controlled) {
|
|
128
|
-
set_internal_files(
|
|
129
|
-
}
|
|
130
|
-
if (on_files_change) {
|
|
131
|
-
on_files_change(files_with_visibility);
|
|
100
|
+
set_internal_files(new_files);
|
|
132
101
|
}
|
|
102
|
+
on_files_change?.(new_files);
|
|
133
103
|
}, [is_controlled, on_files_change]);
|
|
134
104
|
/**
|
|
135
105
|
* Update private files state
|
|
136
106
|
*/
|
|
137
107
|
const update_private_files = useCallback((new_files) => {
|
|
138
|
-
// Ensure all files have visibility set to 'private'
|
|
139
|
-
const files_with_visibility = new_files.map(f => ({
|
|
140
|
-
...f,
|
|
141
|
-
visibility: 'private',
|
|
142
|
-
}));
|
|
143
108
|
if (!is_private_controlled) {
|
|
144
|
-
set_internal_private_files(
|
|
145
|
-
}
|
|
146
|
-
if (on_private_files_change) {
|
|
147
|
-
on_private_files_change(files_with_visibility);
|
|
109
|
+
set_internal_private_files(new_files);
|
|
148
110
|
}
|
|
111
|
+
on_private_files_change?.(new_files);
|
|
149
112
|
}, [is_private_controlled, on_private_files_change]);
|
|
150
113
|
/**
|
|
151
114
|
* Toggle file visibility (move between public and private)
|
|
152
115
|
*/
|
|
153
116
|
const toggle_file_visibility = useCallback((file_id, current_visibility) => {
|
|
154
117
|
if (current_visibility === 'public') {
|
|
155
|
-
// Move from public to private
|
|
156
118
|
const file_to_move = files.find(f => f.file_id === file_id);
|
|
157
119
|
if (file_to_move) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
update_files(new_public_files);
|
|
161
|
-
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' }]);
|
|
162
122
|
}
|
|
163
123
|
}
|
|
164
124
|
else {
|
|
165
|
-
// Move from private to public
|
|
166
125
|
const file_to_move = private_files.find(f => f.file_id === file_id);
|
|
167
126
|
if (file_to_move) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
update_private_files(new_private_files);
|
|
171
|
-
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' }]);
|
|
172
129
|
}
|
|
173
130
|
}
|
|
174
131
|
}, [files, private_files, update_files, update_private_files]);
|
|
@@ -176,124 +133,68 @@ export function CollabFormFileUpload({ field_id_final, accept_files, files_dir,
|
|
|
176
133
|
* Validate file before upload
|
|
177
134
|
*/
|
|
178
135
|
const validate_file = useCallback((file) => {
|
|
179
|
-
// Run custom validator first (if provided)
|
|
180
136
|
if (file_validator) {
|
|
181
137
|
const custom_error = file_validator(file);
|
|
182
|
-
if (custom_error)
|
|
138
|
+
if (custom_error)
|
|
183
139
|
return custom_error;
|
|
184
|
-
}
|
|
185
140
|
}
|
|
186
|
-
// Check file count
|
|
187
141
|
if (files.length >= max_files) {
|
|
188
142
|
return `Maximum ${max_files} files allowed`;
|
|
189
143
|
}
|
|
190
|
-
// Check file size
|
|
191
144
|
if (max_size && file.size > max_size) {
|
|
192
145
|
return `File size exceeds maximum of ${format_file_size(max_size)}`;
|
|
193
146
|
}
|
|
194
|
-
// Check file type (if file_accept is specified)
|
|
195
147
|
if (file_accept) {
|
|
196
|
-
const accept_patterns = file_accept.split(',').map(
|
|
148
|
+
const accept_patterns = file_accept.split(',').map(p => p.trim());
|
|
197
149
|
const file_extension = '.' + file.name.split('.').pop()?.toLowerCase();
|
|
198
150
|
const file_mime = file.type;
|
|
199
|
-
const matches = accept_patterns.some(
|
|
200
|
-
if (pattern.startsWith('.'))
|
|
201
|
-
// Extension match
|
|
151
|
+
const matches = accept_patterns.some(pattern => {
|
|
152
|
+
if (pattern.startsWith('.'))
|
|
202
153
|
return file_extension === pattern.toLowerCase();
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const base_type = pattern.split('/')[0];
|
|
207
|
-
return file_mime.startsWith(base_type + '/');
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
// Exact MIME type match
|
|
211
|
-
return file_mime === pattern;
|
|
212
|
-
}
|
|
154
|
+
if (pattern.includes('/*'))
|
|
155
|
+
return file_mime.startsWith(pattern.split('/')[0] + '/');
|
|
156
|
+
return file_mime === pattern;
|
|
213
157
|
});
|
|
214
|
-
if (!matches)
|
|
158
|
+
if (!matches)
|
|
215
159
|
return `File type not allowed. Accepted: ${file_accept}`;
|
|
216
|
-
}
|
|
217
160
|
}
|
|
218
161
|
return null;
|
|
219
162
|
}, [files.length, max_files, max_size, file_accept, file_validator]);
|
|
220
163
|
/**
|
|
221
|
-
* Upload file
|
|
164
|
+
* Upload file via file_manager.upload callback
|
|
222
165
|
*/
|
|
223
166
|
const upload_file = useCallback(async (file, visibility = 'public') => {
|
|
224
|
-
const
|
|
225
|
-
const uploading_id = `${file_id}-${file.name}`;
|
|
167
|
+
const uploading_id = `${Date.now()}-${file.name}`;
|
|
226
168
|
try {
|
|
227
|
-
set_uploading_files(
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (files_dir) {
|
|
232
|
-
form_data.append('files_dir', files_dir);
|
|
233
|
-
}
|
|
234
|
-
// Add visibility to the form data
|
|
235
|
-
form_data.append('visibility', visibility);
|
|
236
|
-
// Add required permission for private files (server-side validation)
|
|
237
|
-
if (visibility === 'private' && private_files_permission) {
|
|
238
|
-
form_data.append('required_permission', private_files_permission);
|
|
239
|
-
}
|
|
240
|
-
const response = await fetch(upload_endpoint, {
|
|
241
|
-
method: 'POST',
|
|
242
|
-
body: form_data,
|
|
243
|
-
credentials: 'include',
|
|
244
|
-
});
|
|
245
|
-
if (!response.ok) {
|
|
246
|
-
const error_data = await response.json().catch(() => ({ error: 'Upload failed' }));
|
|
247
|
-
throw new Error(error_data.error || `Upload failed: ${response.statusText}`);
|
|
248
|
-
}
|
|
249
|
-
const result = await response.json();
|
|
250
|
-
const file_data = {
|
|
251
|
-
file_path: result.file_path || result.path || '',
|
|
252
|
-
file_name: file.name,
|
|
253
|
-
file_size: file.size,
|
|
254
|
-
file_type: file.type,
|
|
255
|
-
file_id,
|
|
256
|
-
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,
|
|
257
173
|
visibility,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
await file_processor(file_data, component_ref);
|
|
264
|
-
set_processed_files((prev) => new Set(prev).add(file_data.file_id));
|
|
265
|
-
}
|
|
266
|
-
finally {
|
|
267
|
-
set_processing_files((prev) => {
|
|
268
|
-
const next = new Set(prev);
|
|
269
|
-
next.delete(file_data.file_id);
|
|
270
|
-
return next;
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
set_upload_errors((prev) => {
|
|
174
|
+
scope_id,
|
|
175
|
+
uploaded_by,
|
|
176
|
+
metadata: files_dir ? { files_dir } : undefined,
|
|
177
|
+
});
|
|
178
|
+
set_upload_errors(prev => {
|
|
275
179
|
const next = { ...prev };
|
|
276
180
|
delete next[uploading_id];
|
|
277
181
|
return next;
|
|
278
182
|
});
|
|
279
|
-
return
|
|
183
|
+
return attachment;
|
|
280
184
|
}
|
|
281
185
|
catch (error) {
|
|
282
186
|
const error_message = error instanceof Error ? error.message : 'Upload failed';
|
|
283
|
-
set_upload_errors(
|
|
284
|
-
...prev,
|
|
285
|
-
[uploading_id]: error_message,
|
|
286
|
-
}));
|
|
187
|
+
set_upload_errors(prev => ({ ...prev, [uploading_id]: error_message }));
|
|
287
188
|
return null;
|
|
288
189
|
}
|
|
289
190
|
finally {
|
|
290
|
-
set_uploading_files(
|
|
191
|
+
set_uploading_files(prev => {
|
|
291
192
|
const next = new Set(prev);
|
|
292
193
|
next.delete(uploading_id);
|
|
293
194
|
return next;
|
|
294
195
|
});
|
|
295
196
|
}
|
|
296
|
-
}, [field_id_final,
|
|
197
|
+
}, [field_id_final, file_manager, files_dir, scope_id, uploaded_by]);
|
|
297
198
|
/**
|
|
298
199
|
* Handle file selection (public files)
|
|
299
200
|
*/
|
|
@@ -302,8 +203,7 @@ export function CollabFormFileUpload({ field_id_final, accept_files, files_dir,
|
|
|
302
203
|
return;
|
|
303
204
|
const files_to_upload = [];
|
|
304
205
|
const errors = {};
|
|
305
|
-
|
|
306
|
-
Array.from(selected_files).forEach((file) => {
|
|
206
|
+
Array.from(selected_files).forEach(file => {
|
|
307
207
|
const error = validate_file(file);
|
|
308
208
|
if (error) {
|
|
309
209
|
errors[file.name] = error;
|
|
@@ -312,16 +212,14 @@ export function CollabFormFileUpload({ field_id_final, accept_files, files_dir,
|
|
|
312
212
|
files_to_upload.push(file);
|
|
313
213
|
}
|
|
314
214
|
});
|
|
315
|
-
// Set validation errors
|
|
316
215
|
if (Object.keys(errors).length > 0) {
|
|
317
|
-
set_upload_errors(
|
|
216
|
+
set_upload_errors(prev => ({ ...prev, ...errors }));
|
|
318
217
|
}
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
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]);
|
|
325
223
|
}
|
|
326
224
|
}, [files, validate_file, upload_file, update_files]);
|
|
327
225
|
/**
|
|
@@ -332,8 +230,7 @@ export function CollabFormFileUpload({ field_id_final, accept_files, files_dir,
|
|
|
332
230
|
return;
|
|
333
231
|
const files_to_upload = [];
|
|
334
232
|
const errors = {};
|
|
335
|
-
|
|
336
|
-
Array.from(selected_files).forEach((file) => {
|
|
233
|
+
Array.from(selected_files).forEach(file => {
|
|
337
234
|
const error = validate_file(file);
|
|
338
235
|
if (error) {
|
|
339
236
|
errors[file.name] = error;
|
|
@@ -342,21 +239,17 @@ export function CollabFormFileUpload({ field_id_final, accept_files, files_dir,
|
|
|
342
239
|
files_to_upload.push(file);
|
|
343
240
|
}
|
|
344
241
|
});
|
|
345
|
-
// Set validation errors
|
|
346
242
|
if (Object.keys(errors).length > 0) {
|
|
347
|
-
set_upload_errors(
|
|
243
|
+
set_upload_errors(prev => ({ ...prev, ...errors }));
|
|
348
244
|
}
|
|
349
|
-
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
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]);
|
|
355
250
|
}
|
|
356
251
|
}, [private_files, validate_file, upload_file, update_private_files]);
|
|
357
|
-
|
|
358
|
-
* Handle drag and drop
|
|
359
|
-
*/
|
|
252
|
+
// Drag handlers (public)
|
|
360
253
|
const handle_drag_enter = useCallback((e) => {
|
|
361
254
|
e.preventDefault();
|
|
362
255
|
e.stopPropagation();
|
|
@@ -377,9 +270,7 @@ export function CollabFormFileUpload({ field_id_final, accept_files, files_dir,
|
|
|
377
270
|
set_is_dragging(false);
|
|
378
271
|
handle_files_selected(e.dataTransfer.files);
|
|
379
272
|
}, [handle_files_selected]);
|
|
380
|
-
|
|
381
|
-
* Handle private drag and drop
|
|
382
|
-
*/
|
|
273
|
+
// Drag handlers (private)
|
|
383
274
|
const handle_private_drag_enter = useCallback((e) => {
|
|
384
275
|
e.preventDefault();
|
|
385
276
|
e.stopPropagation();
|
|
@@ -396,102 +287,136 @@ export function CollabFormFileUpload({ field_id_final, accept_files, files_dir,
|
|
|
396
287
|
set_is_dragging_private(false);
|
|
397
288
|
handle_private_files_selected(e.dataTransfer.files);
|
|
398
289
|
}, [handle_private_files_selected]);
|
|
399
|
-
|
|
400
|
-
* Handle file input change
|
|
401
|
-
*/
|
|
290
|
+
// Input handlers
|
|
402
291
|
const handle_input_change = useCallback((e) => {
|
|
403
292
|
handle_files_selected(e.target.files);
|
|
404
|
-
|
|
405
|
-
if (file_input_ref.current) {
|
|
293
|
+
if (file_input_ref.current)
|
|
406
294
|
file_input_ref.current.value = '';
|
|
407
|
-
}
|
|
408
295
|
}, [handle_files_selected]);
|
|
409
|
-
/**
|
|
410
|
-
* Handle private file input change
|
|
411
|
-
*/
|
|
412
296
|
const handle_private_input_change = useCallback((e) => {
|
|
413
297
|
handle_private_files_selected(e.target.files);
|
|
414
|
-
|
|
415
|
-
if (private_file_input_ref.current) {
|
|
298
|
+
if (private_file_input_ref.current)
|
|
416
299
|
private_file_input_ref.current.value = '';
|
|
417
|
-
}
|
|
418
300
|
}, [handle_private_files_selected]);
|
|
419
301
|
/**
|
|
420
|
-
* Handle file delete
|
|
302
|
+
* Handle file delete — calls file_manager.remove then updates state
|
|
421
303
|
*/
|
|
422
|
-
const handle_delete_file = useCallback((file_id) => {
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
delete_error: `Minimum ${min_files} files required`,
|
|
429
|
-
}));
|
|
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` }));
|
|
430
310
|
return;
|
|
431
311
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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]);
|
|
439
324
|
/**
|
|
440
325
|
* Handle private file delete
|
|
441
326
|
*/
|
|
442
|
-
const handle_delete_private_file = useCallback((file_id) => {
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
*/
|
|
449
|
-
const get_file_url = useCallback((file_path, visibility) => {
|
|
450
|
-
if (file_path.startsWith('/')) {
|
|
451
|
-
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);
|
|
452
333
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
+
});
|
|
456
339
|
}
|
|
457
|
-
|
|
458
|
-
}, [
|
|
459
|
-
|
|
460
|
-
|
|
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
|
|
398
|
+
const accordion_title = accept_files_public && show_private_section
|
|
461
399
|
? `Files (${files.length} public, ${private_files.length} private)`
|
|
462
|
-
:
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
return (_jsx("div", { className: "cls_collab_file_upload space-y-2", suppressHydrationWarning: true, children:
|
|
466
|
-
/* Dynamic import for Accordion - will be loaded by consuming app */
|
|
467
|
-
_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,
|
|
468
|
-
// Private files props
|
|
469
|
-
enable_file_visibility: enable_file_visibility, 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 }));
|
|
400
|
+
: show_private_section && !accept_files_public
|
|
401
|
+
? private_files.length > 0 ? `Private Files (${private_files.length})` : 'Private Files'
|
|
402
|
+
: files.length > 0 ? `Files (${files.length})` : 'Files';
|
|
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 }));
|
|
470
404
|
}
|
|
471
405
|
/**
|
|
472
406
|
* File upload accordion component with dynamic imports
|
|
473
407
|
*/
|
|
474
|
-
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,
|
|
475
|
-
// Private files props
|
|
476
|
-
enable_file_visibility = 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, }) {
|
|
477
409
|
const logger = use_logger();
|
|
478
410
|
const [AccordionComponents, set_accordion_components] = React.useState(null);
|
|
479
411
|
const [is_loading, set_is_loading] = React.useState(true);
|
|
480
412
|
const [is_mounted, set_is_mounted] = React.useState(false);
|
|
481
|
-
|
|
482
|
-
React.useEffect(() => {
|
|
483
|
-
set_is_mounted(true);
|
|
484
|
-
}, []);
|
|
413
|
+
React.useEffect(() => { set_is_mounted(true); }, []);
|
|
485
414
|
React.useEffect(() => {
|
|
486
|
-
// Only load accordion on client side
|
|
487
415
|
if (!is_mounted)
|
|
488
416
|
return;
|
|
489
|
-
// Dynamic import - components will be resolved by consuming app's bundler
|
|
490
417
|
const load_accordion = async () => {
|
|
491
418
|
try {
|
|
492
419
|
set_is_loading(true);
|
|
493
|
-
// Try to import from consuming app's components directory
|
|
494
|
-
// This path will be resolved by Next.js/webpack in the consuming app
|
|
495
420
|
// @ts-expect-error - These modules are provided by the consuming application
|
|
496
421
|
const accordion_module = await import('@/components/ui/accordion').catch(() => null);
|
|
497
422
|
if (accordion_module) {
|
|
@@ -519,40 +444,31 @@ enable_file_visibility = false, show_private_section = false, private_files = []
|
|
|
519
444
|
};
|
|
520
445
|
load_accordion();
|
|
521
446
|
}, [is_mounted]);
|
|
522
|
-
// Always render consistent structure to prevent hydration mismatch
|
|
523
|
-
// During SSR and initial client render, show empty placeholder
|
|
524
|
-
// After mount and accordion load, show actual content
|
|
525
447
|
if (!is_mounted || is_loading || !AccordionComponents) {
|
|
526
448
|
return (_jsx("div", { className: "cls_collab_file_upload_loading text-sm text-muted-foreground", suppressHydrationWarning: true }));
|
|
527
449
|
}
|
|
528
450
|
const { Accordion, AccordionItem, AccordionTrigger, AccordionContent } = AccordionComponents;
|
|
529
|
-
// Default to open if files exist
|
|
530
451
|
const default_value = files.length > 0 ? 'files' : undefined;
|
|
531
|
-
// Determine if files are required (min_files >= 1)
|
|
532
452
|
const is_files_required = min_files !== undefined && min_files >= 1;
|
|
533
453
|
/**
|
|
534
|
-
* Render a file item
|
|
454
|
+
* Render a single file item
|
|
535
455
|
*/
|
|
536
|
-
const render_file_item = (
|
|
537
|
-
const is_uploading = Array.from(uploading_files).some(
|
|
538
|
-
const
|
|
539
|
-
const
|
|
540
|
-
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'
|
|
541
461
|
? "text-amber-600 hover:text-amber-700"
|
|
542
|
-
: "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));
|
|
543
463
|
};
|
|
544
464
|
/**
|
|
545
465
|
* Render file upload section (drop zone + file list)
|
|
546
466
|
*/
|
|
547
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
|
|
548
468
|
? 'border-primary bg-primary/5'
|
|
549
|
-
: '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(
|
|
550
|
-
|
|
551
|
-
// The Accordion content may differ between server and client due to dynamic imports
|
|
552
|
-
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))) })), enable_file_visibility && 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') })] })] })) : (
|
|
553
|
-
/* Single-section UI (original behavior) */
|
|
554
|
-
_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
|
|
555
471
|
? 'border-primary bg-primary/5'
|
|
556
|
-
: '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] }) })] }) }) }));
|
|
557
473
|
}
|
|
558
474
|
//# sourceMappingURL=collab_form_file_upload.js.map
|