frontend_nyla/src/components/FormGenerator/ActionButtons/EditActionButton/EditActionButton.tsx

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;