Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
tasks
  • Loading branch information
olyamrshn committed Sep 11, 2024
commit 9aaac1cdfbc69a3044d5a3878f55f7f6987500c1
Binary file modified bun.lockb
Binary file not shown.
5 changes: 5 additions & 0 deletions web/app/(pages)/tasks/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { TaskRoute } from "@/components/routes/task/TaskRoute"

export default function TaskPage() {
return <TaskRoute />
}
107 changes: 107 additions & 0 deletions web/components/custom/sidebar/partial/task-section.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="group/tasks flex flex-col gap-px py-2">
<TaskSectionHeader taskCount={taskCount} isActive={isActive} />
<List tasks={me.root.tasks as ListOfTasks} />
</div>
)
}

interface TaskSectionHeaderProps {
taskCount: number
isActive: boolean
}

const TaskSectionHeader: React.FC<TaskSectionHeaderProps> = ({ taskCount, isActive }) => (
<div
className={cn(
"flex min-h-[30px] items-center gap-px rounded-md",
isActive ? "bg-accent text-accent-foreground" : "hover:bg-accent hover:text-accent-foreground"
)}
>
<Link
href="/tasks"
className="flex flex-1 items-center justify-start rounded-md px-2 py-1 focus-visible:outline-none focus-visible:ring-0"
>
<p className="text-xs">
Tasks
{taskCount > 0 && <span className="text-muted-foreground ml-1">{taskCount}</span>}
</p>
</Link>
</div>
// <div
// className={cn(
// "flex min-h-[30px] items-center gap-px rounded-md",
// isActive ? "bg-accent text-accent-foreground" : "hover:bg-accent hover:text-accent-foreground"
// )}
// >
// <Button
// variant="ghost"
// className="size-6 flex-1 items-center justify-start rounded-md px-2 py-1 focus-visible:outline-none focus-visible:ring-0"
// >
// <p className="flex items-center text-xs font-medium">
// Tasks
// {taskCount > 0 && <span className="text-muted-foreground ml-1">{taskCount}</span>}
// </p>
// </Button>
// </div>
)

interface ListProps {
tasks: ListOfTasks
}

const List: React.FC<ListProps> = ({ tasks }) => {
const pathname = usePathname()

return (
<div className="flex flex-col gap-px">
<ListItem label="All Tasks" href="/tasks" count={tasks.length} isActive={pathname === "/tasks"} />
</div>
)
}

interface ListItemProps {
label: string
href: string
count: number
isActive: boolean
}

const ListItem: React.FC<ListItemProps> = ({ label, href, count, isActive }) => (
<div className="group/reorder-task relative">
<div className="group/task-link relative flex min-w-0 flex-1">
<Link
// TODO: update links
href="/tasks"
className="relative flex h-8 w-full items-center gap-2 rounded-md p-1.5 font-medium"
// className={cn(
// "relative flex h-8 w-full items-center gap-2 rounded-md p-1.5 font-medium",
// isActive ? "bg-accent text-accent-foreground" : "hover:bg-accent hover:text-accent-foreground"
// )}
>
<div className="flex max-w-full flex-1 items-center gap-1.5 truncate text-sm">
<LaIcon name="BookCheck" className="opacity-60" />
<p className={cn("truncate opacity-95 group-hover/task-link:opacity-100")}>{label}</p>
</div>
</Link>
{count > 0 && (
<span className="absolute right-2 top-1/2 z-[1] -translate-y-1/2 rounded p-1 text-sm">{count}</span>
)}
</div>
</div>
)
3 changes: 3 additions & 0 deletions web/components/custom/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -124,6 +125,8 @@ const SidebarContent: React.FC = React.memo(() => {
{me._type === "Account" && <LinkSection pathname={pathname} />}
{me._type === "Account" && <PageSection pathname={pathname} />}
{me._type === "Account" && <TopicSection pathname={pathname} />}
{/* {me._type === "Account" && <TaskSection pathname={pathname} />} */}
<TaskSection pathname={pathname} />
</div>

<ProfileSection />
Expand Down
43 changes: 43 additions & 0 deletions web/components/routes/task/TaskForm.tsx
Original file line number Diff line number Diff line change
@@ -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<TaskFormProps> = ({ 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 (
<form onSubmit={handleSubmit} className="flex space-x-2">
<Input value={title} className="w-[50%]" onChange={e => setTitle(e.target.value)} placeholder="Add new task" />
<button className="bg-inherit" type="submit">
<LaIcon name="CirclePlus" className="size-6" />
</button>
</form>
)
}
21 changes: 21 additions & 0 deletions web/components/routes/task/TaskItem.tsx
Original file line number Diff line number Diff line change
@@ -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<Task>) => void
}

export const TaskItem: React.FC<TaskItemProps> = ({ task, onUpdateTask }) => {
const statusChange = (checked: boolean) => {
onUpdateTask(task.id, { status: checked ? "done" : "todo" })
}

return (
<li className="flex items-center space-x-2">
<Checkbox checked={task.status === "done"} onCheckedChange={statusChange} />
<span className={task.status === "done" ? "text-foreground line-through" : ""}>{task.title}</span>
</li>
)
}
18 changes: 18 additions & 0 deletions web/components/routes/task/TaskList.tsx
Original file line number Diff line number Diff line change
@@ -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<Task>) => void
}

export const TaskList: React.FC<TaskListProps> = ({ tasks, onUpdateTask }) => {
return (
<ul className="space-y-2">
{tasks.map(task => (
<TaskItem key={task.id} task={task} onUpdateTask={onUpdateTask} />
))}
</ul>
)
}
34 changes: 34 additions & 0 deletions web/components/routes/task/TaskRoute.tsx
Original file line number Diff line number Diff line change
@@ -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<Task>) => {
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 (
<div className="flex flex-col space-y-4 p-4">
<h1 className="text-2xl font-bold">Tasks</h1>
<TaskForm onAddTask={addTask} />
<TaskList tasks={tasks as Task[]} updateTask={updateTask} />
</div>
)
}
7 changes: 6 additions & 1 deletion web/lib/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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 }
)
Expand Down
11 changes: 11 additions & 0 deletions web/lib/schema/tasks.ts
Original file line number Diff line number Diff line change
@@ -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)) {}