soojeongcrystal commited on
Commit
c665bc7
Β·
verified Β·
1 Parent(s): d6debd9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -85
app.py CHANGED
@@ -7,17 +7,53 @@ import tempfile
7
  import os
8
  from streamlit_sortables import sort_items
9
 
 
 
 
10
  st.set_page_config(page_title="νŒ€ 업무 ꡬ쑰화 λ„μš°λ―Έ (P-D-E-R-O)", layout="wide")
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  # ------------------------------
13
- # μ΄ˆκΈ°ν™”
14
  # ------------------------------
15
  if "page" not in st.session_state:
16
  st.session_state.page = "도메인 μ„€μ •"
17
  if "domains" not in st.session_state:
18
  st.session_state.domains = []
19
- if "tasks" not in st.session_state:
20
- st.session_state.tasks = []
21
  if "grouped_tasks" not in st.session_state:
22
  st.session_state.grouped_tasks = {}
23
  if "dependencies" not in st.session_state:
@@ -26,24 +62,23 @@ if "outputs" not in st.session_state:
26
  st.session_state.outputs = {}
27
 
28
  # ------------------------------
29
- # νŽ˜μ΄μ§€ 이동 ν•¨μˆ˜
30
  # ------------------------------
31
  def goto(page):
32
  st.session_state.page = page
33
  st.rerun()
34
 
35
  # ------------------------------
36
- # Helper
37
  # ------------------------------
38
  def export_file(df, kind="csv"):
39
- if kind=="csv":
40
  return df.to_csv(index=False).encode("utf-8-sig")
41
- if kind=="xlsx":
42
- bio = BytesIO()
43
- with pd.ExcelWriter(bio, engine="openpyxl") as w:
44
- df.to_excel(w, index=False, sheet_name="tasks")
45
- bio.seek(0)
46
- return bio.getvalue()
47
 
48
  def draw_dependency_graph(df):
49
  G = nx.DiGraph()
@@ -58,7 +93,7 @@ def draw_dependency_graph(df):
58
  dep = dep.strip()
59
  if dep:
60
  G.add_edge(dep, code)
61
- nt = Network(height="550px", width="100%", directed=True, bgcolor="#FFFFFF", font_color="#222222")
62
  nt.from_nx(G)
63
  tmp_path = tempfile.NamedTemporaryFile(delete=False, suffix=".html").name
64
  nt.save_graph(tmp_path)
@@ -73,36 +108,48 @@ def draw_dependency_graph(df):
73
  if st.session_state.page == "도메인 μ„€μ •":
74
  st.title("1️⃣ 도메인 μ„€μ •")
75
  st.markdown("""
76
- νŒ€μ΄ ν•˜κ³  μžˆλŠ” 일을 3~4개의 **도메인(업무 λ²”μ£Ό)** 둜 λ‚˜λˆ„μ–΄ λ΄…λ‹ˆλ‹€.
77
- 예: μš΄μ˜κ΄€λ¦¬, ꡐ윑운영, μ§„λ‹¨κ°œλ°œ, 데이터뢄석, 기타 λ“±
78
- """)
 
79
  cols = st.columns(4)
80
  new_domains = []
81
  for i in range(4):
82
  with cols[i]:
83
- d = st.text_input(f"도메인 {i+1}", st.session_state.domains[i] if i < len(st.session_state.domains) else "")
84
- if d:
85
- new_domains.append(d)
86
  st.session_state.domains = [d for d in new_domains if d]
87
  if st.button("➑️ λ‹€μŒ: 업무 λ°œμ‚°ν•˜κΈ°") and st.session_state.domains:
88
  goto("업무 λ°œμ‚°")
89
 
90
  # ------------------------------
91
- # PAGE 2. 업무 λ°œμ‚° (자유 μž…λ ₯)
92
  # ------------------------------
93
  elif st.session_state.page == "업무 λ°œμ‚°":
94
  st.title("2️⃣ 업무 자유 λ°œμ‚°")
