feat(KB-127): add Quick Entry box to ListView for rapid task creation

- Add QuickEntryBox component to ListView for inline task creation
- Wire up Quick Create functionality in App.tsx
- Add comprehensive tests for Quick Entry feature in ListView
- Remove legacy TaskDetailModal edit button and related styles
- Clean up unused store methods and update vitest configs
This commit is contained in:
gsxdsm
2026-03-30 08:12:45 -07:00
parent 9bcba4db49
commit e540019b36
4 changed files with 113 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ import type { Task, TaskDetail, Column, TaskStep, TaskCreateInput } from "@kb/co
import { COLUMN_LABELS, COLUMNS } from "@kb/core";
import { fetchTaskDetail } from "../api";
import { InlineCreateCard } from "./InlineCreateCard";
import { QuickEntryBox } from "./QuickEntryBox";
import type { ToastType } from "../hooks/useToast";
const COLUMN_COLOR_MAP: Record<Column, string> = {
@@ -34,6 +35,7 @@ interface ListViewProps {
onCancelCreate?: () => void;
onCreateTask?: (input: TaskCreateInput) => Promise<Task>;
onNewTask?: () => void;
onQuickCreate?: (description: string) => Promise<void>;
}
function getStepProgress(steps: TaskStep[]): string {
@@ -58,6 +60,7 @@ export function ListView({
isCreating,
onCancelCreate,
onCreateTask,
onQuickCreate,
}: ListViewProps) {
const [sortField, setSortField] = useState<SortField>("id");
const [sortDirection, setSortDirection] = useState<SortDirection>("desc");
@@ -350,6 +353,11 @@ export function ListView({
</button>
)}
</div>
{onQuickCreate && (
<div className="list-quick-entry">
<QuickEntryBox onCreate={onQuickCreate} addToast={addToast} />
</div>
)}
<div className="list-column-toggle" ref={columnDropdownRef}>
<button
className="btn btn-sm"

View File

@@ -1401,3 +1401,98 @@ describe("ListView Inline Create Card", () => {
expect(saveButton?.textContent).toBe("Save");
});
});
describe("ListView Quick Entry", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders QuickEntryBox when onQuickCreate is provided", () => {
const mockOnQuickCreate = vi.fn().mockResolvedValue(undefined);
renderListView({ onQuickCreate: mockOnQuickCreate });
// Quick entry box should be visible
const quickEntry = screen.getByTestId("quick-entry-box");
expect(quickEntry).toBeDefined();
// Input should be visible
const input = screen.getByTestId("quick-entry-input");
expect(input).toBeDefined();
});
it("does not render QuickEntryBox when onQuickCreate is not provided", () => {
renderListView({ onQuickCreate: undefined });
// Quick entry box should not be visible
const quickEntry = screen.queryByTestId("quick-entry-box");
expect(quickEntry).toBeNull();
});
it("calls onQuickCreate with description when Enter is pressed", async () => {
const mockOnQuickCreate = vi.fn().mockResolvedValue(undefined);
renderListView({ onQuickCreate: mockOnQuickCreate });
const input = screen.getByTestId("quick-entry-input");
fireEvent.change(input, { target: { value: "New quick task" } });
fireEvent.keyDown(input, { key: "Enter" });
await waitFor(() => {
expect(mockOnQuickCreate).toHaveBeenCalledWith("New quick task");
});
});
it("clears input after successful quick create", async () => {
const mockOnQuickCreate = vi.fn().mockResolvedValue(undefined);
renderListView({ onQuickCreate: mockOnQuickCreate });
const input = screen.getByTestId("quick-entry-input") as HTMLInputElement;
fireEvent.change(input, { target: { value: "Task to create" } });
fireEvent.keyDown(input, { key: "Enter" });
await waitFor(() => {
expect(mockOnQuickCreate).toHaveBeenCalled();
});
expect(input.value).toBe("");
});
it("shows error toast when onQuickCreate fails and keeps input content", async () => {
const mockOnQuickCreate = vi.fn().mockRejectedValue(new Error("Create failed"));
renderListView({ onQuickCreate: mockOnQuickCreate });
const input = screen.getByTestId("quick-entry-input") as HTMLInputElement;
fireEvent.change(input, { target: { value: "Failed task" } });
fireEvent.keyDown(input, { key: "Enter" });
await waitFor(() => {
expect(mockAddToast).toHaveBeenCalledWith("Create failed", "error");
});
// Input content should be preserved for retry
expect(input.value).toBe("Failed task");
});
it("trims whitespace when creating task via quick entry", async () => {
const mockOnQuickCreate = vi.fn().mockResolvedValue(undefined);
renderListView({ onQuickCreate: mockOnQuickCreate });
const input = screen.getByTestId("quick-entry-input");
fireEvent.change(input, { target: { value: " Task with spaces " } });
fireEvent.keyDown(input, { key: "Enter" });
await waitFor(() => {
expect(mockOnQuickCreate).toHaveBeenCalledWith("Task with spaces");
});
});
it("does not submit on Enter if input is empty", async () => {
const mockOnQuickCreate = vi.fn().mockResolvedValue(undefined);
renderListView({ onQuickCreate: mockOnQuickCreate });
const input = screen.getByTestId("quick-entry-input");
fireEvent.keyDown(input, { key: "Enter" });
await new Promise((resolve) => setTimeout(resolve, 50));
expect(mockOnQuickCreate).not.toHaveBeenCalled();
});
});