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
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>
|
|
);
|
|
}
|