95
  st.markdown("""
96
- 각 λ„λ©”μΈλ³„λ‘œ νŒ€μ—μ„œ μ‹€μ œ ν•˜κ³  μžˆλŠ” 일을 κ°€λŠ₯ν•œ ν•œ 많이 μ μ–΄λ³΄μ„Έμš”.
97
- ν˜•μ‹μ€ μžμœ λ‘­μŠ΅λ‹ˆλ‹€. λ‚˜μ€‘μ— λ¬Άκ³  정리할 수 μžˆμŠ΅λ‹ˆλ‹€.
98
- """)
99
- for d in st.session_state.domains + ["기타"]:
 
 
 
 
 
 
100
  st.subheader(f"πŸ“‚ {d}")
101
- task_text = st.text_area(f"{d} λ„λ©”μΈμ˜ 업무듀", key=f"tasks_{d}", height=150,
102
- placeholder="예: 리더십 진단 κ²°κ³Ό 리포트 μž‘μ„±\n쑰직문화 ꡐ윑 기획\n데이터 정리 μžλ™ν™” λ“±")
103
- if task_text:
104
- st.session_state.grouped_tasks[d] = [t.strip() for t in task_text.split("\n") if t.strip()]
105
- if st.button("➑️ λ‹€μŒ: κ·Έλ£Ή μ‘°μ • 및 정리"):
 
 
 
 
 
106
  goto("κ·Έλ£Ή μ‘°μ •")
107
 
108
  # ------------------------------
@@ -111,101 +158,76 @@ elif st.session_state.page == "업무 λ°œμ‚°":
111
  elif st.session_state.page == "κ·Έλ£Ή μ‘°μ •":
112
  st.title("3️⃣ 업무 κ·Έλ£Ή μ •λ¦¬ν•˜κΈ°")
113
  st.markdown("""
114
- μ•„λž˜μ—μ„œ 각 도메인별 업무λ₯Ό μ •λ¦¬ν•˜μ„Έμš”.
115
- - **λ“œλž˜κ·Έ μ•€ λ“œλ‘­**으둜 μˆœμ„œ μ‘°μ •
116
- - **βž• μΆ”κ°€** λ²„νŠΌμœΌλ‘œ μƒˆ 업무 μž…λ ₯
117
- - **πŸ—‘ μ‚­μ œ**, **↔ 이동** λ²„νŠΌμœΌλ‘œ 재배치 κ°€λŠ₯
118
- """)
119
 
120
  st.divider()
121
  domains = st.session_state.domains + ["기타"]
122
  updated_grouped = {d: list(st.session_state.grouped_tasks.get(d, [])) for d in domains}
123
 
124
- # 콜백 ν•¨μˆ˜ μ •μ˜
125
  def add_task(domain):
126
  new_task = st.session_state.get(f"add_{domain}", "").strip()
127
  if new_task:
128
  updated_grouped[domain].append(new_task)
129
  st.session_state[f"add_{domain}"] = ""
130
 
131
- # 메인 도메인 반볡
132
  for d in domains:
133
  with st.container():
134
  st.subheader(f"πŸ“¦ {d}")
135
  tasks = updated_grouped.get(d, [])
136
-
137
- # μ •λ ¬
138
  sorted_tasks = sort_items(tasks, direction="vertical", key=f"sort_{d}")
139
 
140
- # 업무 μΆ”κ°€
141
  st.text_input(
142
  f"{d} 도메인에 업무 μΆ”κ°€",
143
  key=f"add_{d}",
144
- placeholder="μƒˆ 업무λ₯Ό μž…λ ₯ν•˜κ³  Enterλ₯Ό 눌러 μΆ”κ°€",
145
  on_change=add_task,
146
  args=(d,),
147
  )
148
 
149
- # μ‚­μ œ
150
  if sorted_tasks:
151
  del_col1, del_col2 = st.columns([4, 1])
152
  with del_col1:
153
- delete_target = st.selectbox(
154
- f"μ‚­μ œν•  업무 선택",
155
- ["(선택 μ—†μŒ)"] + sorted_tasks,
156
- key=f"del_{d}",
157
- )
158
  with del_col2:
159
  if st.button("πŸ—‘ μ‚­μ œ", key=f"btn_del_{d}"):
160
  if delete_target != "(선택 μ—†μŒ)":
161
  sorted_tasks.remove(delete_target)
162
- st.success(f"β€˜{delete_target}’ μ‚­μ œλ¨", icon="βœ…")
163
 
