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.
253 lines
13 KiB
JavaScript
253 lines
13 KiB
JavaScript
/* 아리 — 작업 관리 데이터 (중첩 프로젝트 트리) */
|
|
(function () {
|
|
/* 최상위 = 폴더(업무/개인) — 그 아래가 프로젝트. 폴더·프로젝트 모두 사용자가 생성 가능 */
|
|
const tree = [
|
|
{
|
|
id: "work", name: "업무", folder: true, tone: "ink", icon: "folder",
|
|
children: [
|
|
{
|
|
id: "biz", name: "경영 전략", tone: "coral",
|
|
children: [
|
|
{ id: "biz-okr", name: "2026 Q2 OKR", tone: "coral" },
|
|
{ id: "biz-report", name: "분기 리포트", tone: "coral" },
|
|
{ id: "biz-budget", name: "예산 관리", tone: "coral" },
|
|
],
|
|
},
|
|
{
|
|
id: "onb", name: "온보딩 리디자인", tone: "violet",
|
|
children: [
|
|
{ id: "onb-research", name: "사용자 리서치", tone: "violet" },
|
|
{ id: "onb-wire", name: "와이어프레임", tone: "violet" },
|
|
{ id: "onb-voice", name: "음성 인터페이스", tone: "violet" },
|
|
],
|
|
},
|
|
{
|
|
id: "team", name: "팀 운영", tone: "green",
|
|
children: [
|
|
{ id: "team-hr", name: "채용 & 온보딩", tone: "green" },
|
|
{ id: "team-retro", name: "회고", tone: "green" },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: "life", name: "개인", folder: true, tone: "blue", icon: "heart",
|
|
children: [
|
|
{ id: "me", name: "일상", tone: "blue" },
|
|
{ id: "life-trip", name: "여행 — 한국", tone: "coral" },
|
|
{ id: "life-fam", name: "가족", tone: "green" },
|
|
],
|
|
},
|
|
];
|
|
|
|
/* 트리 → 인덱스 (동적 트리에서도 재계산할 수 있게 함수로 분리) */
|
|
function buildIndex(tree) {
|
|
const projects = [];
|
|
const byId = {};
|
|
(function walk(nodes, parentId, depth) {
|
|
nodes.forEach((n) => {
|
|
const rec = {
|
|
id: n.id, name: n.name, tone: n.tone, folder: !!n.folder,
|
|
parentId, depth, hasChildren: !!(n.children && n.children.length),
|
|
};
|
|
projects.push(rec);
|
|
byId[n.id] = rec;
|
|
if (n.children) walk(n.children, n.id, depth + 1);
|
|
});
|
|
})(tree, null, 0);
|
|
function isDescendant(pid, ancestorId) {
|
|
let cur = byId[pid];
|
|
while (cur) {
|
|
if (cur.id === ancestorId) return true;
|
|
cur = cur.parentId ? byId[cur.parentId] : null;
|
|
}
|
|
return false;
|
|
}
|
|
function pathOf(id) {
|
|
const out = [];
|
|
let cur = byId[id];
|
|
while (cur) { out.unshift(cur); cur = cur.parentId ? byId[cur.parentId] : null; }
|
|
return out;
|
|
}
|
|
return { projects, byId, isDescendant, pathOf };
|
|
}
|
|
const { projects, byId, isDescendant, pathOf } = buildIndex(tree);
|
|
|
|
const people = {
|
|
jiwoo: { name: "지우", initial: "지", c: "var(--blue)", me: true },
|
|
hyunwoo: { name: "현우", initial: "현", c: "var(--violet)" },
|
|
minseo: { name: "민서", initial: "민", c: "var(--green)" },
|
|
jaeho: { name: "재호", initial: "재", c: "var(--coral)" },
|
|
sua: { name: "수아", initial: "수", c: "oklch(0.66 0.13 200)" },
|
|
};
|
|
|
|
const columns = [
|
|
{ id: "todo", label: "할 일", accent: "var(--faint)" },
|
|
{ id: "doing", label: "진행 중", accent: "var(--blue)" },
|
|
{ id: "waiting", label: "대기 중", accent: "var(--violet)" },
|
|
{ id: "review", label: "검토", accent: "var(--coral)" },
|
|
{ id: "done", label: "완료", accent: "var(--green)" },
|
|
];
|
|
|
|
/* 직무 맞춤형 업무 쪼개기 (Auto-Scaffolding) 템플릿 */
|
|
const scaffolds = {
|
|
research: {
|
|
kind: "연구 · 논문 작성", icon: "brain",
|
|
items: [
|
|
{ title: "선행 연구 조사", est: "1일" },
|
|
{ title: "실험 데이터 취합", est: "1일" },
|
|
{ title: "아키텍처 다이어그램 작성", est: "반나절" },
|
|
{ title: "서론 초안", est: "반나절" },
|
|
{ title: "결론 작성", est: "반나절" },
|
|
],
|
|
},
|
|
dev: {
|
|
kind: "개발 업무", icon: "branch",
|
|
items: [
|
|
{ title: "API 엔드포인트 설계", est: "2시간" },
|
|
{ title: "UI 폼 컴포넌트 개발", est: "3시간" },
|
|
{ title: "토큰 저장소 세팅", est: "1.5시간" },
|
|
{ title: "테스트 코드 작성", est: "2시간" },
|
|
],
|
|
},
|
|
doc: {
|
|
kind: "문서 · 리포트", icon: "file",
|
|
items: [
|
|
{ title: "목차 & 범위 정의", est: "30분" },
|
|
{ title: "핵심 데이터 수집", est: "2시간" },
|
|
{ title: "섹션별 초안 작성", est: "3시간" },
|
|
{ title: "리뷰 & 수정", est: "1시간" },
|
|
{ title: "최종 정리 & 공유", est: "30분" },
|
|
],
|
|
},
|
|
generic: {
|
|
kind: "일반 업무", icon: "list",
|
|
items: [
|
|
{ title: "범위 정의 & 자료 수집", est: "1시간" },
|
|
{ title: "초안 작성", est: "2시간" },
|
|
{ title: "리뷰 & 피드백 반영", est: "1시간" },
|
|
{ title: "최종 정리 & 공유", est: "30분" },
|
|
],
|
|
},
|
|
};
|
|
function pickScaffold(title) {
|
|
const t = title || "";
|
|
if (/(논문|연구|리서치|실험|학회|발표|조사|인터뷰)/.test(t)) return scaffolds.research;
|
|
if (/(구현|개발|API|로직|컴포넌트|버그|배포|인증|앱)/.test(t)) return scaffolds.dev;
|
|
if (/(리포트|문서|보고|자료|작성|초안|회고|정산)/.test(t)) return scaffolds.doc;
|
|
return scaffolds.generic;
|
|
}
|
|
|
|
/* 위임 추천 — 아리는 동료의 업무량을 알 수 없으므로,
|
|
내 과부하 + 같은 프로젝트 공동작업자만 근거로 제안하고 넘긴 일을 추적한다 */
|
|
const delegateHint = { who: "minseo" };
|
|
|
|
/* 작업 노드 — 하위 작업은 그 자체가 완전한 작업(무한 중첩) */
|
|
let _t = 0;
|
|
const tuid = () => "kx" + (++_t);
|
|
function T(o) {
|
|
return {
|
|
id: o.id || tuid(),
|
|
title: o.title,
|
|
status: o.status || "todo",
|
|
assignee: o.assignee || "jiwoo",
|
|
due: o.due || "",
|
|
prio: o.prio || "보통",
|
|
project: o.project || "me",
|
|
notes: o.notes || "",
|
|
est: o.est || "",
|
|
delegated: o.delegated || false,
|
|
comments: o.comments || [],
|
|
children: (o.children || []).map(T),
|
|
};
|
|
}
|
|
|
|
const tasks = [
|
|
T({ id: "k1", title: "분기 리포트 초안 마무리", project: "biz-report", assignee: "jiwoo", due: "06-08", prio: "높음", status: "doing",
|
|
notes: "<h3>목표</h3><p>경영진 미팅용 Q2 성과 리포트. <b>리텐션·매출·예산</b> 세 섹션으로 구성한다.</p><blockquote>핵심 지표는 시각화해서 한눈에 들어오게.</blockquote>",
|
|
comments: [
|
|
{ id: "c1", who: "hyunwoo", text: "매출 섹션은 제가 오늘 안으로 넘겨드릴게요.", time: "2시간 전" },
|
|
{ id: "c2", who: "jiwoo", text: "좋아요, 예산 섹션만 확정되면 취합할게요.", time: "1시간 전" },
|
|
],
|
|
children: [
|
|
T({ title: "리텐션 데이터 취합", status: "done", assignee: "sua", due: "06-08", project: "biz-report",
|
|
children: [
|
|
T({ title: "코호트 정의 확정", status: "done", assignee: "sua", due: "06-07" }),
|
|
T({ title: "주차별 잔존율 추출", status: "done", assignee: "sua", due: "06-08" }),
|
|
T({ title: "이탈 사유 태깅", status: "done", assignee: "jaeho", due: "06-08",
|
|
children: [
|
|
T({ title: "인터뷰 발췌 5건", status: "done", assignee: "jaeho" }),
|
|
T({ title: "사유 카테고리 분류", status: "done", assignee: "jaeho" }),
|
|
] }),
|
|
] }),
|
|
T({ title: "매출 섹션 작성", status: "doing", assignee: "jiwoo", due: "06-09", prio: "높음", project: "biz-report",
|
|
notes: "<p>MRR/ARR 표는 완료. 증감 코멘트 마무리 필요.</p>",
|
|
children: [
|
|
T({ title: "MRR / ARR 표 정리", status: "done", assignee: "jiwoo" }),
|
|
T({ title: "전분기 대비 증감 코멘트", status: "doing", assignee: "jiwoo" }),
|
|
T({ title: "예측 시나리오 3종", status: "todo", assignee: "jiwoo" }),
|
|
] }),
|
|
T({ title: "예산 섹션 작성", status: "todo", assignee: "jiwoo", due: "06-10", project: "biz-budget" }),
|
|
T({ title: "경영진 검토 요청 메일", status: "todo", assignee: "jiwoo", due: "06-10", project: "biz-report" }),
|
|
] }),
|
|
T({ id: "k2", title: "온보딩 와이어프레임 피드백 정리", project: "onb-wire", assignee: "hyunwoo", due: "06-09", prio: "높음", status: "review",
|
|
notes: "<p>디자인 리뷰에서 나온 3개 화면 수정사항을 취합하고 우선순위를 매긴다.</p>",
|
|
comments: [
|
|
{ id: "c3", who: "sua", text: "3번 화면 CTA 위치는 아래로 내리는 게 좋겠어요.", time: "어제" },
|
|
],
|
|
children: [
|
|
T({ title: "환영 화면 수정안", status: "done", assignee: "hyunwoo", project: "onb-wire",
|
|
children: [
|
|
T({ title: "카피 톤 조정", status: "done", assignee: "hyunwoo" }),
|
|
T({ title: "일러스트 교체 요청", status: "doing", assignee: "minseo" }),
|
|
] }),
|
|
T({ title: "권한 요청 화면 재배치", status: "doing", assignee: "hyunwoo" }),
|
|
T({ title: "음성 안내 추가 검토", status: "todo", assignee: "sua" }),
|
|
] }),
|
|
T({ id: "k5", title: "사용자 인터뷰 5건 정리", project: "onb-research", assignee: "sua", due: "06-10", prio: "보통", status: "doing",
|
|
children: [
|
|
T({ title: "녹취 요약 (5건)", status: "doing", assignee: "sua" }),
|
|
T({ title: "인사이트 태깅", status: "todo", assignee: "sua" }),
|
|
T({ title: "리서치 보드 업데이트", status: "todo", assignee: "minseo" }),
|
|
] }),
|
|
T({ id: "k6", title: "OKR 중간 점검 자료 준비", project: "biz-okr", assignee: "jiwoo", due: "06-11", prio: "높음", status: "todo",
|
|
children: [
|
|
T({ title: "Objective별 진척도 집계", status: "todo", assignee: "jiwoo" }),
|
|
T({ title: "리스크 항목 표시", status: "todo", assignee: "jiwoo" }),
|
|
] }),
|
|
T({ id: "k4", title: "구독 결제 카드 갱신", project: "me", assignee: "jiwoo", due: "06-12", prio: "보통", status: "todo", children: [] }),
|
|
T({ id: "k3", title: "신규 입사자 환영 메일 발송", project: "team-hr", assignee: "minseo", due: "06-07", prio: "보통", status: "done",
|
|
children: [
|
|
T({ title: "메일 템플릿 작성", status: "done", assignee: "minseo" }),
|
|
T({ title: "수신자 명단 확인", status: "done", assignee: "minseo" }),
|
|
] }),
|
|
T({ id: "k13", title: "스프린트 회고 문서 배포", project: "team-retro", assignee: "minseo", due: "06-08", prio: "낮음", status: "review",
|
|
children: [
|
|
T({ title: "액션 아이템 담당자 지정", status: "done", assignee: "minseo" }),
|
|
T({ title: "다음 스프린트 반영 확인", status: "doing", assignee: "jaeho" }),
|
|
] }),
|
|
T({ id: "k10", title: "모바일 푸시 알림 QA", project: "onb-wire", assignee: "sua", due: "06-09", prio: "높음", status: "doing",
|
|
children: [
|
|
T({ title: "iOS 시나리오 3종", status: "done", assignee: "sua" }),
|
|
T({ title: "Android 시나리오 3종", status: "doing", assignee: "sua" }),
|
|
T({ title: "딥링크 라우팅 확인", status: "todo", assignee: "jaeho" }),
|
|
] }),
|
|
T({ id: "k14", title: "데이터 전처리 파이프라인", project: "onb-research", assignee: "minseo", due: "06-10", prio: "보통", status: "waiting", delegated: true,
|
|
notes: "<p>일정이 빠듯해 같은 프로젝트의 <b>민서님</b>께 위임 — 현재 완료를 기다리는 중입니다.</p>",
|
|
children: [
|
|
T({ title: "원천 데이터 스키마 정리", status: "done", assignee: "minseo" }),
|
|
T({ title: "결측치 처리 규칙 정의", status: "doing", assignee: "minseo" }),
|
|
T({ title: "정제 스크립트 작성", status: "todo", assignee: "minseo" }),
|
|
] }),
|
|
T({ id: "k20", title: "한국행 비행기 티켓 구매", project: "life-trip", assignee: "jiwoo", due: "06-14", prio: "높음", status: "todo",
|
|
notes: "<p>스마트 인박스에서 자동 생성된 작업이에요. 아리가 <b>가격 추적 알림</b>을 켜뒀고, 적정가가 보이면 결재함으로 알려드려요.</p>",
|
|
children: [
|
|
T({ title: "날짜 후보 확정", status: "doing", assignee: "jiwoo", project: "life-trip" }),
|
|
T({ title: "가격 알림 확인 후 결제", status: "todo", assignee: "jiwoo", project: "life-trip" }),
|
|
] }),
|
|
T({ id: "k21", title: "엄마 생신 선물 알아보기", project: "life-fam", assignee: "jiwoo", due: "06-20", prio: "보통", status: "todo", children: [] }),
|
|
];
|
|
|
|
window.TaskData = { tree, buildIndex, projects, byId, isDescendant, pathOf, people, columns, tasks, scaffolds, pickScaffold, delegateHint };
|
|
})();
|