You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

149 lines
4.7 KiB
TypeScript

// frontend/components/tasks/TaskSidebar.tsx
"use client";
import { Fragment } from "react";
import { Icon } from "@/components/Icon";
import { cx } from "@/lib/cx";
import type { Folder, Person, Project } from "@/lib/types";
import { TreeAdd } from "./TreeAdd";
import { TreeNode } from "./TreeNode";
interface Props {
folders: Folder[];
me: Person;
sel: string;
onSelect: (id: string) => void;
expanded: Record<string, boolean>;
onToggle: (id: string) => void;
onPin: (id: string) => void;
counts: Record<string, number>;
pinnedProjects: Project[];
onAddProject: (folderId: string, name: string) => void;
}
export function TaskSidebar(p: Props) {
return (
<aside className="subnav">
<div className="sn-title"></div>
<div className="sn-scroll">
{/* 업무 / 개인 */}
{p.folders.map((f) => {
const open = !!p.expanded[f.id];
return (
<Fragment key={f.id}>
<div
className={cx("tree-row folder", p.sel === f.id && "on")}
style={{ paddingLeft: 8 }}
onClick={() => {
p.onSelect(f.id);
if (!open) p.onToggle(f.id);
}}
>
<button
className={cx("tree-chev", open && "open")}
onClick={(e) => {
e.stopPropagation();
p.onToggle(f.id);
}}
aria-label="펼치기"
>
<Icon name="chev" />
</button>
<span className="tree-fold">
<Icon name={(f.icon || "folder") as never} />
</span>
<span className="tree-name">{f.name}</span>
<span className="tree-badge">{p.counts[f.id] || 0}</span>
</div>
{open &&
f.projects.map((n) => (
<TreeNode
key={n.id}
node={n}
depth={1}
sel={p.sel}
onSelect={p.onSelect}
expanded={p.expanded}
onToggle={p.onToggle}
onPin={p.onPin}
counts={p.counts}
/>
))}
{open && (
<TreeAdd
depth={1}
placeholder="새 프로젝트"
onAdd={(name) => p.onAddProject(f.id, name)}
/>
)}
</Fragment>
);
})}
{/* 즐겨찾기 */}
<div
className={cx("tree-row folder", p.sel === "fav" && "on")}
style={{ paddingLeft: 8 }}
onClick={() => {
p.onSelect("fav");
if (!p.expanded.fav) p.onToggle("fav");
}}
>
<button
className={cx("tree-chev", p.expanded.fav && "open")}
onClick={(e) => {
e.stopPropagation();
p.onToggle("fav");
}}
aria-label="펼치기"
>
<Icon name="chev" />
</button>
<span className="tree-fold">
<Icon name="star" />
</span>
<span className="tree-name"></span>
<span className="tree-badge">{p.pinnedProjects.length}</span>
</div>
{p.expanded.fav &&
(p.pinnedProjects.length > 0 ? (
p.pinnedProjects.map((pr) => (
<div
key={pr.id}
className={cx("tree-row", p.sel === pr.id && "on")}
style={{ paddingLeft: 8 + 15 }}
onClick={() => p.onSelect(pr.id)}
>
<span className="tree-chev-spacer" />
<span className="tree-dot" style={{ background: `var(--${pr.tone})` }} />
<span className="tree-name">{pr.name}</span>
<button
className="tree-pin on"
onClick={(e) => {
e.stopPropagation();
p.onPin(pr.id);
}}
aria-label="즐겨찾기 해제"
>
<Icon name="star" w={0} fill="currentColor" />
</button>
</div>
))
) : (
<div className="sn-empty" style={{ paddingLeft: 8 + 15 }}>
<Icon name="star" />
</div>
))}
</div>
<div className="sn-foot">
<div className="av">{p.me.initial}</div>
<div className="txt">
<b>{p.me.name}</b>
<span>Pro </span>
</div>
</div>
</aside>
);
}