164
- # 이동
165
- if sorted_tasks:
166
- move_task = st.selectbox(
167
- f"이동할 업무 선택",
168
- ["(선택 μ—†μŒ)"] + sorted_tasks,
169
- key=f"move_{d}",
170
- )
171
- move_target_domain = st.selectbox(
172
- f"β†’ 이동할 도메인 선택",
173
- domains,
174
- key=f"moveto_{d}",
175
- )
176
  if st.button("↔ 이동", key=f"btn_move_{d}"):
177
- if move_task != "(선택 μ—†μŒ)" and move_target_domain != d:
178
  sorted_tasks.remove(move_task)
179
- updated_grouped.setdefault(move_target_domain, []).append(move_task)
180
- st.success(f"β€˜{move_task}’ β†’ {move_target_domain} 둜 이동 μ™„λ£Œ", icon="βœ…")
181
 
182
  updated_grouped[d] = sorted_tasks
183
  st.markdown("---")
184
 
185
- # λ³€κ²½ 사항 반영
186
  st.session_state.grouped_tasks = updated_grouped
187
-
188
- # λ„€λΉ„κ²Œμ΄μ…˜
189
  c1, c2 = st.columns(2)
190
  if c1.button("⬅️ 이전: λ°œμ‚° λ‹€μ‹œλ³΄κΈ°"):
191
  goto("업무 λ°œμ‚°")
192
  if c2.button("➑️ λ‹€μŒ: μ˜μ‘΄μ„± νŒλ‹¨"):
193
  goto("μ˜μ‘΄μ„± νŒλ‹¨")
194
 
195
-
196
  # ------------------------------
197
  # PAGE 4. μ˜μ‘΄μ„± νŒλ‹¨
198
  # ------------------------------
199
  elif st.session_state.page == "μ˜μ‘΄μ„± νŒλ‹¨":
200
  st.title("4️⃣ 업무 κ°„ μ˜μ‘΄μ„± νŒλ‹¨")
201
  st.markdown("""
202
- 각 업무 간에 **μ„ ν›„ 관계(μ˜μ‘΄μ„±)** κ°€ μžˆλŠ” 경우λ₯Ό μ²΄ν¬ν•˜μ„Έμš”.
203
- 예: β€œλ°μ΄ν„° μˆ˜μ§‘ β†’ 뢄석 리포트 μž‘μ„±β€μ²˜λŸΌ μˆœμ„œκ°€ ν•„μš”ν•œ 경우.
204
- """)
205
  all_tasks = [t for tasks in st.session_state.grouped_tasks.values() for t in tasks]
206
  dependencies = {}
207
  for t in all_tasks:
208
- deps = st.multiselect(f"'{t}' 이전에 ν•„μš”ν•œ μž‘μ—…", [x for x in all_tasks if x != t],
209
  default=st.session_state.dependencies.get(t, []))
210
  dependencies[t] = deps
211
  st.session_state.dependencies = dependencies
@@ -219,11 +241,9 @@ elif st.session_state.page == "μ˜μ‘΄μ„± νŒλ‹¨":
219
  # PAGE 5. μ‚°μΆœλ¬Ό μ •μ˜
220
  # ------------------------------
221
  elif st.session_state.page == "μ‚°μΆœλ¬Ό μ •μ˜":
222
- st.title("5️⃣ μ‚°μΆœλ¬Ό(Output) μ •μ˜")
223
- st.markdown("""
224
- 각 업무(Task)의 κ²°κ³Όλ¬Ό(μ‚°μΆœλ¬Ό)을 μ μ–΄μ£Όμ„Έμš”.
225
- 예: λ³΄κ³ μ„œ, λŒ€μ‹œλ³΄λ“œ, ꡐ윑자료, μžλ™ν™”μŠ€ν¬λ¦½νŠΈ λ“±
226
- """)
227
  outputs = {}
228
  for d, tasks in st.session_state.grouped_tasks.items():
229
  st.subheader(f"πŸ“‚ {d}")
@@ -238,30 +258,27 @@ elif st.session_state.page == "μ‚°μΆœλ¬Ό μ •μ˜":
238
  goto("μ΅œμ’… 정리")
239
 
240
  # ------------------------------
241
- # PAGE 6. μ΅œμ’… 정리 및 λ‹€μš΄λ‘œλ“œ
242
  # ------------------------------
