init
This commit is contained in:
143
frontend/src/components/IdeasTable/EditableCell.tsx
Normal file
143
frontend/src/components/IdeasTable/EditableCell.tsx
Normal file
@ -0,0 +1,143 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
TextField,
|
||||
Select,
|
||||
MenuItem,
|
||||
Box,
|
||||
ClickAwayListener,
|
||||
} from '@mui/material';
|
||||
import type { Idea, IdeaStatus, IdeaPriority } 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') {
|
||||
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 }}
|
||||
>
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
// Status options
|
||||
export const statusOptions: { value: IdeaStatus; label: string }[] = [
|
||||
{ value: 'backlog', label: 'Backlog' },
|
||||
{ value: 'todo', label: 'To Do' },
|
||||
{ value: 'in_progress', label: 'In Progress' },
|
||||
{ value: 'done', label: 'Done' },
|
||||
{ value: 'cancelled', label: 'Cancelled' },
|
||||
];
|
||||
|
||||
// Priority options
|
||||
export const priorityOptions: { value: IdeaPriority; label: string }[] = [
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'high', label: 'High' },
|
||||
{ value: 'critical', label: 'Critical' },
|
||||
];
|
||||
Reference in New Issue
Block a user