128 lines
3.0 KiB
TypeScript
128 lines
3.0 KiB
TypeScript
import { useState, useEffect, useRef } from 'react';
|
|
import {
|
|
TextField,
|
|
Select,
|
|
MenuItem,
|
|
Box,
|
|
ClickAwayListener,
|
|
} from '@mui/material';
|
|
import type { Idea } from '../../types/idea';
|
|
import { useUpdateIdea } from '../../hooks/useIdeas';
|
|
|
|
interface EditableCellProps {
|
|
idea: Idea;
|
|
field: keyof Idea;
|
|
value: string | null;
|
|
type?: 'text' | 'select';
|
|
options?: { value: string; label: string }[];
|
|
renderDisplay: (value: string | null) => React.ReactNode;
|
|
}
|
|
|
|
export function EditableCell({
|
|
idea,
|
|
field,
|
|
value,
|
|
type = 'text',
|
|
options,
|
|
renderDisplay,
|
|
}: EditableCellProps) {
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [editValue, setEditValue] = useState(value ?? '');
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const updateIdea = useUpdateIdea();
|
|
|
|
useEffect(() => {
|
|
if (isEditing && inputRef.current) {
|
|
inputRef.current.focus();
|
|
inputRef.current.select();
|
|
}
|
|
}, [isEditing]);
|
|
|
|
const handleDoubleClick = () => {
|
|
setIsEditing(true);
|
|
setEditValue(value ?? '');
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
setIsEditing(false);
|
|
if (editValue !== value) {
|
|
await updateIdea.mutateAsync({
|
|
id: idea.id,
|
|
dto: { [field]: editValue || null },
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
if (e.key === 'Enter') {
|
|
void handleSave();
|
|
} else if (e.key === 'Escape') {
|
|
setIsEditing(false);
|
|
setEditValue(value ?? '');
|
|
}
|
|
};
|
|
|
|
if (isEditing) {
|
|
if (type === 'select' && options) {
|
|
return (
|
|
<ClickAwayListener onClickAway={handleSave}>
|
|
<Select
|
|
size="small"
|
|
value={editValue}
|
|
onChange={(e) => {
|
|
setEditValue(e.target.value);
|
|
setTimeout(() => {
|
|
updateIdea.mutate({
|
|
id: idea.id,
|
|
dto: { [field]: e.target.value },
|
|
});
|
|
setIsEditing(false);
|
|
}, 0);
|
|
}}
|
|
onKeyDown={handleKeyDown}
|
|
autoFocus
|
|
sx={{ minWidth: 100 }}
|
|
MenuProps={{ disablePortal: true }}
|
|
>
|
|
{options.map((opt) => (
|
|
<MenuItem key={opt.value} value={opt.value}>
|
|
{opt.label}
|
|
</MenuItem>
|
|
))}
|
|
</Select>
|
|
</ClickAwayListener>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ClickAwayListener onClickAway={handleSave}>
|
|
<TextField
|
|
inputRef={inputRef}
|
|
size="small"
|
|
value={editValue}
|
|
onChange={(e) => setEditValue(e.target.value)}
|
|
onKeyDown={handleKeyDown}
|
|
onBlur={handleSave}
|
|
sx={{ minWidth: 100 }}
|
|
/>
|
|
</ClickAwayListener>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box
|
|
onDoubleClick={handleDoubleClick}
|
|
sx={{
|
|
cursor: 'pointer',
|
|
minHeight: 24,
|
|
'&:hover': {
|
|
backgroundColor: 'action.hover',
|
|
borderRadius: 0.5,
|
|
},
|
|
}}
|
|
>
|
|
{renderDisplay(value)}
|
|
</Box>
|
|
);
|
|
}
|