243
  elif st.session_state.page == "μ΅œμ’… 정리":
244
  st.title("6️⃣ μ΅œμ’… 정리 및 λ‹€μš΄λ‘œλ“œ")
245
- st.markdown("μ•„λž˜λŠ” 전체 업무(Task) μ •λ¦¬ν‘œμž…λ‹ˆλ‹€. μ˜μ‘΄μ„± κ΄€κ³„λŠ” κ·Έλž˜ν”„λ‘œ μ‹œκ°ν™”λ©λ‹ˆλ‹€.")
246
-
247
- # λ°μ΄ν„°ν”„λ ˆμž„ ꡬ성
248
  rows = []
249
  for d, tasks in st.session_state.grouped_tasks.items():
250
  for i, t in enumerate(tasks, 1):
251
  deps = ",".join(st.session_state.dependencies.get(t, []))
252
  outp = st.session_state.outputs.get(t, "")
253
  rows.append({
254
- "domain": d, "order": i, "name": t, "depends_on": deps, "output": outp,
 
255
  "code": f"{d[:3].upper()}-{i:02d}", "lifecycle": "E"
256
  })
257
  df = pd.DataFrame(rows)
258
  st.dataframe(df, use_container_width=True)
259
-
260
- # κ·Έλž˜ν”„ ν‘œμ‹œ
261
  html = draw_dependency_graph(df)
262
  st.components.v1.html(html, height=600, scrolling=True)
263
 
264
- # λ‹€μš΄λ‘œλ“œ
265
  csv_data = export_file(df, "csv")
266
  xlsx_data = export_file(df, "xlsx")
267
  c1, c2 = st.columns(2)
@@ -269,9 +286,9 @@ elif st.session_state.page == "μ΅œμ’… 정리":
269
  c2.download_button("⬇️ Excel λ‹€μš΄λ‘œλ“œ", xlsx_data, "tasks.xlsx")
270
 
271
  if st.button("πŸ”„ 처음으둜 λŒμ•„κ°€κΈ°"):
272
- for k in ["page","domains","tasks","grouped_tasks","dependencies","outputs"]:
273
  if k in st.session_state:
274
  del st.session_state[k]
275
  goto("도메인 μ„€μ •")
276
 
277
- st.caption("Β© 2025 νŒ€ 업무 ꡬ쑰화 λ„μš°λ―Έ - Streamlit & Pyvis 기반")
 
7
  import os
8
  from streamlit_sortables import sort_items
9
 
10
+ # ------------------------------
11
+ # κΈ€λ‘œλ²Œ μŠ€νƒ€μΌ μ„€μ •
12
+ # ------------------------------
13
  st.set_page_config(page_title="νŒ€ 업무 ꡬ쑰화 λ„μš°λ―Έ (P-D-E-R-O)", layout="wide")
14
 
15
+ st.markdown("""
16
+ <style>
17
+ @import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css');
18
+ html, body, [class*="css"] {
19
+ font-family: 'Pretendard', sans-serif;
20
+ color: #222;
21
+ background-color: #f4f6f8;
22
+ }
23
+ h1, h2, h3 {
24
+ color: #2b2b2b;
25
+ font-weight: 700;
26
+ }
27
+ .stButton>button {
28
+ border-radius: 8px;
29
+ border: 1px solid #d0d7de;
30
+ background-color: white;
31
+ color: #333;
32
+ padding: 0.5rem 1rem;
33
+ transition: all 0.2s ease;
34
+ }
35
+ .stButton>button:hover {
36
+ background-color: #e8eef5;
37
+ color: #111;
38
+ }
39
+ .stTextInput>div>div>input, textarea {
40
+ border-radius: 6px;
41
+ border: 1px solid #d6d6d6;
42
+ }
43
+ .stSelectbox>div>div {
44
+ border-radius: 6px;
45
+ border: 1px solid #d6d6d6;
46
+ }
47
+ </style>
48
+ """, unsafe_allow_html=True)
49
+
50
  # ------------------------------
51
+ # μ„Έμ…˜ μ΄ˆκΈ°ν™”
52
  # ------------------------------
53
  if "page" not in st.session_state:
54
  st.session_state.page = "도메인 μ„€μ •"
