251 lines
7.8 KiB
TypeScript
251 lines
7.8 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { MdModeEdit } from 'react-icons/md';
|
|
import { useLanguage } from '../../../../contexts/LanguageContext';
|
|
import { Popup, EditForm } from '../../../Popup';
|
|
import styles from '../ActionButton.module.css';
|
|
|
|
export interface EditActionButtonProps<T = any> {
|
|
row: T;
|
|
onEdit?: (row: T) => void;
|
|
disabled?: boolean | { disabled: boolean; message?: string };
|
|
loading?: boolean;
|
|
className?: string;
|
|
title?: string;
|
|
isEditing?: boolean;
|
|
hookData: any; // REQUIRED: Contains all hook data including operations
|
|
// Field mappings
|
|
idField?: string; // Field name for the unique identifier
|
|
nameField?: string; // Field name for display name
|
|
typeField?: string; // Field name for type/mime type
|
|
operationName?: string; // Name of the operation function in hookData
|
|
loadingStateName?: string; // Name of the loading state in hookData
|
|
// Edit configuration
|
|
editFields?: Array<{
|
|
key: string;
|
|
label: string;
|
|
type: 'string' | 'boolean' | 'email' | 'textarea' | 'date' | 'enum' | 'readonly';
|
|
editable?: boolean;
|
|
required?: boolean;
|
|
validator?: (value: any) => string | null;
|
|
}>;
|
|
}
|
|
|
|
export function EditActionButton<T = any>({
|
|
row,
|
|
onEdit,
|
|
disabled = false,
|
|
loading = false,
|
|
className = '',
|
|
title,
|
|
isEditing = false,
|
|
hookData,
|
|
idField = 'id',
|
|
nameField = 'name',
|
|
typeField = 'type',
|
|
operationName = 'handleFileUpdate',
|
|
loadingStateName = 'editingFiles',
|
|
editFields = [
|
|
{
|
|
key: 'file_name',
|
|
label: 'Filename',
|
|
type: 'string',
|
|
editable: true,
|
|
required: true,
|
|
validator: (value: string) => {
|
|
if (!value || value.trim() === '') {
|
|
return 'Filename cannot be empty';
|
|
}
|
|
if (value.includes('/') || value.includes('\\')) {
|
|
return 'Filename cannot contain / or \\ characters';
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
]
|
|
}: EditActionButtonProps<T>) {
|
|
const { t } = useLanguage();
|
|
const [internalLoading, setInternalLoading] = useState(false);
|
|
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
|
const [editData, setEditData] = useState<T | null>(null);
|
|
|
|
// Extract disabled state and tooltip message
|
|
const isDisabled = typeof disabled === 'boolean' ? disabled : disabled?.disabled || false;
|
|
const disabledMessage = typeof disabled === 'object' ? disabled?.message : undefined;
|
|
|
|
// Debug logging for disabled state
|
|
if (import.meta.env.DEV) {
|
|
console.log('EditActionButton disabled prop:', disabled);
|
|
console.log('EditActionButton isDisabled:', isDisabled);
|
|
console.log('EditActionButton disabledMessage:', disabledMessage);
|
|
console.log('EditActionButton row:', row);
|
|
}
|
|
|
|
// Validate that hookData is provided
|
|
if (!hookData) {
|
|
throw new Error('EditActionButton requires hookData to be provided');
|
|
}
|
|
|
|
const handleClick = async (e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
if (!isDisabled && !loading && !isEditing && !internalLoading) {
|
|
setInternalLoading(true);
|
|
try {
|
|
// Debug logging to see what data we're working with
|
|
if (import.meta.env.DEV) {
|
|
console.log('EditActionButton received row:', row);
|
|
console.log('Field mappings:', { idField, nameField, typeField });
|
|
console.log('Extracted values:', {
|
|
id: (row as any)[idField],
|
|
name: (row as any)[nameField],
|
|
type: (row as any)[typeField]
|
|
});
|
|
}
|
|
|
|
// Call the onEdit callback if provided
|
|
if (onEdit) {
|
|
await onEdit(row);
|
|
}
|
|
|
|
// Set up edit data and open popup
|
|
setEditData(row);
|
|
setIsPopupOpen(true);
|
|
} finally {
|
|
setInternalLoading(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleSave = async (updatedData: T) => {
|
|
if (!editData) return;
|
|
|
|
try {
|
|
setInternalLoading(true);
|
|
|
|
// Get the item ID from the row
|
|
const itemId = (editData as any)[idField];
|
|
|
|
// Extract the fields to update from the edit data
|
|
const updateData: any = {};
|
|
editFields.forEach(field => {
|
|
if (field.editable !== false) {
|
|
// Map frontend field names to API field names
|
|
if (field.key === 'file_name') {
|
|
updateData.fileName = (updatedData as any)[field.key];
|
|
} else {
|
|
updateData[field.key] = (updatedData as any)[field.key];
|
|
}
|
|
}
|
|
});
|
|
|
|
// Debug logging
|
|
if (import.meta.env.DEV) {
|
|
console.log('EditActionButton - Update data:', {
|
|
itemId,
|
|
updateData,
|
|
originalData: editData,
|
|
updatedData,
|
|
editFields: editFields.map(f => ({ key: f.key, value: (updatedData as any)[f.key] })),
|
|
updateDataKeys: Object.keys(updateData),
|
|
updateDataValues: Object.values(updateData)
|
|
});
|
|
}
|
|
|
|
// Validate required operation exists
|
|
if (!hookData[operationName]) {
|
|
throw new Error(`EditActionButton requires hookData.${operationName} to be defined`);
|
|
}
|
|
if (!hookData.refetch) {
|
|
throw new Error('EditActionButton requires hookData.refetch to be defined');
|
|
}
|
|
|
|
// Use hookData operation to update
|
|
const result = await hookData[operationName](itemId, updateData, editData);
|
|
const success = result?.success || result === true;
|
|
|
|
if (success) {
|
|
// Close popup and reset state
|
|
setIsPopupOpen(false);
|
|
setEditData(null);
|
|
|
|
// Trigger refetch to sync with backend
|
|
await hookData.refetch();
|
|
} else {
|
|
console.error('Failed to update item:', itemId);
|
|
// TODO: Show error message to user
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to update item:', error);
|
|
// TODO: Show error message to user
|
|
} finally {
|
|
setInternalLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setIsPopupOpen(false);
|
|
setEditData(null);
|
|
};
|
|
|
|
const buttonTitle = title || t('files.action.edit', 'Edit');
|
|
// Use hookData editing state if available, otherwise use passed isEditing
|
|
const loadingState = hookData?.[loadingStateName];
|
|
const actualIsEditing = loadingState?.has((row as any)[idField]) || isEditing;
|
|
const isLoading = loading || actualIsEditing || internalLoading;
|
|
|
|
// Determine the final button title (tooltip)
|
|
const finalTitle = isDisabled && disabledMessage ? disabledMessage : buttonTitle;
|
|
|
|
// Debug logging for button rendering
|
|
if (import.meta.env.DEV) {
|
|
console.log('EditActionButton rendering with:', {
|
|
isDisabled,
|
|
isLoading,
|
|
finalTitle,
|
|
className: `${styles.actionButton} ${styles.edit} ${isLoading ? styles.loading : ''} ${isDisabled ? styles.disabled : ''} ${className}`
|
|
});
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<button
|
|
onClick={handleClick}
|
|
className={`${styles.actionButton} ${styles.edit} ${isLoading ? styles.loading : ''} ${isDisabled ? styles.disabled : ''} ${className}`}
|
|
title={finalTitle}
|
|
disabled={isDisabled || isLoading}
|
|
>
|
|
<span className={styles.actionIcon}>
|
|
{isLoading ? '⏳' : <MdModeEdit />}
|
|
</span>
|
|
</button>
|
|
|
|
{/* Edit Popup */}
|
|
<Popup
|
|
isOpen={isPopupOpen}
|
|
title={t('files.edit.title', 'Edit Item')}
|
|
onClose={handleCancel}
|
|
size="small"
|
|
closable={true}
|
|
>
|
|
{editData && (
|
|
<EditForm
|
|
data={editData}
|
|
fields={editFields.map(field => ({
|
|
key: field.key,
|
|
label: field.label,
|
|
type: field.type,
|
|
editable: field.editable ?? true,
|
|
required: field.required ?? false,
|
|
validator: field.validator
|
|
}))}
|
|
onSave={handleSave}
|
|
onCancel={handleCancel}
|
|
saveButtonText={t('common.save', 'Save')}
|
|
cancelButtonText={t('common.cancel', 'Cancel')}
|
|
/>
|
|
)}
|
|
</Popup>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default EditActionButton;
|