Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -6,43 +6,46 @@ from io import BytesIO
|
|
| 6 |
import tempfile
|
| 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-
|
| 20 |
-
|
| 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 |
-
|
|
|
|
| 42 |
}
|
| 43 |
-
.
|
|
|
|
|
|
|
| 44 |
border-radius: 6px;
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
}
|
| 47 |
</style>
|
| 48 |
""", unsafe_allow_html=True)
|
|
@@ -50,42 +53,38 @@ st.markdown("""
|
|
| 50 |
# ------------------------------
|
| 51 |
# ์ธ์
์ด๊ธฐํ
|
| 52 |
# ------------------------------
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
if
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
if "outputs" 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 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
| 82 |
|
| 83 |
def draw_dependency_graph(df):
|
| 84 |
G = nx.DiGraph()
|
| 85 |
color_map = {"P": "#A7C7E7", "D": "#FFE8A3", "E": "#A8E6CF", "R": "#FFD3B6", "O": "#FFAAA5"}
|
| 86 |
for _, row in df.iterrows():
|
| 87 |
code = row["code"]
|
| 88 |
-
label =
|
| 89 |
lifecycle = row.get("lifecycle", "E")
|
| 90 |
color = color_map.get(lifecycle, "#CFCFCF")
|
| 91 |
G.add_node(code, label=label, color=color)
|
|
@@ -93,7 +92,7 @@ def draw_dependency_graph(df):
|
|
| 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="#
|
| 97 |
nt.from_nx(G)
|
| 98 |
tmp_path = tempfile.NamedTemporaryFile(delete=False, suffix=".html").name
|
| 99 |
nt.save_graph(tmp_path)
|
|
@@ -103,168 +102,113 @@ def draw_dependency_graph(df):
|
|
| 103 |
return html
|
| 104 |
|
| 105 |
# ------------------------------
|
| 106 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
# ------------------------------
|
| 108 |
if st.session_state.page == "๋๋ฉ์ธ ์ค์ ":
|
| 109 |
st.title("1๏ธโฃ ๋๋ฉ์ธ ์ค์ ")
|
| 110 |
st.markdown("""
|
| 111 |
-
ํ
|
| 112 |
-
์: ์ด์๊ด๋ฆฌ
|
| 113 |
""")
|
| 114 |
-
|
| 115 |
cols = st.columns(4)
|
| 116 |
new_domains = []
|
| 117 |
for i in range(4):
|
| 118 |
with cols[i]:
|
| 119 |
-
|
| 120 |
-
if
|
| 121 |
-
new_domains.append(
|
| 122 |
st.session_state.domains = [d for d in new_domains if d]
|
| 123 |
-
if st.button("โก๏ธ ๋ค์: ์
๋ฌด ๋ฐ์ฐ
|
| 124 |
goto("์
๋ฌด ๋ฐ์ฐ")
|
| 125 |
|
| 126 |
# ------------------------------
|
| 127 |
-
#
|
| 128 |
# ------------------------------
|
| 129 |
-
|
| 130 |
-
st.title("2๏ธโฃ ์
๋ฌด
|
| 131 |
-
st.markdown(""
|
| 132 |
-
๊ฐ ๋๋ฉ์ธ๋ณ๋ก ํ์์ ์ค์ ํ๊ณ ์๋ ์ผ์ ๊ฐ๋ฅํ ํ ๋ง์ด ์ ์ด๋ณด์ธ์.
|
| 133 |
-
ํ์์ ์์ ๋กญ์ต๋๋ค. ๋์ค์ ๋ฌถ๊ณ ์ ๋ฆฌํ ์ ์์ต๋๋ค.
|
| 134 |
-
""")
|
| 135 |
for d in st.session_state.domains + ["๊ธฐํ"]:
|
| 136 |
st.subheader(f"๐ {d}")
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
placeholder="์: ๋ฆฌ๋์ญ ์ง๋จ ๊ฒฐ๊ณผ ๋ฆฌํฌํธ ์์ฑ\n์กฐ์ง๋ฌธํ ๊ต์ก ๊ธฐํ\n๋ฐ์ดํฐ ์ ๋ฆฌ ์๋ํ ๋ฑ"
|
| 142 |
-
)
|
| 143 |
-
if task_text:
|
| 144 |
-
st.session_state.grouped_tasks[d] = [
|
| 145 |
-
t.strip() for t in task_text.split("\n") if t.strip()
|
| 146 |
-
]
|
| 147 |
if st.button("โก๏ธ ๋ค์: ๊ทธ๋ฃน ์กฐ์ "):
|
| 148 |
goto("๊ทธ๋ฃน ์กฐ์ ")
|
| 149 |
|
| 150 |
-
|
| 151 |
# ------------------------------
|
| 152 |
-
#
|
| 153 |
# ------------------------------
|
| 154 |
elif st.session_state.page == "๊ทธ๋ฃน ์กฐ์ ":
|
| 155 |
-
st.title("3๏ธโฃ ์
๋ฌด ๊ทธ๋ฃน ์
|
| 156 |
-
st.markdown(""
|
| 157 |
-
๊ฐ ๋๋ฉ์ธ๋ณ๋ก ์ ๋ฆฌ๋ ์
๋ฌด๋ฅผ ๊ด๋ฆฌํ์ธ์.
|
| 158 |
-
- **๋๋๊ทธ ์ค ๋๋กญ**์ผ๋ก ์์๋ฅผ ๋ฐ๊ฟ ์ ์์ต๋๋ค.
|
| 159 |
-
- **๐ ๋ฒํผ**์ผ๋ก ์ญ์ ํ ์ ์์ต๋๋ค.
|
| 160 |
-
- **โ ์ ํ๋ฐ์ค**๋ฅผ ํตํด ๋ค๋ฅธ ๋๋ฉ์ธ์ผ๋ก ์ด๋ํ ์ ์์ต๋๋ค.
|
| 161 |
-
- **โ ์ถ๊ฐ ๋ฒํผ**์ผ๋ก ์ ์
๋ฌด๋ฅผ ๋ฑ๋กํ ์ ์์ต๋๋ค.
|
| 162 |
-
""")
|
| 163 |
-
|
| 164 |
st.divider()
|
| 165 |
-
domains = st.session_state.domains + ["๊ธฐํ"]
|
| 166 |
-
updated_grouped = {
|
| 167 |
-
d: list(st.session_state.grouped_tasks.get(d, [])) for d in domains
|
| 168 |
-
}
|
| 169 |
|
| 170 |
-
|
|
|
|
| 171 |
palette = ["#E3F2FD", "#E8F5E9", "#FFF8E1", "#FCE4EC", "#E0F7FA"]
|
| 172 |
-
domain_colors = {d: palette[i % len(palette)] for i, d in enumerate(domains)}
|
| 173 |
-
|
| 174 |
-
# ์คํ์ผ
|
| 175 |
-
st.markdown("""
|
| 176 |
-
<style>
|
| 177 |
-
.task-card {
|
| 178 |
-
background-color: #fff;
|
| 179 |
-
border: 1px solid #ddd;
|
| 180 |
-
border-radius: 8px;
|
| 181 |
-
padding: 6px 10px;
|
| 182 |
-
margin-bottom: 6px;
|
| 183 |
-
font-size: 14px;
|
| 184 |
-
}
|
| 185 |
-
.domain-box {
|
| 186 |
-
border-radius: 10px;
|
| 187 |
-
padding: 10px;
|
| 188 |
-
margin: 4px;
|
| 189 |
-
}
|
| 190 |
-
</style>
|
| 191 |
-
""", unsafe_allow_html=True)
|
| 192 |
-
|
| 193 |
cols = st.columns(len(domains))
|
| 194 |
for i, d in enumerate(domains):
|
| 195 |
with cols[i]:
|
| 196 |
-
|
| 197 |
-
st.markdown(
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
)
|
| 201 |
-
st.markdown(f"### ๐ฆ {d}")
|
| 202 |
-
|
| 203 |
-
tasks = updated_grouped.get(d, [])
|
| 204 |
-
new_task_list = []
|
| 205 |
-
|
| 206 |
-
# ์ ๋ ฌ
|
| 207 |
-
sorted_tasks = sort_items(tasks, direction="vertical", key=f"sort_{d}")
|
| 208 |
-
|
| 209 |
for t in sorted_tasks:
|
| 210 |
c1, c2, c3 = st.columns([4, 1, 1])
|
| 211 |
with c1:
|
| 212 |
st.markdown(f"<div class='task-card'>{t}</div>", unsafe_allow_html=True)
|
| 213 |
with c2:
|
| 214 |
if st.button("๐", key=f"del_{d}_{t}"):
|
| 215 |
-
|
| 216 |
-
x for x in sorted_tasks if x != t
|
| 217 |
-
]
|
| 218 |
st.rerun()
|
| 219 |
with c3:
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
[
|
| 223 |
-
|
| 224 |
-
)
|
| 225 |
-
if move_target != "(์ด๋)":
|
| 226 |
-
st.session_state.grouped_tasks.setdefault(move_target, []).append(t)
|
| 227 |
-
st.session_state.grouped_tasks[d] = [
|
| 228 |
-
x for x in sorted_tasks if x != t
|
| 229 |
-
]
|
| 230 |
st.rerun()
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
new_task = st.text_input(f"{d} ์ ์
๋ฌด", key=f"add_{i}")
|
| 235 |
-
if st.button(f"โ ์ถ๊ฐ ({d})", key=f"btn_add_{i}") and new_task.strip():
|
| 236 |
-
new_task_list.append(new_task.strip())
|
| 237 |
-
st.session_state.grouped_tasks[d] = new_task_list
|
| 238 |
st.rerun()
|
| 239 |
-
|
| 240 |
-
updated_grouped[d] = new_task_list
|
| 241 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 242 |
-
|
| 243 |
-
st.session_state.grouped_tasks = updated_grouped
|
| 244 |
-
|
| 245 |
-
st.divider()
|
| 246 |
c1, c2 = st.columns(2)
|
| 247 |
-
if c1.button("โฌ
๏ธ ์ด์ : ์
๋ฌด ๋ฐ์ฐ
|
| 248 |
goto("์
๋ฌด ๋ฐ์ฐ")
|
| 249 |
if c2.button("โก๏ธ ๋ค์: ์์กด์ฑ ํ๋จ"):
|
| 250 |
goto("์์กด์ฑ ํ๋จ")
|
| 251 |
|
| 252 |
# ------------------------------
|
| 253 |
-
#
|
| 254 |
# ------------------------------
|
| 255 |
elif st.session_state.page == "์์กด์ฑ ํ๋จ":
|
| 256 |
-
st.title("4๏ธโฃ
|
| 257 |
-
st.markdown(""
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
|
|
|
|
|
|
|
|
|
| 268 |
c1, c2 = st.columns(2)
|
| 269 |
if c1.button("โฌ
๏ธ ์ด์ : ๊ทธ๋ฃน ์กฐ์ "):
|
| 270 |
goto("๊ทธ๋ฃน ์กฐ์ ")
|
|
@@ -272,19 +216,18 @@ elif st.session_state.page == "์์กด์ฑ ํ๋จ":
|
|
| 272 |
goto("์ฐ์ถ๋ฌผ ์ ์")
|
| 273 |
|
| 274 |
# ------------------------------
|
| 275 |
-
#
|
| 276 |
# ------------------------------
|
| 277 |
elif st.session_state.page == "์ฐ์ถ๋ฌผ ์ ์":
|
| 278 |
st.title("5๏ธโฃ ์ฐ์ถ๋ฌผ ์ ์")
|
| 279 |
-
st.markdown("๊ฐ ์
๋ฌด
|
| 280 |
-
|
| 281 |
-
outputs = {}
|
| 282 |
for d, tasks in st.session_state.grouped_tasks.items():
|
| 283 |
st.subheader(f"๐ {d}")
|
| 284 |
for t in tasks:
|
| 285 |
-
val = st.text_input(f"{t} โ ์ฐ์ถ๋ฌผ", value=st.session_state.outputs.get(t, ""))
|
| 286 |
-
|
| 287 |
-
st.session_state.outputs =
|
| 288 |
c1, c2 = st.columns(2)
|
| 289 |
if c1.button("โฌ
๏ธ ์ด์ : ์์กด์ฑ ํ๋จ"):
|
| 290 |
goto("์์กด์ฑ ํ๋จ")
|
|
@@ -292,37 +235,70 @@ elif st.session_state.page == "์ฐ์ถ๋ฌผ ์ ์":
|
|
| 292 |
goto("์ต์ข
์ ๋ฆฌ")
|
| 293 |
|
| 294 |
# ------------------------------
|
| 295 |
-
#
|
| 296 |
# ------------------------------
|
| 297 |
elif st.session_state.page == "์ต์ข
์ ๋ฆฌ":
|
| 298 |
-
st.title("6๏ธโฃ ์ต์ข
์ ๋ฆฌ ๋ฐ
|
| 299 |
-
st.markdown("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
rows = []
|
| 302 |
for d, tasks in st.session_state.grouped_tasks.items():
|
| 303 |
for i, t in enumerate(tasks, 1):
|
| 304 |
deps = ",".join(st.session_state.dependencies.get(t, []))
|
| 305 |
outp = st.session_state.outputs.get(t, "")
|
|
|
|
| 306 |
rows.append({
|
| 307 |
-
"domain": d,
|
| 308 |
-
"
|
| 309 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
})
|
| 311 |
df = pd.DataFrame(rows)
|
| 312 |
st.dataframe(df, use_container_width=True)
|
|
|
|
|
|
|
| 313 |
html = draw_dependency_graph(df)
|
| 314 |
st.components.v1.html(html, height=600, scrolling=True)
|
| 315 |
|
|
|
|
| 316 |
csv_data = export_file(df, "csv")
|
| 317 |
xlsx_data = export_file(df, "xlsx")
|
| 318 |
c1, c2 = st.columns(2)
|
| 319 |
-
c1.download_button("โฌ๏ธ CSV ๋ค์ด๋ก๋", csv_data, "
|
| 320 |
-
c2.download_button("โฌ๏ธ Excel ๋ค์ด๋ก๋", xlsx_data, "
|
| 321 |
|
| 322 |
if st.button("๐ ์ฒ์์ผ๋ก ๋์๊ฐ๊ธฐ"):
|
| 323 |
-
for k in ["page","domains","grouped_tasks","dependencies","outputs"]:
|
| 324 |
-
if k
|
| 325 |
-
del st.session_state[k]
|
| 326 |
goto("๋๋ฉ์ธ ์ค์ ")
|
| 327 |
|
| 328 |
-
st.caption("ยฉ 2025 ํ ์
๋ฌด ๊ตฌ์กฐํ ๋์ฐ๋ฏธ
|
|
|
|
| 6 |
import tempfile
|
| 7 |
import os
|
| 8 |
from streamlit_sortables import sort_items
|
| 9 |
+
import hashlib
|
| 10 |
|
| 11 |
# ------------------------------
|
| 12 |
+
# ๊ธฐ๋ณธ ์ค์
|
| 13 |
# ------------------------------
|
| 14 |
st.set_page_config(page_title="ํ ์
๋ฌด ๊ตฌ์กฐํ ๋์ฐ๋ฏธ (P-D-E-R-O)", layout="wide")
|
| 15 |
|
| 16 |
+
# ------------------------------
|
| 17 |
+
# ์ ์ญ ์คํ์ผ
|
| 18 |
+
# ------------------------------
|
| 19 |
st.markdown("""
|
| 20 |
<style>
|
|
|
|
| 21 |
html, body, [class*="css"] {
|
| 22 |
+
font-size: 14px !important;
|
| 23 |
+
font-family: "Noto Sans KR", "Helvetica", sans-serif;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
.stButton>button {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
border-radius: 6px;
|
| 27 |
+
padding: 4px 10px;
|
| 28 |
+
font-size: 13px;
|
| 29 |
}
|
| 30 |
+
.task-card {
|
| 31 |
+
background-color: #fff;
|
| 32 |
+
border: 1px solid #ddd;
|
| 33 |
border-radius: 6px;
|
| 34 |
+
padding: 6px 10px;
|
| 35 |
+
margin-bottom: 6px;
|
| 36 |
+
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
| 37 |
+
font-size: 13px;
|
| 38 |
+
}
|
| 39 |
+
.domain-box {
|
| 40 |
+
border-radius: 8px;
|
| 41 |
+
padding: 8px;
|
| 42 |
+
margin: 6px 4px;
|
| 43 |
+
}
|
| 44 |
+
.domain-header {
|
| 45 |
+
font-weight: 600;
|
| 46 |
+
font-size: 15px;
|
| 47 |
+
text-align: center;
|
| 48 |
+
margin-bottom: 6px;
|
| 49 |
}
|
| 50 |
</style>
|
| 51 |
""", unsafe_allow_html=True)
|
|
|
|
| 53 |
# ------------------------------
|
| 54 |
# ์ธ์
์ด๊ธฐํ
|
| 55 |
# ------------------------------
|
| 56 |
+
for key in ["page", "domains", "grouped_tasks", "dependencies", "outputs", "code_map"]:
|
| 57 |
+
if key not in st.session_state:
|
| 58 |
+
if key == "page":
|
| 59 |
+
st.session_state[key] = "๋๋ฉ์ธ ์ค์ "
|
| 60 |
+
elif key in ["grouped_tasks", "dependencies", "outputs", "code_map"]:
|
| 61 |
+
st.session_state[key] = {}
|
| 62 |
+
else:
|
| 63 |
+
st.session_state[key] = []
|
|
|
|
|
|
|
| 64 |
|
| 65 |
# ------------------------------
|
| 66 |
+
# Helper Functions
|
| 67 |
# ------------------------------
|
| 68 |
def goto(page):
|
| 69 |
st.session_state.page = page
|
| 70 |
st.rerun()
|
| 71 |
|
|
|
|
|
|
|
|
|
|
| 72 |
def export_file(df, kind="csv"):
|
| 73 |
if kind == "csv":
|
| 74 |
return df.to_csv(index=False).encode("utf-8-sig")
|
| 75 |
+
if kind == "xlsx":
|
| 76 |
+
bio = BytesIO()
|
| 77 |
+
with pd.ExcelWriter(bio, engine="openpyxl") as w:
|
| 78 |
+
df.to_excel(w, index=False, sheet_name="tasks")
|
| 79 |
+
bio.seek(0)
|
| 80 |
+
return bio.getvalue()
|
| 81 |
|
| 82 |
def draw_dependency_graph(df):
|
| 83 |
G = nx.DiGraph()
|
| 84 |
color_map = {"P": "#A7C7E7", "D": "#FFE8A3", "E": "#A8E6CF", "R": "#FFD3B6", "O": "#FFAAA5"}
|
| 85 |
for _, row in df.iterrows():
|
| 86 |
code = row["code"]
|
| 87 |
+
label = row["name"]
|
| 88 |
lifecycle = row.get("lifecycle", "E")
|
| 89 |
color = color_map.get(lifecycle, "#CFCFCF")
|
| 90 |
G.add_node(code, label=label, color=color)
|
|
|
|
| 92 |
dep = dep.strip()
|
| 93 |
if dep:
|
| 94 |
G.add_edge(dep, code)
|
| 95 |
+
nt = Network(height="550px", width="100%", directed=True, bgcolor="#FFFFFF", font_color="#222222")
|
| 96 |
nt.from_nx(G)
|
| 97 |
tmp_path = tempfile.NamedTemporaryFile(delete=False, suffix=".html").name
|
| 98 |
nt.save_graph(tmp_path)
|
|
|
|
| 102 |
return html
|
| 103 |
|
| 104 |
# ------------------------------
|
| 105 |
+
# ์๋จ ์งํ๋ฐ
|
| 106 |
+
# ------------------------------
|
| 107 |
+
steps = [
|
| 108 |
+
"๋๋ฉ์ธ ์ค์ ", "์
๋ฌด ๋ฐ์ฐ", "๊ทธ๋ฃน ์กฐ์ ", "์์กด์ฑ ํ๋จ", "์ฐ์ถ๋ฌผ ์ ์", "์ต์ข
์ ๋ฆฌ"
|
| 109 |
+
]
|
| 110 |
+
current_idx = steps.index(st.session_state.page) if st.session_state.page in steps else 0
|
| 111 |
+
progress_ratio = (current_idx + 1) / len(steps)
|
| 112 |
+
st.progress(progress_ratio, text=f"๋จ๊ณ {current_idx+1} / {len(steps)} : {st.session_state.page}")
|
| 113 |
+
|
| 114 |
+
# ------------------------------
|
| 115 |
+
# 1๏ธโฃ ๋๋ฉ์ธ ์ค์
|
| 116 |
# ------------------------------
|
| 117 |
if st.session_state.page == "๋๋ฉ์ธ ์ค์ ":
|
| 118 |
st.title("1๏ธโฃ ๋๋ฉ์ธ ์ค์ ")
|
| 119 |
st.markdown("""
|
| 120 |
+
ํ์ ์ฃผ์ ์
๋ฌด ๋๋ฉ์ธ์ 3~4๊ฐ๋ก ์ ์ํด๋ณด์ธ์.
|
| 121 |
+
์: ์ด์๊ด๋ฆฌ / ๊ต์ก์ด์ / ์ง๋จ๊ฐ๋ฐ / ๋ฐ์ดํฐ๋ถ์
|
| 122 |
""")
|
|
|
|
| 123 |
cols = st.columns(4)
|
| 124 |
new_domains = []
|
| 125 |
for i in range(4):
|
| 126 |
with cols[i]:
|
| 127 |
+
d = st.text_input(f"๋๋ฉ์ธ {i+1}", st.session_state.domains[i] if i < len(st.session_state.domains) else "")
|
| 128 |
+
if d:
|
| 129 |
+
new_domains.append(d)
|
| 130 |
st.session_state.domains = [d for d in new_domains if d]
|
| 131 |
+
if st.button("โก๏ธ ๋ค์: ์
๋ฌด ๋ฐ์ฐ"):
|
| 132 |
goto("์
๋ฌด ๋ฐ์ฐ")
|
| 133 |
|
| 134 |
# ------------------------------
|
| 135 |
+
# 2๏ธโฃ ์
๋ฌด ๋ฐ์ฐ
|
| 136 |
# ------------------------------
|
| 137 |
+
elif st.session_state.page == "์
๋ฌด ๋ฐ์ฐ":
|
| 138 |
+
st.title("2๏ธโฃ ์
๋ฌด ๋ฐ์ฐ")
|
| 139 |
+
st.markdown("๊ฐ ๋๋ฉ์ธ๋ณ๋ก ํ์ด ์ค์ ์ํ ์ค์ธ ์
๋ฌด๋ฅผ ์์ ๋กญ๊ฒ ๋์ดํ์ธ์.")
|
|
|
|
|
|
|
|
|
|
| 140 |
for d in st.session_state.domains + ["๊ธฐํ"]:
|
| 141 |
st.subheader(f"๐ {d}")
|
| 142 |
+
text = st.text_area(f"{d} ์
๋ฌด ์
๋ ฅ", key=f"tasks_{d}", height=150,
|
| 143 |
+
placeholder="์: ๋ฆฌ๋์ญ ์ง๋จ ๊ฒฐ๊ณผ ๋ฆฌํฌํธ ์์ฑ\n์กฐ์ง๋ฌธํ ๊ต์ก ๊ธฐํ\n๋ฐ์ดํฐ ์ ๋ฆฌ ์๋ํ ๋ฑ")
|
| 144 |
+
if text:
|
| 145 |
+
st.session_state.grouped_tasks[d] = [t.strip() for t in text.split("\n") if t.strip()]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
if st.button("โก๏ธ ๋ค์: ๊ทธ๋ฃน ์กฐ์ "):
|
| 147 |
goto("๊ทธ๋ฃน ์กฐ์ ")
|
| 148 |
|
|
|
|
| 149 |
# ------------------------------
|
| 150 |
+
# 3๏ธโฃ ๊ทธ๋ฃน ์กฐ์
|
| 151 |
# ------------------------------
|
| 152 |
elif st.session_state.page == "๊ทธ๋ฃน ์กฐ์ ":
|
| 153 |
+
st.title("3๏ธโฃ ์
๋ฌด ๊ทธ๋ฃน ์กฐ์ ")
|
| 154 |
+
st.markdown("๋๋๊ทธ ์ค ๋๋กญ์ผ๋ก ์
๋ฌด ์์๋ฅผ ์กฐ์ ํ๊ณ , ํ์ ์ ๋๋ฉ์ธ ๊ฐ ์ด๋ํ์ธ์.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
st.divider()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
+
domains = st.session_state.domains + ["๊ธฐํ"]
|
| 158 |
+
updated = {d: list(st.session_state.grouped_tasks.get(d, [])) for d in domains}
|
| 159 |
palette = ["#E3F2FD", "#E8F5E9", "#FFF8E1", "#FCE4EC", "#E0F7FA"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
cols = st.columns(len(domains))
|
| 161 |
for i, d in enumerate(domains):
|
| 162 |
with cols[i]:
|
| 163 |
+
bg = palette[i % len(palette)]
|
| 164 |
+
st.markdown(f"<div class='domain-box' style='background-color:{bg};'>", unsafe_allow_html=True)
|
| 165 |
+
st.markdown(f"<div class='domain-header'>๐ฆ {d}</div>", unsafe_allow_html=True)
|
| 166 |
+
sorted_tasks = sort_items(updated[d], direction="vertical", key=f"sort_{d}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
for t in sorted_tasks:
|
| 168 |
c1, c2, c3 = st.columns([4, 1, 1])
|
| 169 |
with c1:
|
| 170 |
st.markdown(f"<div class='task-card'>{t}</div>", unsafe_allow_html=True)
|
| 171 |
with c2:
|
| 172 |
if st.button("๐", key=f"del_{d}_{t}"):
|
| 173 |
+
updated[d].remove(t)
|
|
|
|
|
|
|
| 174 |
st.rerun()
|
| 175 |
with c3:
|
| 176 |
+
move_to = st.selectbox("โ", ["(์ด๋)"] + [x for x in domains if x != d], key=f"mv_{d}_{t}")
|
| 177 |
+
if move_to != "(์ด๋)":
|
| 178 |
+
updated[move_to].append(t)
|
| 179 |
+
updated[d].remove(t)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
st.rerun()
|
| 181 |
+
new_t = st.text_input(f"{d} ์ ์
๋ฌด", key=f"add_{i}")
|
| 182 |
+
if st.button(f"โ ์ถ๊ฐ ({d})", key=f"btn_add_{i}") and new_t.strip():
|
| 183 |
+
updated[d].append(new_t.strip())
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
st.rerun()
|
|
|
|
|
|
|
| 185 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 186 |
+
st.session_state.grouped_tasks = updated
|
|
|
|
|
|
|
|
|
|
| 187 |
c1, c2 = st.columns(2)
|
| 188 |
+
if c1.button("โฌ
๏ธ ์ด์ : ์
๋ฌด ๋ฐ์ฐ"):
|
| 189 |
goto("์
๋ฌด ๋ฐ์ฐ")
|
| 190 |
if c2.button("โก๏ธ ๋ค์: ์์กด์ฑ ํ๋จ"):
|
| 191 |
goto("์์กด์ฑ ํ๋จ")
|
| 192 |
|
| 193 |
# ------------------------------
|
| 194 |
+
# 4๏ธโฃ ์์กด์ฑ ํ๋จ
|
| 195 |
# ------------------------------
|
| 196 |
elif st.session_state.page == "์์กด์ฑ ํ๋จ":
|
| 197 |
+
st.title("4๏ธโฃ ์์กด์ฑ ํ๋จ")
|
| 198 |
+
st.markdown("๊ฐ ์
๋ฌด ๊ฐ์ ์ ํ ๊ด๊ณ(์์กด์ฑ)๋ฅผ ๋๋ฉ์ธ๋ณ๋ก ์ง์ ํ์ธ์.")
|
| 199 |
+
st.divider()
|
| 200 |
+
domains = st.session_state.domains + ["๊ธฐํ"]
|
| 201 |
+
deps = {}
|
| 202 |
+
for d in domains:
|
| 203 |
+
st.subheader(f"๐ {d}")
|
| 204 |
+
tasks = st.session_state.grouped_tasks.get(d, [])
|
| 205 |
+
all_tasks = [t for td in st.session_state.grouped_tasks.values() for t in td]
|
| 206 |
+
for t in tasks:
|
| 207 |
+
key = f"deps_{hashlib.md5(f'{d}_{t}'.encode()).hexdigest()[:8]}"
|
| 208 |
+
selected = st.multiselect(f"'{t}' ์ด์ ์ ํ์ํ ์
๋ฌด", [x for x in all_tasks if x != t],
|
| 209 |
+
default=st.session_state.dependencies.get(t, []), key=key)
|
| 210 |
+
deps[t] = selected
|
| 211 |
+
st.session_state.dependencies = deps
|
| 212 |
c1, c2 = st.columns(2)
|
| 213 |
if c1.button("โฌ
๏ธ ์ด์ : ๊ทธ๋ฃน ์กฐ์ "):
|
| 214 |
goto("๊ทธ๋ฃน ์กฐ์ ")
|
|
|
|
| 216 |
goto("์ฐ์ถ๋ฌผ ์ ์")
|
| 217 |
|
| 218 |
# ------------------------------
|
| 219 |
+
# 5๏ธโฃ ์ฐ์ถ๋ฌผ ์ ์
|
| 220 |
# ------------------------------
|
| 221 |
elif st.session_state.page == "์ฐ์ถ๋ฌผ ์ ์":
|
| 222 |
st.title("5๏ธโฃ ์ฐ์ถ๋ฌผ ์ ์")
|
| 223 |
+
st.markdown("๊ฐ ์
๋ฌด๊ฐ ๋ง๋ค์ด๋ด๋ ์ฐ์ถ๋ฌผ(Output)์ ์
๋ ฅํ์ธ์.")
|
| 224 |
+
outs = {}
|
|
|
|
| 225 |
for d, tasks in st.session_state.grouped_tasks.items():
|
| 226 |
st.subheader(f"๐ {d}")
|
| 227 |
for t in tasks:
|
| 228 |
+
val = st.text_input(f"{t} โ ์ฐ์ถ๋ฌผ", value=st.session_state.outputs.get(t, ""), key=f"out_{t}")
|
| 229 |
+
outs[t] = val
|
| 230 |
+
st.session_state.outputs = outs
|
| 231 |
c1, c2 = st.columns(2)
|
| 232 |
if c1.button("โฌ
๏ธ ์ด์ : ์์กด์ฑ ํ๋จ"):
|
| 233 |
goto("์์กด์ฑ ํ๋จ")
|
|
|
|
| 235 |
goto("์ต์ข
์ ๋ฆฌ")
|
| 236 |
|
| 237 |
# ------------------------------
|
| 238 |
+
# 6๏ธโฃ ์ต์ข
์ ๋ฆฌ + ์ฝ๋ํ ๋จ๊ณ
|
| 239 |
# ------------------------------
|
| 240 |
elif st.session_state.page == "์ต์ข
์ ๋ฆฌ":
|
| 241 |
+
st.title("6๏ธโฃ ์ต์ข
์ ๋ฆฌ ๋ฐ ์
๋ฌด ์ฝ๋ํ")
|
| 242 |
+
st.markdown("๊ฐ ์
๋ฌด๋ฅผ **์ ํ / ์ฌ์ดํด / ์์์๊ฐ** ๊ธฐ์ค์ผ๋ก ์ฝ๋ํํ์ธ์.")
|
| 243 |
+
st.divider()
|
| 244 |
+
|
| 245 |
+
type_opts = ["COMM", "CEO", "MULTI", "DEEP", "ADHOC"]
|
| 246 |
+
cycle_opts = ["P", "D", "E", "R", "O"]
|
| 247 |
+
time_opts = ["T", "FE"]
|
| 248 |
|
| 249 |
+
code_map = {}
|
| 250 |
+
for d, tasks in st.session_state.grouped_tasks.items():
|
| 251 |
+
st.subheader(f"๐ {d}")
|
| 252 |
+
for t in tasks:
|
| 253 |
+
c1, c2, c3 = st.columns(3)
|
| 254 |
+
with c1:
|
| 255 |
+
typ = st.selectbox(f"{t} - ์ ํ", type_opts, key=f"type_{t}",
|
| 256 |
+
index=type_opts.index(st.session_state.code_map.get(t, {}).get("type", "COMM")) if t in st.session_state.code_map else 0)
|
| 257 |
+
with c2:
|
| 258 |
+
cyc = st.selectbox("์ฌ์ดํด", cycle_opts, key=f"cycle_{t}",
|
| 259 |
+
index=cycle_opts.index(st.session_state.code_map.get(t, {}).get("cycle", "E")) if t in st.session_state.code_map else 2)
|
| 260 |
+
with c3:
|
| 261 |
+
tm = st.selectbox("์์์๊ฐ", time_opts, key=f"time_{t}",
|
| 262 |
+
index=time_opts.index(st.session_state.code_map.get(t, {}).get("time", "T")) if t in st.session_state.code_map else 0)
|
| 263 |
+
code_map[t] = {"type": typ, "cycle": cyc, "time": tm}
|
| 264 |
+
|
| 265 |
+
st.session_state.code_map = code_map
|
| 266 |
+
|
| 267 |
+
# ๋ฐ์ดํฐํ๋ ์ ์ ๋ฆฌ
|
| 268 |
rows = []
|
| 269 |
for d, tasks in st.session_state.grouped_tasks.items():
|
| 270 |
for i, t in enumerate(tasks, 1):
|
| 271 |
deps = ",".join(st.session_state.dependencies.get(t, []))
|
| 272 |
outp = st.session_state.outputs.get(t, "")
|
| 273 |
+
meta = st.session_state.code_map.get(t, {})
|
| 274 |
rows.append({
|
| 275 |
+
"domain": d,
|
| 276 |
+
"order": i,
|
| 277 |
+
"name": t,
|
| 278 |
+
"depends_on": deps,
|
| 279 |
+
"output": outp,
|
| 280 |
+
"type": meta.get("type", ""),
|
| 281 |
+
"cycle": meta.get("cycle", ""),
|
| 282 |
+
"time": meta.get("time", ""),
|
| 283 |
+
"code": f"{d[:3].upper()}-{i:02d}"
|
| 284 |
})
|
| 285 |
df = pd.DataFrame(rows)
|
| 286 |
st.dataframe(df, use_container_width=True)
|
| 287 |
+
|
| 288 |
+
# ๊ทธ๋ํ
|
| 289 |
html = draw_dependency_graph(df)
|
| 290 |
st.components.v1.html(html, height=600, scrolling=True)
|
| 291 |
|
| 292 |
+
# Export
|
| 293 |
csv_data = export_file(df, "csv")
|
| 294 |
xlsx_data = export_file(df, "xlsx")
|
| 295 |
c1, c2 = st.columns(2)
|
| 296 |
+
c1.download_button("โฌ๏ธ CSV ๋ค์ด๋ก๋", csv_data, "tasks_code.csv", "text/csv")
|
| 297 |
+
c2.download_button("โฌ๏ธ Excel ๋ค์ด๋ก๋", xlsx_data, "tasks_code.xlsx")
|
| 298 |
|
| 299 |
if st.button("๐ ์ฒ์์ผ๋ก ๋์๊ฐ๊ธฐ"):
|
| 300 |
+
for k in ["page", "domains", "grouped_tasks", "dependencies", "outputs", "code_map"]:
|
| 301 |
+
st.session_state[k] = [] if k == "domains" else {}
|
|
|
|
| 302 |
goto("๋๋ฉ์ธ ์ค์ ")
|
| 303 |
|
| 304 |
+
st.caption("ยฉ 2025 ํ ์
๋ฌด ๊ตฌ์กฐํ ๋์ฐ๋ฏธ โ Streamlit ๊ธฐ๋ฐ | Designed for clarity & usability")
|