55
  if "domains" not in st.session_state:
56
  st.session_state.domains = []
 
 
57
  if "grouped_tasks" not in st.session_state:
58
  st.session_state.grouped_tasks = {}
59
  if "dependencies" not in st.session_state:
 
62
  st.session_state.outputs = {}
63
 
64
  # ------------------------------
65
+ # λ„€λΉ„κ²Œμ΄μ…˜ ν•¨μˆ˜
66
  # ------------------------------
67
  def goto(page):
68
  st.session_state.page = page
69
  st.rerun()
70
 
71
  # ------------------------------
72
+ # 헬퍼 ν•¨μˆ˜
73
  # ------------------------------
74
  def export_file(df, kind="csv"):
75
+ if kind == "csv":
76
  return df.to_csv(index=False).encode("utf-8-sig")
77
+ bio = BytesIO()
78
+ with pd.ExcelWriter(bio, engine="openpyxl") as w:
79
+ df.to_excel(w, index=False, sheet_name="tasks")
80
+ bio.seek(0)
81
+ return bio.getvalue()
 
82
 
83
  def draw_dependency_graph(df):
84
  G = nx.DiGraph()
 
93
  dep = dep.strip()
94
  if dep:
95
  G.add_edge(dep, code)
96
+ nt = Network(height="550px", width="100%", directed=True, bgcolor="#FFFFFF", font_color="#222")
97
  nt.from_nx(G)
98
  tmp_path = tempfile.NamedTemporaryFile(delete=False, suffix=".html").name
99
  nt.save_graph(tmp_path)
 
108
  if st.session_state.page == "도메인 μ„€μ •":
109
  st.title("1️⃣ 도메인 μ„€μ •")
110
  st.markdown("""
111
+ νŒ€μ΄ ν•˜κ³  μžˆλŠ” 일을 3~4개의 **도메인(업무 λ²”μ£Ό)** 둜 λ‚˜λˆ„μ–΄ λ΄…λ‹ˆλ‹€.
112
+ 예: μš΄μ˜κ΄€λ¦¬, ꡐ윑운영, μ§„λ‹¨κ°œλ°œ, 데이터뢄석 λ“±
113
+ """)
114
+
115
  cols = st.columns(4)
116
  new_domains = []
117
  for i in range(4):
118
  with cols[i]:
119
+ val = st.text_input(f"도메인 {i+1}", st.session_state.domains[i] if i < len(st.session_state.domains) else "")
120
+ if val:
121
+ new_domains.append(val.strip())
122
  st.session_state.domains = [d for d in new_domains if d]
123
  if st.button("➑️ λ‹€μŒ: 업무 λ°œμ‚°ν•˜κΈ°") and st.session_state.domains:
124
  goto("업무 λ°œμ‚°")
125
 
126
  # ------------------------------
127
+ # PAGE 2. 업무 λ°œμ‚°
128
  # ------------------------------
129
  elif st.session_state.page == "업무 λ°œμ‚°":
130
  st.title("2️⃣ 업무 자유 λ°œμ‚°")
131
  st.markdown("""
132
+ 각 λ„λ©”μΈλ³„λ‘œ νŒ€μ—μ„œ μ‹€μ œ ν•˜κ³  μžˆλŠ” 일을 κ°€λŠ₯ν•œ ν•œ 많이 μ μ–΄λ³΄μ„Έμš”.
133
+ ν˜•μ‹μ€ μžμœ λ‘­μŠ΅λ‹ˆλ‹€. λ‚˜μ€‘μ— λ¬Άκ³  정리할 수 μžˆμŠ΅λ‹ˆλ‹€.
134
+ """)
135
+
136
+ unique_domains = list(dict.fromkeys(st.session_state.domains))
137
+ if "기타" not in unique_domains:
138
+ unique_domains.append("κΈ°οΏ½οΏ½οΏ½")
139
+
140
+ for d in unique_domains:
141
+ safe_key = f"tasks_{d}".replace(" ", "_").replace("/", "_")
142
  st.subheader(f"πŸ“‚ {d}")
