diff --git a/bun.lockb b/bun.lockb index 263389b0..8d317c47 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/web/app/(pages)/tasks/page.tsx b/web/app/(pages)/tasks/page.tsx new file mode 100644 index 00000000..31c7823c --- /dev/null +++ b/web/app/(pages)/tasks/page.tsx @@ -0,0 +1,5 @@ +import { TaskRoute } from "@/components/routes/task/TaskRoute" + +export default function TaskPage() { + return +} diff --git a/web/components/custom/sidebar/partial/task-section.tsx b/web/components/custom/sidebar/partial/task-section.tsx new file mode 100644 index 00000000..5f725eab --- /dev/null +++ b/web/components/custom/sidebar/partial/task-section.tsx @@ -0,0 +1,107 @@ +import Link from "next/link" +import { usePathname } from "next/navigation" +import { useAccount } from "@/lib/providers/jazz-provider" +import { cn } from "@/lib/utils" +import { ListOfTasks } from "@/lib/schema/tasks" +import { LaIcon } from "../../la-icon" + +export const TaskSection: React.FC<{ pathname: string }> = ({ pathname }) => { + const me = { root: { tasks: [{ id: "1", title: "Test Task" }] } } + + const taskCount = me?.root.tasks?.length || 0 + const isActive = pathname === "/tasks" + + if (!me) return null + + return ( +
+ + +
+ ) +} + +interface TaskSectionHeaderProps { + taskCount: number + isActive: boolean +} + +const TaskSectionHeader: React.FC = ({ taskCount, isActive }) => ( +
+ +

+ Tasks + {taskCount > 0 && {taskCount}} +

+ +
+ //
+ // + //
+) + +interface ListProps { + tasks: ListOfTasks +} + +const List: React.FC = ({ tasks }) => { + const pathname = usePathname() + + return ( +
+ +
+ ) +} + +interface ListItemProps { + label: string + href: string + count: number + isActive: boolean +} + +const ListItem: React.FC = ({ label, href, count, isActive }) => ( +
+
+ +
+ +

{label}

+
+ + {count > 0 && ( + {count} + )} +
+
+) diff --git a/web/components/custom/sidebar/sidebar.tsx b/web/components/custom/sidebar/sidebar.tsx index 3679128a..8bbf2637 100644 --- a/web/components/custom/sidebar/sidebar.tsx +++ b/web/components/custom/sidebar/sidebar.tsx @@ -14,6 +14,7 @@ import { LinkSection } from "./partial/link-section" import { PageSection } from "./partial/page-section" import { TopicSection } from "./partial/topic-section" import { ProfileSection } from "./partial/profile-section" +import { TaskSection } from "./partial/task-section" import { useAccountOrGuest } from "@/lib/providers/jazz-provider" interface SidebarContextType { @@ -124,6 +125,8 @@ const SidebarContent: React.FC = React.memo(() => { {me._type === "Account" && } {me._type === "Account" && } {me._type === "Account" && } + {/* {me._type === "Account" && } */} + diff --git a/web/components/routes/task/TaskForm.tsx b/web/components/routes/task/TaskForm.tsx new file mode 100644 index 00000000..567df5e8 --- /dev/null +++ b/web/components/routes/task/TaskForm.tsx @@ -0,0 +1,43 @@ +"use client" + +import React, { useState } from "react" +import { Task } from "@/lib/schema/tasks" +import { Input } from "@/components/ui/input" +import { useAccount } from "@/lib/providers/jazz-provider" +import { LaIcon } from "@/components/custom/la-icon" + +interface TaskFormProps { + onAddTask: (task: Task) => void +} + +export const TaskForm: React.FC = ({ onAddTask }) => { + const [title, setTitle] = useState("") + const { me } = useAccount() + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (title.trim() && me) { + const newTask = Task.create( + { + title, + description: "", + status: "todo", + createdAt: new Date(), + updatedAt: new Date() + }, + { owner: me._owner } + ) + onAddTask(newTask) + setTitle("") + } + } + + return ( +
+ setTitle(e.target.value)} placeholder="Add new task" /> + +
+ ) +} diff --git a/web/components/routes/task/TaskItem.tsx b/web/components/routes/task/TaskItem.tsx new file mode 100644 index 00000000..38160141 --- /dev/null +++ b/web/components/routes/task/TaskItem.tsx @@ -0,0 +1,21 @@ +import React from "react" +import { Task } from "@/lib/schema/tasks" +import { Checkbox } from "@/components/ui/checkbox" + +interface TaskItemProps { + task: Task + onUpdateTask: (taskId: string, updates: Partial) => void +} + +export const TaskItem: React.FC = ({ task, onUpdateTask }) => { + const statusChange = (checked: boolean) => { + onUpdateTask(task.id, { status: checked ? "done" : "todo" }) + } + + return ( +
  • + + {task.title} +
  • + ) +} diff --git a/web/components/routes/task/TaskList.tsx b/web/components/routes/task/TaskList.tsx new file mode 100644 index 00000000..c5497b52 --- /dev/null +++ b/web/components/routes/task/TaskList.tsx @@ -0,0 +1,18 @@ +import React from "react" +import { Task } from "@/lib/schema/tasks" +import { TaskItem } from "./TaskItem" + +interface TaskListProps { + tasks: Task[] + onUpdateTask: (taskId: string, updates: Partial) => void +} + +export const TaskList: React.FC = ({ tasks, onUpdateTask }) => { + return ( +
      + {tasks.map(task => ( + + ))} +
    + ) +} diff --git a/web/components/routes/task/TaskRoute.tsx b/web/components/routes/task/TaskRoute.tsx new file mode 100644 index 00000000..7ef2d180 --- /dev/null +++ b/web/components/routes/task/TaskRoute.tsx @@ -0,0 +1,34 @@ +"use client" + +import { useAccount } from "@/lib/providers/jazz-provider" +import { Task } from "@/lib/schema/tasks" +import { TaskList } from "./TaskList" +import { TaskForm } from "./TaskForm" + +export const TaskRoute: React.FC = () => { + const { me } = useAccount({ root: { tasks: [] } }) + const tasks = me?.root?.tasks || [] + + const addTask = (newTask: Task) => { + if (me?.root?.tasks) { + me.root.tasks.push(newTask) + } + } + + const updateTask = (taskId: string, updates: Partial) => { + if (me?.root?.tasks) { + const taskIndex = me.root.tasks.findIndex(task => task?.id === taskId) + if (taskIndex !== -1) { + Object.assign(me.root.tasks[taskIndex]!, updates) + } + } + } + + return ( +
    +

    Tasks

    + + +
    + ) +} diff --git a/web/lib/schema/index.ts b/web/lib/schema/index.ts index 12ebd60e..8b15d634 100644 --- a/web/lib/schema/index.ts +++ b/web/lib/schema/index.ts @@ -12,6 +12,7 @@ import { CoMap, co, Account, Profile } from "jazz-tools" import { PersonalPageLists } from "./personal-page" import { PersonalLinkLists } from "./personal-link" import { ListOfTopics } from "./master/topic" +import { ListOfTasks } from "./tasks" declare module "jazz-tools" { interface Profile { @@ -32,6 +33,8 @@ export class UserRoot extends CoMap { topicsWantToLearn = co.ref(ListOfTopics) topicsLearning = co.ref(ListOfTopics) topicsLearned = co.ref(ListOfTopics) + + tasks = co.ref(ListOfTasks) } export class LaAccount extends Account { @@ -59,7 +62,9 @@ export class LaAccount extends Account { topicsWantToLearn: ListOfTopics.create([], { owner: this }), topicsLearning: ListOfTopics.create([], { owner: this }), - topicsLearned: ListOfTopics.create([], { owner: this }) + topicsLearned: ListOfTopics.create([], { owner: this }), + + tasks: ListOfTasks.create([], { owner: this }) }, { owner: this } ) diff --git a/web/lib/schema/tasks.ts b/web/lib/schema/tasks.ts new file mode 100644 index 00000000..71cf643f --- /dev/null +++ b/web/lib/schema/tasks.ts @@ -0,0 +1,11 @@ +import { co, CoList, CoMap, Encoders } from "jazz-tools" + +export class Task extends CoMap { + title = co.string + description = co.optional.string + status = co.literal("todo", "in_progress", "done") + createdAt = co.encoded(Encoders.Date) + updatedAt = co.encoded(Encoders.Date) +} + +export class ListOfTasks extends CoList.Of(co.ref(Task)) {}