- {tasks
- .filter((task) => task.status === status)
- .map((task, index) => (
-
- {(provided) => (
-
- setTasks((cols) =>
- cols.map((c) => (c.id === t.id ? t : c))
- )
- }
- provided={provided}
- task={task}
- tags={tags}
- />
- )}
-
- ))}
+ {statusTasks.map((task, index) => (
+
+ {(provided) => (
+
+ setTasks({
+ ...tasks,
+ [t.id]: t,
+ })
+ }
+ provided={provided}
+ task={task}
+ tags={tags}
+ />
+ )}
+
+ ))}
{provided.placeholder}
- {tasks.filter((task) => task.status === status).length ===
- 0 && (
+ {statusTasks.length === 0 && (
No tasks
diff --git a/src/components/view/board/Task.tsx b/src/components/view/board/Task.tsx
index 6a5d96a..5f58544 100644
--- a/src/components/view/board/Task.tsx
+++ b/src/components/view/board/Task.tsx
@@ -7,12 +7,12 @@ import {
CardTitle,
} from "@/components/ui/card";
import { SheetTrigger } from "@/components/ui/sheet";
-import { Task } from "@/lib/types";
+import { Task, Tag, TagId } from "@/lib/types";
import { Color, getColor } from "@/lib/ui/color";
import { DraggableProvided } from "@hello-pangea/dnd";
import TaskEdit from "../edit/TaskEdit";
import { Button } from "@/components/ui/button";
-import { NotepadText } from "lucide-react";
+import { NotepadText, Trash2 } from "lucide-react";
import DocDialog from "../edit/DocDialog";
export default function BoardTask({
@@ -20,11 +20,13 @@ export default function BoardTask({
provided,
setTask,
tags,
+ onDelete,
}: {
task: Task;
setTask: (t: Task) => void;
provided: DraggableProvided;
- tags: Record
;
+ tags: Record;
+ onDelete?: (taskId: string) => void;
}) {
return (
@@ -33,34 +35,56 @@ export default function BoardTask({
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
- className="bg-background"
+ className="bg-background cursor-grab active:cursor-grabbing"
>
- {task.title}
-
-
-
+ {task.title}
+
+
+
+
+ {onDelete && (
+
+ )}
+
- {task.description}
+ {task.description && (
+
+ {task.description}
+
+ )}
- {task.tags?.map((tag, i) => (
+ {task.tagIds?.map((tagId) => (
- {tag}
+ {tags[tagId]?.name || tagId}
))}
diff --git a/src/components/view/edit/DocDialog.tsx b/src/components/view/edit/DocDialog.tsx
index e4cb881..43058eb 100644
--- a/src/components/view/edit/DocDialog.tsx
+++ b/src/components/view/edit/DocDialog.tsx
@@ -10,8 +10,8 @@ import {
import { ScrollArea } from "@/components/ui/scroll-area";
import DocView from "../DocView";
import { Badge } from "@/components/ui/badge";
-import { Task } from "@/lib/types";
-import { Color, getColor } from "@/lib/ui/color";
+import { Task, Tag, TagId } from "@/lib/types";
+import { getColor } from "@/lib/ui/color";
import { useState } from "react";
import { OutputData } from "@editorjs/editorjs";
@@ -23,10 +23,14 @@ export default function DocDialog({
}: {
task: Task;
setTask: (t: Task) => void;
- tags: Record;
+ tags: Record;
children: React.ReactNode;
}) {
- const [doc, setDoc] = useState({ blocks: [] });
+ const [doc, setDoc] = useState({
+ version: "2.28.0",
+ time: Date.now(),
+ blocks: [],
+ });
return (
diff --git a/src/components/view/table/TableView.tsx b/src/components/view/table/TableView.tsx
index 282e88a..248030d 100644
--- a/src/components/view/table/TableView.tsx
+++ b/src/components/view/table/TableView.tsx
@@ -15,6 +15,10 @@ import { getColor } from "@/lib/ui/color";
import TableTask from "./Task";
export function TableView({ tasks, setTasks, tags }: TaskViewProps) {
+ const tasksArray = Object.values(tasks).sort(
+ (a, b) => b.createdAt - a.createdAt,
+ );
+
return (
@@ -26,7 +30,7 @@ export function TableView({ tasks, setTasks, tags }: TaskViewProps) {
- {tasks.length === 0 ? (
+ {tasksArray.length === 0 ? (
) : (
- tasks.map((task) => (
+ tasksArray.map((task) => (
- setTasks((tasks) => tasks.map((c) => (c.id === t.id ? t : c)))
+ setTasks({
+ ...tasks,
+ [t.id]: t,
+ })
}
tags={tags}
/>
diff --git a/src/components/view/table/Task.tsx b/src/components/view/table/Task.tsx
index 089fd18..3682d62 100644
--- a/src/components/view/table/Task.tsx
+++ b/src/components/view/table/Task.tsx
@@ -1,50 +1,62 @@
import { Badge } from "@/components/ui/badge";
import { SheetTrigger } from "@/components/ui/sheet";
import { TableCell, TableRow } from "@/components/ui/table";
-import { Task } from "@/lib/types";
+import { Task, Tag, TagId } from "@/lib/types";
import { Color, getColor } from "@/lib/ui/color";
import TaskEdit from "../edit/TaskEdit";
+import { Button } from "@/components/ui/button";
+import { Trash2 } from "lucide-react";
export default function TableTask({
task,
setTask,
tags,
+ onDelete,
}: {
task: Task;
setTask: (t: Task) => void;
- tags: Record;
+ tags: Record;
+ onDelete?: (taskId: string) => void;
}) {
return (
-
+
{task.title}
{task.description || "-"}
-
- {task.status}
-
+ {task.statusId}
-
- {task.tags?.map((tag, i) => (
-
+
+ {task.tagIds?.map((tagId) => (
+
+ {tags[tagId]?.name || tagId}
+
+ ))}
+
+ {onDelete && (
+
- ))}
+
+
+ )}
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 70f2463..e25eb75 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -22,16 +22,17 @@ export interface Workspace {
updatedAt: number;
}
-export type LayoutNode = {
- type: "group";
- id: string;
- name: string;
- children: LayoutItem[];
-};
-
-export type LayoutItem = {
- entityId: EntityId;
-};
+export type LayoutNode =
+ | {
+ type: "group";
+ id: string;
+ name: string;
+ children: LayoutNode[];
+ }
+ | {
+ type: "item";
+ entityId: EntityId;
+ };
export type Entity = TodoEntity | DocEntity;
diff --git a/src/lib/ui/color.ts b/src/lib/ui/color.ts
index d628397..6611575 100644
--- a/src/lib/ui/color.ts
+++ b/src/lib/ui/color.ts
@@ -8,7 +8,8 @@ export type Color =
| "blue"
| "purple"
| "pink"
- | "red";
+ | "red"
+ | string;
interface ColorStyle {
backgroundColor: string;
@@ -59,5 +60,5 @@ export function getColor(color: Color): ColorStyle {
},
};
- return colors[color];
+ return colors[color] || color;
}
diff --git a/src/lib/workspace.ts b/src/lib/workspace.ts
index 44d6e4e..01ab95f 100644
--- a/src/lib/workspace.ts
+++ b/src/lib/workspace.ts
@@ -1,13 +1,60 @@
-import { Workspace } from "./types";
+import { Workspace, TodoEntity, DocEntity, Status } from "./types";
export function createDefaultWorkspace(): Workspace {
const now = Date.now();
+ const todoId = crypto.randomUUID();
+ const docId = crypto.randomUUID();
+
+ const defaultStatuses: Status[] = [
+ { id: "status_todo", name: "To Do", order: 0, color: "#808080" },
+ {
+ id: "status_inprogress",
+ name: "In Progress",
+ order: 1,
+ color: "#0ea5e9",
+ },
+ { id: "status_done", name: "Done", order: 2, color: "#10b981" },
+ ];
+
+ const todoEntity: TodoEntity = {
+ id: todoId,
+ type: "todo",
+ name: "Tasks",
+ config: { statuses: defaultStatuses },
+ tags: {},
+ tasks: {},
+ createdAt: now,
+ updatedAt: now,
+ };
+
+ const docEntity: DocEntity = {
+ id: docId,
+ type: "doc",
+ name: "Notes",
+ content: { version: "2.28.0", time: now, blocks: [] },
+ createdAt: now,
+ updatedAt: now,
+ };
+
return {
id: crypto.randomUUID(),
- name: "New Workspace",
- entities: {},
- layout: [],
+ name: "My Workspace",
+ entities: {
+ [todoId]: todoEntity,
+ [docId]: docEntity,
+ },
+ layout: [
+ {
+ type: "group",
+ id: crypto.randomUUID(),
+ name: "Productivity",
+ children: [
+ { entityId: todoId, type: "item" },
+ { entityId: docId, type: "item" },
+ ],
+ },
+ ],
createdAt: now,
updatedAt: now,
};
@@ -27,7 +74,7 @@ export function loadWorkspaceFromLocalStorage(id: string): Workspace {
if (
typeof parsed !== "object" ||
parsed === null ||
- typeof parsed.version !== "number"
+ typeof parsed.id !== "string"
) {
console.warn("Invalid workspace shape. Resetting.");
return createDefaultWorkspace();