143
+ text = st.text_area(
144
+ f"{d} λ„λ©”μΈμ˜ 업무듀",
145
+ key=safe_key,
146
+ height=150,
147
+ placeholder="예: 리더십 진단 리포트 μž‘μ„±\n쑰직문화 ꡐ윑 기획\n데이터 정리 μžλ™ν™” λ“±",
148
+ )
149
+ if text:
150
+ st.session_state.grouped_tasks[d] = [t.strip() for t in text.split("\n") if t.strip()]
151
+
152
+ if st.button("➑️ λ‹€μŒ: κ·Έλ£Ή μ‘°μ •"):
153
  goto("κ·Έλ£Ή μ‘°μ •")
154
 
155
  # ------------------------------
 
158
  elif st.session_state.page == "κ·Έλ£Ή μ‘°μ •":
159
  st.title("3️⃣ 업무 κ·Έλ£Ή μ •λ¦¬ν•˜κΈ°")
160
  st.markdown("""
161
+ μ•„λž˜μ—μ„œ 각 도메인별 업무λ₯Ό μ •λ¦¬ν•˜μ„Έμš”.
162
+ - **λ“œλž˜κ·Έ μ•€ λ“œλ‘­**으둜 μˆœμ„œ μ‘°μ •
163
+ - **βž• μΆ”κ°€**, **πŸ—‘ μ‚­μ œ**, **↔ 이동** κ°€λŠ₯
164
+ """)
 
165
 
166
  st.divider()
167
  domains = st.session_state.domains + ["기타"]
168
  updated_grouped = {d: list(st.session_state.grouped_tasks.get(d, [])) for d in domains}
169
 
 
170
  def add_task(domain):
171
  new_task = st.session_state.get(f"add_{domain}", "").strip()
172
  if new_task:
173
  updated_grouped[domain].append(new_task)
174
  st.session_state[f"add_{domain}"] = ""
175
 
 
176
  for d in domains:
177
  with st.container():
178
  st.subheader(f"πŸ“¦ {d}")
179
  tasks = updated_grouped.get(d, [])
 
 
180
  sorted_tasks = sort_items(tasks, direction="vertical", key=f"sort_{d}")
181
 
 
182
  st.text_input(
183
  f"{d} 도메인에 업무 μΆ”κ°€",
184
  key=f"add_{d}",
185
+ placeholder="μƒˆ 업무λ₯Ό μž…λ ₯ν•˜κ³  Enter",
186
  on_change=add_task,
187
  args=(d,),
188
  )
189
 
 
190
  if sorted_tasks:
191
  del_col1, del_col2 = st.columns([4, 1])
192
  with del_col1:
193
+ delete_target = st.selectbox("μ‚­μ œν•  업무", ["(선택 μ—†μŒ)"] + sorted_tasks, key=f"del_{d}")
 
 
 
 
194
  with del_col2:
195
  if st.button("πŸ—‘ μ‚­μ œ", key=f"btn_del_{d}"):
196
  if delete_target != "(선택 μ—†μŒ)":
197
  sorted_tasks.remove(delete_target)
198
+ st.success(f"β€˜{delete_target}’ μ‚­μ œλ¨")
199
 
200
+ move_task = st.selectbox("이동할 업무", ["(선택 μ—†μŒ)"] + sorted_tasks, key=f"move_{d}")
201
+ move_target = st.selectbox("β†’ 이동할 도메인", domains, key=f"moveto_{d}")
 
 
 
 
 
 
 
 
 
 
202
  if st.button("↔ 이동", key=f"btn_move_{d}"):
203
+ if move_task != "(선택 μ—†μŒ)" and move_target != d:
204
  sorted_tasks.remove(move_task)
205
+ updated_grouped.setdefault(move_target, []).append(move_task)
206
+ st.success(f"β€˜{move_task}’ β†’ {move_target} 둜 이동")
207
 
208
  updated_grouped[d] = sorted_tasks
209
  st.markdown("---")
210
 
 
211
  st.session_state.grouped_tasks = updated_grouped
 
 
212
  c1, c2 = st.columns(2)
213
  if c1.button("⬅️ 이전: λ°œμ‚° λ‹€μ‹œλ³΄κΈ°"):
214
  goto("업무 λ°œμ‚°")
215
  if c2.button("➑️ λ‹€μŒ: μ˜μ‘΄μ„± νŒλ‹¨"):
216
  goto("μ˜μ‘΄μ„± νŒλ‹¨")
