This commit is contained in:
@ -69,7 +69,12 @@ export function CreateIdeaModal() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={createModalOpen} onClose={handleClose} maxWidth="sm" fullWidth>
|
||||
<Dialog
|
||||
open={createModalOpen}
|
||||
onClose={handleClose}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<DialogTitle>Create New Idea</DialogTitle>
|
||||
<DialogContent>
|
||||
@ -163,7 +168,9 @@ export function CreateIdeaModal() {
|
||||
<TextField
|
||||
label="Verification Method"
|
||||
value={formData.verificationMethod}
|
||||
onChange={(e) => handleChange('verificationMethod', e.target.value)}
|
||||
onChange={(e) =>
|
||||
handleChange('verificationMethod', e.target.value)
|
||||
}
|
||||
multiline
|
||||
rows={2}
|
||||
placeholder="How to verify this is done?"
|
||||
|
||||
@ -32,7 +32,7 @@ const priorityOptions: { value: IdeaPriority; label: string }[] = [
|
||||
export function IdeasFilters() {
|
||||
const { filters, setFilter, clearFilters } = useIdeasStore();
|
||||
const { data: modules = [] } = useModulesQuery();
|
||||
const [searchValue, setSearchValue] = useState(filters.search || '');
|
||||
const [searchValue, setSearchValue] = useState(filters.search ?? '');
|
||||
|
||||
// Debounced search
|
||||
useEffect(() => {
|
||||
@ -42,31 +42,40 @@ export function IdeasFilters() {
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchValue, setFilter]);
|
||||
|
||||
const hasFilters = filters.status || filters.priority || filters.module || filters.search;
|
||||
const hasFilters = Boolean(
|
||||
filters.status ?? filters.priority ?? filters.module ?? filters.search,
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<Box
|
||||
sx={{ display: 'flex', gap: 2, flexWrap: 'wrap', alignItems: 'center' }}
|
||||
>
|
||||
<TextField
|
||||
size="small"
|
||||
placeholder="Search ideas..."
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
sx={{ minWidth: 200 }}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Search fontSize="small" />
|
||||
</InputAdornment>
|
||||
),
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Search fontSize="small" />
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<FormControl size="small" sx={{ minWidth: 120 }}>
|
||||
<InputLabel>Status</InputLabel>
|
||||
<Select
|
||||
value={filters.status || ''}
|
||||
<Select<IdeaStatus | ''>
|
||||
value={filters.status ?? ''}
|
||||
label="Status"
|
||||
onChange={(e) => setFilter('status', e.target.value as IdeaStatus || undefined)}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value;
|
||||
setFilter('status', val === '' ? undefined : val);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{statusOptions.map((opt) => (
|
||||
@ -79,10 +88,13 @@ export function IdeasFilters() {
|
||||
|
||||
<FormControl size="small" sx={{ minWidth: 120 }}>
|
||||
<InputLabel>Priority</InputLabel>
|
||||
<Select
|
||||
value={filters.priority || ''}
|
||||
<Select<IdeaPriority | ''>
|
||||
value={filters.priority ?? ''}
|
||||
label="Priority"
|
||||
onChange={(e) => setFilter('priority', e.target.value as IdeaPriority || undefined)}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value;
|
||||
setFilter('priority', val === '' ? undefined : val);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{priorityOptions.map((opt) => (
|
||||
@ -96,7 +108,7 @@ export function IdeasFilters() {
|
||||
<FormControl size="small" sx={{ minWidth: 120 }}>
|
||||
<InputLabel>Module</InputLabel>
|
||||
<Select
|
||||
value={filters.module || ''}
|
||||
value={filters.module ?? ''}
|
||||
label="Module"
|
||||
onChange={(e) => setFilter('module', e.target.value || undefined)}
|
||||
>
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
Box,
|
||||
ClickAwayListener,
|
||||
} from '@mui/material';
|
||||
import type { Idea, IdeaStatus, IdeaPriority } from '../../types/idea';
|
||||
import type { Idea } from '../../types/idea';
|
||||
import { useUpdateIdea } from '../../hooks/useIdeas';
|
||||
|
||||
interface EditableCellProps {
|
||||
@ -27,7 +27,7 @@ export function EditableCell({
|
||||
renderDisplay,
|
||||
}: EditableCellProps) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editValue, setEditValue] = useState(value || '');
|
||||
const [editValue, setEditValue] = useState(value ?? '');
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const updateIdea = useUpdateIdea();
|
||||
|
||||
@ -40,7 +40,7 @@ export function EditableCell({
|
||||
|
||||
const handleDoubleClick = () => {
|
||||
setIsEditing(true);
|
||||
setEditValue(value || '');
|
||||
setEditValue(value ?? '');
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
@ -55,10 +55,10 @@ export function EditableCell({
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSave();
|
||||
void handleSave();
|
||||
} else if (e.key === 'Escape') {
|
||||
setIsEditing(false);
|
||||
setEditValue(value || '');
|
||||
setEditValue(value ?? '');
|
||||
}
|
||||
};
|
||||
|
||||
@ -124,20 +124,3 @@ export function EditableCell({
|
||||
</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' },
|
||||
];
|
||||
|
||||
@ -28,13 +28,15 @@ const SKELETON_COLUMNS_COUNT = 7;
|
||||
export function IdeasTable() {
|
||||
const { data, isLoading, isError } = useIdeasQuery();
|
||||
const deleteIdea = useDeleteIdea();
|
||||
const { sorting, setSorting, pagination, setPage, setLimit } = useIdeasStore();
|
||||
const { sorting, setSorting, pagination, setPage, setLimit } =
|
||||
useIdeasStore();
|
||||
|
||||
const columns = useMemo(
|
||||
() => createColumns((id) => deleteIdea.mutate(id)),
|
||||
[deleteIdea]
|
||||
[deleteIdea],
|
||||
);
|
||||
|
||||
// eslint-disable-next-line react-hooks/incompatible-library
|
||||
const table = useReactTable({
|
||||
data: data?.data ?? [],
|
||||
columns,
|
||||
@ -51,7 +53,9 @@ export function IdeasTable() {
|
||||
setPage(newPage + 1);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
) => {
|
||||
setLimit(parseInt(event.target.value, 10));
|
||||
};
|
||||
|
||||
@ -84,20 +88,22 @@ export function IdeasTable() {
|
||||
active={sorting.sortBy === header.id}
|
||||
direction={
|
||||
sorting.sortBy === header.id
|
||||
? (sorting.sortOrder.toLowerCase() as 'asc' | 'desc')
|
||||
? (sorting.sortOrder.toLowerCase() as
|
||||
| 'asc'
|
||||
| 'desc')
|
||||
: 'asc'
|
||||
}
|
||||
onClick={() => handleSort(header.id)}
|
||||
>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableSortLabel>
|
||||
) : (
|
||||
flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
header.getContext(),
|
||||
)
|
||||
)}
|
||||
</TableCell>
|
||||
@ -109,11 +115,13 @@ export function IdeasTable() {
|
||||
{isLoading ? (
|
||||
Array.from({ length: 5 }).map((_, index) => (
|
||||
<TableRow key={index}>
|
||||
{Array.from({ length: SKELETON_COLUMNS_COUNT }).map((_, colIndex) => (
|
||||
<TableCell key={colIndex}>
|
||||
<Skeleton variant="text" />
|
||||
</TableCell>
|
||||
))}
|
||||
{Array.from({ length: SKELETON_COLUMNS_COUNT }).map(
|
||||
(_, colIndex) => (
|
||||
<TableCell key={colIndex}>
|
||||
<Skeleton variant="text" />
|
||||
</TableCell>
|
||||
),
|
||||
)}
|
||||
</TableRow>
|
||||
))
|
||||
) : table.getRowModel().rows.length === 0 ? (
|
||||
@ -156,7 +164,7 @@ export function IdeasTable() {
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
|
||||
@ -2,11 +2,15 @@ import { createColumnHelper } from '@tanstack/react-table';
|
||||
import { Chip, Box, IconButton } from '@mui/material';
|
||||
import { Delete } from '@mui/icons-material';
|
||||
import type { Idea, IdeaStatus, IdeaPriority } from '../../types/idea';
|
||||
import { EditableCell, statusOptions, priorityOptions } from './EditableCell';
|
||||
import { EditableCell } from './EditableCell';
|
||||
import { statusOptions, priorityOptions } from './constants';
|
||||
|
||||
const columnHelper = createColumnHelper<Idea>();
|
||||
|
||||
const statusColors: Record<IdeaStatus, 'default' | 'primary' | 'secondary' | 'success' | 'error'> = {
|
||||
const statusColors: Record<
|
||||
IdeaStatus,
|
||||
'default' | 'primary' | 'secondary' | 'success' | 'error'
|
||||
> = {
|
||||
backlog: 'default',
|
||||
todo: 'primary',
|
||||
in_progress: 'secondary',
|
||||
@ -14,7 +18,10 @@ const statusColors: Record<IdeaStatus, 'default' | 'primary' | 'secondary' | 'su
|
||||
cancelled: 'error',
|
||||
};
|
||||
|
||||
const priorityColors: Record<IdeaPriority, 'default' | 'info' | 'warning' | 'error'> = {
|
||||
const priorityColors: Record<
|
||||
IdeaPriority,
|
||||
'default' | 'info' | 'warning' | 'error'
|
||||
> = {
|
||||
low: 'default',
|
||||
medium: 'info',
|
||||
high: 'warning',
|
||||
@ -30,7 +37,7 @@ export const createColumns = (onDelete: (id: string) => void) => [
|
||||
field="title"
|
||||
value={info.getValue()}
|
||||
renderDisplay={(value) => (
|
||||
<Box sx={{ fontWeight: 500 }}>{value || '—'}</Box>
|
||||
<Box sx={{ fontWeight: 500 }}>{value ?? '—'}</Box>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
@ -40,7 +47,8 @@ export const createColumns = (onDelete: (id: string) => void) => [
|
||||
header: 'Status',
|
||||
cell: (info) => {
|
||||
const status = info.getValue();
|
||||
const label = statusOptions.find((s) => s.value === status)?.label || status;
|
||||
const label =
|
||||
statusOptions.find((s) => s.value === status)?.label ?? status;
|
||||
return (
|
||||
<EditableCell
|
||||
idea={info.row.original}
|
||||
@ -60,7 +68,8 @@ export const createColumns = (onDelete: (id: string) => void) => [
|
||||
header: 'Priority',
|
||||
cell: (info) => {
|
||||
const priority = info.getValue();
|
||||
const label = priorityOptions.find((p) => p.value === priority)?.label || priority;
|
||||
const label =
|
||||
priorityOptions.find((p) => p.value === priority)?.label ?? priority;
|
||||
return (
|
||||
<EditableCell
|
||||
idea={info.row.original}
|
||||
@ -88,7 +97,7 @@ export const createColumns = (onDelete: (id: string) => void) => [
|
||||
idea={info.row.original}
|
||||
field="module"
|
||||
value={info.getValue()}
|
||||
renderDisplay={(value) => value || '—'}
|
||||
renderDisplay={(value) => value ?? '—'}
|
||||
/>
|
||||
),
|
||||
size: 120,
|
||||
@ -100,7 +109,7 @@ export const createColumns = (onDelete: (id: string) => void) => [
|
||||
idea={info.row.original}
|
||||
field="targetAudience"
|
||||
value={info.getValue()}
|
||||
renderDisplay={(value) => value || '—'}
|
||||
renderDisplay={(value) => value ?? '—'}
|
||||
/>
|
||||
),
|
||||
size: 150,
|
||||
|
||||
16
frontend/src/components/IdeasTable/constants.ts
Normal file
16
frontend/src/components/IdeasTable/constants.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { IdeaStatus, IdeaPriority } from '../../types/idea';
|
||||
|
||||
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' },
|
||||
];
|
||||
|
||||
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