import streamlit as st import pandas as pd import numpy as np from datetime import datetime, timedelta import networkx as nx import plotly.graph_objs as go class TaskDependencyManager: def __init__(self): self.dependency_graph = nx.DiGraph() def add_task(self, task_data): """ Tambahkan tugas baru ke dalam graph dependency """ task_name = task_data['Task Name'] # Tambahkan node tugas ke graph if task_name not in self.dependency_graph.nodes(): self.dependency_graph.add_node(task_name) # Tambahkan dependency predecessor predecessor = task_data.get('Predecessor', '-') if predecessor and predecessor != '-': if predecessor not in self.dependency_graph.nodes(): self.dependency_graph.add_node(predecessor) if not self._is_cyclic_dependency(predecessor, task_name): self.dependency_graph.add_edge(predecessor, task_name) # Tambahkan dependency successor successor = task_data.get('Successor', '-') if successor and successor != '-': if successor not in self.dependency_graph.nodes(): self.dependency_graph.add_node(successor) if not self._is_cyclic_dependency(task_name, successor): self.dependency_graph.add_edge(task_name, successor) def _is_cyclic_dependency(self, source, target): """ Cek apakah menambahkan edge akan menciptakan siklus dalam graph """ try: return nx.has_path(self.dependency_graph, target, source) except nx.NetworkXError: return False def get_task_dependencies(self, task_name): """ Dapatkan predecessor dan successor suatu tugas """ try: predecessors = list(self.dependency_graph.predecessors(task_name)) successors = list(self.dependency_graph.successors(task_name)) return { 'predecessors': ', '.join(predecessors) if predecessors else '-', 'successors': ', '.join(successors) if successors else '-' } except nx.NetworkXError: return { 'predecessors': '-', 'successors': '-' } def create_dependency_visualization(self): """ Buat visualisasi graph dependency dengan posisi node yang optimal """ if not self.dependency_graph.nodes(): return None # Layout untuk positioning node try: pos = nx.kamada_kawai_layout(self.dependency_graph) except: pos = nx.spring_layout(self.dependency_graph, k=0.5, seed=42) # Trace untuk edges edge_x, edge_y = [], [] for edge in self.dependency_graph.edges(): x0, y0 = pos[edge[0]] x1, y1 = pos[edge[1]] edge_x.extend([x0, x1, None]) edge_y.extend([y0, y1, None]) edge_trace = go.Scatter( x=edge_x, y=edge_y, line=dict(width=1.5, color='#888', dash='dot'), hoverinfo='none', mode='lines') # Trace untuk nodes node_x, node_y, node_text = [], [], [] node_degrees = dict(self.dependency_graph.degree()) for node in self.dependency_graph.nodes(): x, y = pos[node] node_x.append(x) node_y.append(y) node_text.append(f"{node} (Koneksi: {node_degrees[node]})") node_trace = go.Scatter( x=node_x, y=node_y, text=node_text, mode='markers+text', textposition='top center', hoverinfo='text', marker=dict( showscale=True, colorscale='Viridis', size=[20 + 10 * node_degrees[node] for node in self.dependency_graph.nodes()], color=[node_degrees[node] for node in self.dependency_graph.nodes()], colorbar=dict( thickness=15, title='Tingkat Koneksi', xanchor='left' ), line_width=2 ) ) layout = go.Layout( title='Graph Dependency Proyek', titlefont_size=16, showlegend=False, hovermode='closest', margin=dict(b=20, l=5, r=5, t=40), xaxis=dict(showgrid=False, zeroline=False, showticklabels=False), yaxis=dict(showgrid=False, zeroline=False, showticklabels=False) ) return go.Figure(data=[edge_trace, node_trace], layout=layout) class StreamlitProjectApp: def __init__(self): # Inisialisasi session state untuk task management if 'tasks' not in st.session_state: st.session_state.tasks = pd.DataFrame(columns=[ 'Task Name', 'Start Date', 'End Date', 'Duration', 'Complexity', 'Predecessor', 'Successor', 'Progress', 'Cost' ]) # Inisialisasi dependency manager self.dependency_manager = TaskDependencyManager() # Load existing tasks from session state for _, task in st.session_state.tasks.iterrows(): self.dependency_manager.add_task(task.to_dict()) def run(self): # Konfigurasi halaman Streamlit st.set_page_config(page_title="Project Dependency Optimizer", page_icon="🚀", layout="wide") st.title("🚀 Advanced Project Dependency Optimizer") # Tab untuk berbagai fungsionalitas tab1, tab2, tab3 = st.tabs(["Manajemen Tugas", "Analisis Proyek", "Visualisasi Dependency"]) with tab1: self._render_task_management() with tab2: self._render_project_metrics() with tab3: self._render_dependency_visualization() def _render_task_management(self): st.subheader("Daftar dan Manajemen Tugas") # Buat kolom untuk editing edited_tasks = st.data_editor( st.session_state.tasks, num_rows="dynamic", column_config={ "Task Name": st.column_config.TextColumn("Nama Tugas"), "Start Date": st.column_config.DateColumn("Tanggal Mulai"), "End Date": st.column_config.DateColumn("Tanggal Selesai"), "Duration": st.column_config.NumberColumn("Durasi (Hari)", min_value=1), "Complexity": st.column_config.SelectboxColumn( "Kompleksitas", options=['Low', 'Medium', 'High'] ), "Predecessor": st.column_config.TextColumn("Predecessor"), "Successor": st.column_config.TextColumn("Successor"), "Progress": st.column_config.ProgressColumn( "Progress", format="%d%%", min_value=0, max_value=100 ), "Cost": st.column_config.NumberColumn("Biaya", min_value=0) }, use_container_width=True ) # Tombol update if st.button("Perbarui Data Tugas"): try: # Validasi duplikasi nama tugas if len(edited_tasks['Task Name'].unique()) != len(edited_tasks): st.error("Terdapat duplikasi nama tugas. Setiap tugas harus memiliki nama unik.") return # Update session state st.session_state.tasks = edited_tasks # Reset dependency manager self.dependency_manager = TaskDependencyManager() for _, task in st.session_state.tasks.iterrows(): self.dependency_manager.add_task(task.to_dict()) # Re-run dengan metode kompatibel multi-versi if hasattr(st, 'experimental_rerun'): st.experimental_rerun() elif hasattr(st, 'rerun'): st.rerun() else: st.experimental_rerun() except Exception as e: st.error(f"Gagal memperbarui data: {e}") def _render_project_metrics(self): st.subheader("Ringkasan Metrik Proyek") metrics = self._calculate_project_metrics() if metrics: col1, col2, col3 = st.columns(3) with col1: st.metric("Total Tugas", metrics['Total Tasks']) st.metric("Total Durasi (Hari)", metrics['Total Duration']) with col2: st.metric("Total Biaya", f"Rp {metrics['Total Cost']:,}") st.metric("Rata-rata Kompleksitas", {1: 'Low', 2: 'Medium', 3: 'High'}.get(round(metrics['Avg Complexity']), 'N/A') ) with col3: st.metric("Rata-rata Progress", f"{metrics['Avg Progress']:.2f}%") else: st.info("Belum ada tugas yang ditambahkan") def _calculate_project_metrics(self): """ Hitung metrik proyek secara keseluruhan """ if st.session_state.tasks.empty: return None tasks = st.session_state.tasks return { 'Total Tasks': len(tasks), 'Total Duration': tasks['Duration'].sum(), 'Total Cost': tasks['Cost'].sum(), 'Avg Complexity': tasks['Complexity'].map({'Low': 1, 'Medium': 2, 'High': 3}).mean(), 'Avg Progress': tasks['Progress'].mean() } def _render_dependency_visualization(self): st.subheader("Visualisasi Graph Dependency") try: dependency_graph_fig = self.dependency_manager.create_dependency_visualization() if dependency_graph_fig: st.plotly_chart(dependency_graph_fig, use_container_width=True) else: st.info("Belum ada dependency yang dibuat") except Exception as e: st.error(f"Gagal membuat visualisasi: {e}") def main(): app = StreamlitProjectApp() app.run() if __name__ == "__main__": main()