217
 
 
218
  # ------------------------------
219
  # PAGE 4. μ˜μ‘΄μ„± νŒλ‹¨
220
  # ------------------------------
221
  elif st.session_state.page == "μ˜μ‘΄μ„± νŒλ‹¨":
222
  st.title("4️⃣ 업무 κ°„ μ˜μ‘΄μ„± νŒλ‹¨")
223
  st.markdown("""
224
+ 업무 κ°„ **μ„ ν›„ 관계(μ˜μ‘΄μ„±)** λ₯Ό μ„ νƒν•˜μ„Έμš”.
225
+ 예: β€œλ°μ΄ν„° μˆ˜μ§‘ β†’ 뢄석 리포트 μž‘μ„±β€
226
+ """)
227
  all_tasks = [t for tasks in st.session_state.grouped_tasks.values() for t in tasks]
228
  dependencies = {}
229
  for t in all_tasks:
230
+ deps = st.multiselect(f"'{t}' 이전에 ν•„μš”ν•œ 업무", [x for x in all_tasks if x != t],
231
  default=st.session_state.dependencies.get(t, []))
232
  dependencies[t] = deps
233
  st.session_state.dependencies = dependencies
 
241
  # PAGE 5. μ‚°μΆœλ¬Ό μ •μ˜
242
  # ------------------------------
243
  elif st.session_state.page == "μ‚°μΆœλ¬Ό μ •μ˜":
244
+ st.title("5️⃣ μ‚°μΆœλ¬Ό μ •μ˜")
245
+ st.markdown("각 μ—…λ¬΄μ˜ κ²°κ³Όλ¬Ό(μ‚°μΆœλ¬Ό)을 μž…λ ₯ν•˜μ„Έμš”. 예: λ³΄κ³ μ„œ, λŒ€μ‹œλ³΄λ“œ, ꡐ윑자료 λ“±")
246
+
 
 
247
  outputs = {}
248
  for d, tasks in st.session_state.grouped_tasks.items():
249
  st.subheader(f"πŸ“‚ {d}")
 
258
  goto("μ΅œμ’… 정리")
259
 
260
  # ------------------------------
261
+ # PAGE 6. μ΅œμ’… 정리
262
  # ------------------------------
263
  elif st.session_state.page == "μ΅œμ’… 정리":
264
  st.title("6️⃣ μ΅œμ’… 정리 및 λ‹€μš΄λ‘œλ“œ")
265
+ st.markdown("μ •λ¦¬λœ 업무 ꡬ쑰λ₯Ό ν™•μΈν•˜κ³ , κ·Έλž˜ν”„μ™€ 파일둜 λ‹€μš΄λ‘œλ“œν•˜μ„Έμš”.")
266
+
 
267
  rows = []
268
  for d, tasks in st.session_state.grouped_tasks.items():
269
  for i, t in enumerate(tasks, 1):
270
  deps = ",".join(st.session_state.dependencies.get(t, []))
271
  outp = st.session_state.outputs.get(t, "")
272
  rows.append({
273
+ "domain": d, "order": i, "name": t,
274
+ "depends_on": deps, "output": outp,
275
  "code": f"{d[:3].upper()}-{i:02d}", "lifecycle": "E"
276
  })
277
  df = pd.DataFrame(rows)
278
  st.dataframe(df, use_container_width=True)
 
 
279
  html = draw_dependency_graph(df)
280
  st.components.v1.html(html, height=600, scrolling=True)
281
 
 
282
  csv_data = export_file(df, "csv")
283
  xlsx_data = export_file(df, "xlsx")
284
  c1, c2 = st.columns(2)
 
286
  c2.download_button("⬇️ Excel λ‹€μš΄λ‘œλ“œ", xlsx_data, "tasks.xlsx")
287
 
288
  if st.button("πŸ”„ 처음으둜 λŒμ•„κ°€κΈ°"):
289
+ for k in ["page","domains","grouped_tasks","dependencies","outputs"]:
290
  if k in st.session_state:
291
  del st.session_state[k]
292
  goto("도메인 μ„€μ •")
293
 
294
+ st.caption("Β© 2025 νŒ€ 업무 ꡬ쑰화 λ„μš°λ―Έ β€” Crystal's")