| | |
| | |
| |
|
| | """ |
| | نموذج إدارة المشاريع لنظام WAHBi-AI |
| | يتضمن ميزات إضافة وإدارة المشاريع الجديدة مع معلومات موسعة |
| | """ |
| |
|
| | import os |
| | import sys |
| | import streamlit as st |
| | import pandas as pd |
| | import numpy as np |
| | import datetime |
| | import tempfile |
| | from pathlib import Path |
| | import plotly.express as px |
| | import plotly.graph_objects as go |
| | import json |
| | import time |
| | from datetime import datetime, timedelta |
| |
|
| | |
| | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) |
| |
|
| | |
| | from utils.components.header import render_header |
| | from utils.components.credits import render_credits |
| | from utils.helpers import format_number, format_currency, styled_button |
| |
|
| | class ProjectsManagement: |
| | """نموذج إدارة المشاريع""" |
| | |
| | def __init__(self): |
| | """تهيئة نموذج إدارة المشاريع""" |
| | |
| | if 'projects' not in st.session_state: |
| | st.session_state.projects = [] |
| | |
| | if 'project_files' not in st.session_state: |
| | st.session_state.project_files = {} |
| | |
| | if 'project_inquiries' not in st.session_state: |
| | st.session_state.project_inquiries = {} |
| | |
| | |
| | self.projects_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../data/projects")) |
| | os.makedirs(self.projects_dir, exist_ok=True) |
| | |
| | def render(self): |
| | """عرض واجهة إدارة المشاريع""" |
| | |
| | render_header("إدارة المشاريع") |
| | |
| | |
| | tabs = st.tabs(["المشاريع الحالية", "إضافة مشروع جديد", "أرشيف المشاريع", "التقارير"]) |
| | |
| | with tabs[0]: |
| | self._render_current_projects() |
| | |
| | with tabs[1]: |
| | self._render_new_project_form() |
| | |
| | with tabs[2]: |
| | self._render_archived_projects() |
| | |
| | with tabs[3]: |
| | self._render_projects_reports() |
| | |
| | |
| | render_credits() |
| | |
| | def _render_current_projects(self): |
| | """عرض قائمة المشاريع الحالية""" |
| | st.markdown(""" |
| | <div class='custom-box info-box'> |
| | <h3>🏗️ المشاريع الحالية</h3> |
| | <p>قائمة المشاريع النشطة التي يتم العمل عليها حالياً.</p> |
| | </div> |
| | """, unsafe_allow_html=True) |
| | |
| | |
| | active_projects = [p for p in st.session_state.projects if p.get("status") != "archived"] |
| | |
| | if not active_projects: |
| | st.info("لا توجد مشاريع نشطة حالياً. يمكنك إضافة مشروع جديد من تبويب 'إضافة مشروع جديد'.") |
| | return |
| | |
| | |
| | for idx, project in enumerate(active_projects): |
| | with st.expander(f"{project.get('name')} - {project.get('client')}"): |
| | self._render_project_details(project, idx) |
| | |
| | def _render_project_details(self, project, idx): |
| | """عرض تفاصيل مشروع محدد""" |
| | |
| | col1, col2, col3 = st.columns(3) |
| | |
| | with col1: |
| | st.markdown(f"**اسم المشروع:** {project.get('name')}") |
| | st.markdown(f"**رقم المشروع:** {project.get('number')}") |
| | |
| | with col2: |
| | st.markdown(f"**العميل:** {project.get('client')}") |
| | st.markdown(f"**الموقع:** {project.get('location')}") |
| | |
| | with col3: |
| | st.markdown(f"**تاريخ البدء:** {project.get('start_date')}") |
| | st.markdown(f"**تاريخ التقديم:** {project.get('submission_date')}") |
| | |
| | |
| | project_tabs = st.tabs([ |
| | "معلومات المشروع", |
| | "مرئيات مدير المنطقة", |
| | "صور وفيديوهات الموقع", |
| | "مميزات ومخاطر المشروع", |
| | "استفسارات المالك", |
| | "معلومات الموقع" |
| | ]) |
| | |
| | |
| | with project_tabs[0]: |
| | |
| | st.markdown("### معلومات المشروع الأساسية") |
| | st.markdown(f"**نوع المشروع:** {project.get('type')}") |
| | st.markdown(f"**القيمة التقديرية:** {format_currency(project.get('estimated_value', 0))} ريال") |
| | st.markdown(f"**المدة المتوقعة:** {project.get('duration')} يوم") |
| | st.markdown(f"**حالة المشروع:** {project.get('status')}") |
| | |
| | |
| | st.markdown("### الجدول الزمني للمشروع") |
| | |
| | |
| | if project.get('submission_date'): |
| | try: |
| | submission_date = datetime.strptime(project.get('submission_date'), "%Y-%m-%d") |
| | today = datetime.now() |
| | days_remaining = (submission_date - today).days |
| | |
| | |
| | if days_remaining > 0: |
| | st.markdown(f"**الوقت المتبقي للتقديم:** {days_remaining} يوم") |
| | progress_pct = min(1.0, max(0.0, days_remaining / 30.0)) |
| | st.progress(progress_pct) |
| | else: |
| | st.error(f"**انتهت مدة التقديم منذ:** {abs(days_remaining)} يوم") |
| | except: |
| | st.warning("تعذر حساب الأيام المتبقية. يرجى التأكد من صحة التاريخ.") |
| | |
| | |
| | col1, col2 = st.columns(2) |
| | with col1: |
| | if styled_button("تحديث حالة المشروع", key=f"update_project_{idx}", type="primary", icon="🔄"): |
| | st.session_state.project_to_update = idx |
| | |
| | with col2: |
| | if styled_button("تصدير معلومات المشروع", key=f"export_project_{idx}", type="success", icon="📤"): |
| | st.session_state.project_to_export = idx |
| | |
| | |
| | with project_tabs[1]: |
| | st.markdown("### مرئيات مدير المنطقة") |
| | |
| | |
| | manager_insights = project.get('manager_insights', []) |
| | |
| | if manager_insights: |
| | for i, insight in enumerate(manager_insights): |
| | with st.expander(f"مرئية #{i+1} - {insight.get('date')}"): |
| | st.markdown(f"**العنوان:** {insight.get('title')}") |
| | st.markdown(f"**التاريخ:** {insight.get('date')}") |
| | st.markdown(f"**المحتوى:**\n{insight.get('content')}") |
| | |
| | |
| | attachments = insight.get('attachments', []) |
| | if attachments: |
| | st.markdown("**المرفقات:**") |
| | for att in attachments: |
| | st.markdown(f"- {att}") |
| | else: |
| | st.info("لا توجد مرئيات مضافة لمدير المنطقة.") |
| | |
| | |
| | st.markdown("### إضافة مرئية جديدة") |
| | |
| | insight_title = st.text_input("عنوان المرئية", key=f"new_insight_title_{idx}") |
| | insight_content = st.text_area("محتوى المرئية", key=f"new_insight_content_{idx}") |
| | insight_file = st.file_uploader("إرفاق ملف (اختياري)", key=f"new_insight_file_{idx}") |
| | |
| | if styled_button("إضافة مرئية", key=f"add_insight_{idx}", type="primary", icon="➕"): |
| | if not insight_title or not insight_content: |
| | st.error("يرجى تعبئة عنوان ومحتوى المرئية.") |
| | else: |
| | |
| | new_insight = { |
| | "title": insight_title, |
| | "date": datetime.now().strftime("%Y-%m-%d"), |
| | "content": insight_content, |
| | "attachments": [] |
| | } |
| | |
| | |
| | if insight_file: |
| | file_path = self._save_project_file(project.get('number'), "insights", insight_file) |
| | if file_path: |
| | new_insight["attachments"].append(file_path) |
| | |
| | |
| | if 'manager_insights' not in project: |
| | project['manager_insights'] = [] |
| | |
| | project['manager_insights'].append(new_insight) |
| | st.success("تمت إضافة المرئية بنجاح!") |
| | st.rerun() |
| | |
| | |
| | with project_tabs[2]: |
| | st.markdown("### صور وفيديوهات الموقع") |
| | |
| | |
| | site_media = project.get('site_media', []) |
| | |
| | if site_media: |
| | media_tabs = st.tabs(["الصور", "الفيديوهات", "مرفقات أخرى"]) |
| | |
| | |
| | with media_tabs[0]: |
| | images = [m for m in site_media if m.get('type') == 'image'] |
| | if images: |
| | for img in images: |
| | st.markdown(f"**{img.get('title')}** - {img.get('date')}") |
| | if 'file_path' in img: |
| | try: |
| | |
| | st.markdown(f"*مسار الملف:* {img.get('file_path')}") |
| | except: |
| | st.warning("تعذر عرض الصورة.") |
| | st.markdown(f"*الوصف:* {img.get('description', '')}") |
| | st.markdown("---") |
| | else: |
| | st.info("لا توجد صور مضافة للموقع.") |
| | |
| | |
| | with media_tabs[1]: |
| | videos = [m for m in site_media if m.get('type') == 'video'] |
| | if videos: |
| | for vid in videos: |
| | st.markdown(f"**{vid.get('title')}** - {vid.get('date')}") |
| | if 'file_path' in vid: |
| | st.markdown(f"*مسار الملف:* {vid.get('file_path')}") |
| | st.markdown(f"*الوصف:* {vid.get('description', '')}") |
| | st.markdown("---") |
| | else: |
| | st.info("لا توجد فيديوهات مضافة للموقع.") |
| | |
| | |
| | with media_tabs[2]: |
| | other_files = [m for m in site_media if m.get('type') not in ['image', 'video']] |
| | if other_files: |
| | for f in other_files: |
| | st.markdown(f"**{f.get('title')}** - {f.get('date')}") |
| | if 'file_path' in f: |
| | st.markdown(f"*مسار الملف:* {f.get('file_path')}") |
| | st.markdown(f"*الوصف:* {f.get('description', '')}") |
| | st.markdown("---") |
| | else: |
| | st.info("لا توجد مرفقات أخرى للموقع.") |
| | else: |
| | st.info("لا توجد صور أو فيديوهات مضافة للموقع.") |
| | |
| | |
| | st.markdown("### إضافة صور أو فيديوهات جديدة") |
| | |
| | media_type = st.selectbox( |
| | "نوع الملف", |
| | options=["صورة", "فيديو", "ملف آخر"], |
| | key=f"new_media_type_{idx}" |
| | ) |
| | |
| | media_title = st.text_input("عنوان الملف", key=f"new_media_title_{idx}") |
| | media_desc = st.text_area("وصف الملف", key=f"new_media_desc_{idx}") |
| | media_file = st.file_uploader( |
| | "اختر الملف", |
| | type=["jpg", "jpeg", "png", "mp4", "avi", "mov", "pdf", "docx"] if media_type == "ملف آخر" else |
| | ["mp4", "avi", "mov"] if media_type == "فيديو" else |
| | ["jpg", "jpeg", "png"], |
| | key=f"new_media_file_{idx}" |
| | ) |
| | |
| | if styled_button("إضافة للموقع", key=f"add_media_{idx}", type="primary", icon="➕"): |
| | if not media_title or not media_file: |
| | st.error("يرجى تعبئة عنوان الملف واختيار الملف.") |
| | else: |
| | |
| | file_type = "images" if media_type == "صورة" else "videos" if media_type == "فيديو" else "others" |
| | |
| | |
| | file_path = self._save_project_file(project.get('number'), file_type, media_file) |
| | |
| | if file_path: |
| | |
| | new_media = { |
| | "title": media_title, |
| | "date": datetime.now().strftime("%Y-%m-%d"), |
| | "description": media_desc, |
| | "type": "image" if media_type == "صورة" else "video" if media_type == "فيديو" else "other", |
| | "file_path": file_path |
| | } |
| | |
| | |
| | if 'site_media' not in project: |
| | project['site_media'] = [] |
| | |
| | project['site_media'].append(new_media) |
| | st.success(f"تمت إضافة {media_type} بنجاح!") |
| | st.rerun() |
| | |
| | |
| | with project_tabs[3]: |
| | st.markdown("### مميزات ومخاطر المشروع") |
| | |
| | |
| | advantage_risk_tabs = st.tabs(["مميزات المشروع", "مخاطر المشروع"]) |
| | |
| | |
| | with advantage_risk_tabs[0]: |
| | advantages = project.get('advantages', []) |
| | |
| | if advantages: |
| | for i, adv in enumerate(advantages): |
| | st.markdown(f"**{i+1}. {adv.get('title')}**") |
| | st.markdown(f"*التأثير:* {adv.get('impact')}") |
| | st.markdown(f"{adv.get('description')}") |
| | st.markdown("---") |
| | else: |
| | st.info("لم يتم إضافة مميزات للمشروع.") |
| | |
| | |
| | st.markdown("### إضافة ميزة جديدة") |
| | adv_title = st.text_input("عنوان الميزة", key=f"new_adv_title_{idx}") |
| | adv_impact = st.selectbox( |
| | "مستوى التأثير", |
| | options=["منخفض", "متوسط", "عالي"], |
| | key=f"new_adv_impact_{idx}" |
| | ) |
| | adv_desc = st.text_area("وصف الميزة", key=f"new_adv_desc_{idx}") |
| | |
| | if styled_button("إضافة ميزة", key=f"add_adv_{idx}", type="success", icon="✨"): |
| | if not adv_title or not adv_desc: |
| | st.error("يرجى تعبئة عنوان ووصف الميزة.") |
| | else: |
| | |
| | new_adv = { |
| | "title": adv_title, |
| | "impact": adv_impact, |
| | "description": adv_desc, |
| | "date_added": datetime.now().strftime("%Y-%m-%d") |
| | } |
| | |
| | |
| | if 'advantages' not in project: |
| | project['advantages'] = [] |
| | |
| | project['advantages'].append(new_adv) |
| | st.success("تمت إضافة الميزة بنجاح!") |
| | st.rerun() |
| | |
| | |
| | with advantage_risk_tabs[1]: |
| | risks = project.get('risks', []) |
| | |
| | if risks: |
| | for i, risk in enumerate(risks): |
| | risk_color = "🔴" if risk.get('severity') == "عالي" else "🟠" if risk.get('severity') == "متوسط" else "🟡" |
| | st.markdown(f"{risk_color} **{i+1}. {risk.get('title')}**") |
| | st.markdown(f"*الحدة:* {risk.get('severity')} | *الاحتمالية:* {risk.get('probability')}%") |
| | st.markdown(f"*الوصف:* {risk.get('description')}") |
| | st.markdown(f"*الإجراءات المقترحة:* {risk.get('mitigation_plan')}") |
| | st.markdown("---") |
| | else: |
| | st.info("لم يتم إضافة مخاطر للمشروع.") |
| | |
| | |
| | st.markdown("### إضافة مخاطر جديدة") |
| | risk_title = st.text_input("عنوان المخاطرة", key=f"new_risk_title_{idx}") |
| | risk_severity = st.selectbox( |
| | "حدة المخاطرة", |
| | options=["منخفض", "متوسط", "عالي"], |
| | key=f"new_risk_severity_{idx}" |
| | ) |
| | risk_probability = st.slider( |
| | "احتمالية الحدوث (%)", |
| | min_value=0, |
| | max_value=100, |
| | value=50, |
| | key=f"new_risk_prob_{idx}" |
| | ) |
| | risk_desc = st.text_area("وصف المخاطرة", key=f"new_risk_desc_{idx}") |
| | risk_mitigation = st.text_area("خطة التخفيف المقترحة", key=f"new_risk_mitigation_{idx}") |
| | |
| | if styled_button("إضافة مخاطرة", key=f"add_risk_{idx}", type="warning", icon="⚠️"): |
| | if not risk_title or not risk_desc: |
| | st.error("يرجى تعبئة عنوان ووصف المخاطرة.") |
| | else: |
| | |
| | new_risk = { |
| | "title": risk_title, |
| | "severity": risk_severity, |
| | "probability": risk_probability, |
| | "description": risk_desc, |
| | "mitigation_plan": risk_mitigation, |
| | "date_added": datetime.now().strftime("%Y-%m-%d") |
| | } |
| | |
| | |
| | if 'risks' not in project: |
| | project['risks'] = [] |
| | |
| | project['risks'].append(new_risk) |
| | st.success("تمت إضافة المخاطرة بنجاح!") |
| | st.rerun() |
| | |
| | |
| | with project_tabs[4]: |
| | st.markdown("### استفسارات المالك") |
| | |
| | |
| | inquiries = project.get('inquiries', []) |
| | |
| | if inquiries: |
| | for i, inq in enumerate(inquiries): |
| | with st.expander(f"استفسار #{i+1} - {inq.get('date')}"): |
| | st.markdown(f"**السؤال:** {inq.get('question')}") |
| | |
| | if inq.get('answer'): |
| | st.markdown(f"**الإجابة:** {inq.get('answer')}") |
| | st.markdown(f"**تاريخ الإجابة:** {inq.get('answer_date', 'غير محدد')}") |
| | else: |
| | st.warning("لم تتم الإجابة على هذا الاستفسار بعد.") |
| | |
| | |
| | answer_text = st.text_area("إجابة الاستفسار", key=f"answer_{idx}_{i}") |
| | |
| | if styled_button("إرسال الإجابة", key=f"send_answer_{idx}_{i}", type="primary", icon="✉️"): |
| | if not answer_text: |
| | st.error("يرجى كتابة الإجابة.") |
| | else: |
| | |
| | inq['answer'] = answer_text |
| | inq['answer_date'] = datetime.now().strftime("%Y-%m-%d") |
| | st.success("تم إرسال الإجابة بنجاح!") |
| | st.rerun() |
| | else: |
| | st.info("لا توجد استفسارات من المالك لهذا المشروع.") |
| | |
| | |
| | st.markdown("### إضافة استفسار جديد") |
| | |
| | inquiry_question = st.text_area("سؤال الاستفسار", key=f"new_inquiry_{idx}") |
| | |
| | if styled_button("إضافة استفسار", key=f"add_inquiry_{idx}", type="primary", icon="❓"): |
| | if not inquiry_question: |
| | st.error("يرجى كتابة السؤال.") |
| | else: |
| | |
| | new_inquiry = { |
| | "question": inquiry_question, |
| | "date": datetime.now().strftime("%Y-%m-%d"), |
| | "answer": None, |
| | "answer_date": None |
| | } |
| | |
| | |
| | if 'inquiries' not in project: |
| | project['inquiries'] = [] |
| | |
| | project['inquiries'].append(new_inquiry) |
| | st.success("تمت إضافة الاستفسار بنجاح!") |
| | st.rerun() |
| | |
| | |
| | with project_tabs[5]: |
| | st.markdown("### معلومات الموقع") |
| | |
| | |
| | site_info = project.get('site_info', {}) |
| | |
| | if site_info: |
| | st.markdown("#### معلومات أساسية") |
| | st.markdown(f"**الطبيعة الجغرافية:** {site_info.get('geography', 'غير محدد')}") |
| | st.markdown(f"**إمكانية الوصول:** {site_info.get('accessibility', 'غير محدد')}") |
| | st.markdown(f"**المسافة عن أقرب مدينة:** {site_info.get('distance_to_city', 'غير محدد')}") |
| | |
| | st.markdown("#### بيانات التضاريس") |
| | st.markdown(f"**نوع التربة:** {site_info.get('soil_type', 'غير محدد')}") |
| | st.markdown(f"**متوسط درجة الحرارة:** {site_info.get('avg_temperature', 'غير محدد')}") |
| | st.markdown(f"**موسم الأمطار:** {site_info.get('rainy_season', 'غير محدد')}") |
| | |
| | st.markdown("#### الخدمات المتوفرة") |
| | st.markdown(f"**مياه:** {'متوفر' if site_info.get('has_water', False) else 'غير متوفر'}") |
| | st.markdown(f"**كهرباء:** {'متوفر' if site_info.get('has_electricity', False) else 'غير متوفر'}") |
| | st.markdown(f"**اتصالات:** {'متوفر' if site_info.get('has_communications', False) else 'غير متوفر'}") |
| | |
| | st.markdown("#### ملاحظات إضافية") |
| | st.markdown(f"{site_info.get('notes', '')}") |
| | |
| | |
| | if 'latitude' in site_info and 'longitude' in site_info: |
| | st.markdown("#### موقع المشروع على الخريطة") |
| | |
| | else: |
| | st.info("لم يتم إضافة معلومات الموقع بعد.") |
| | |
| | |
| | st.markdown("### تحديث معلومات الموقع") |
| | |
| | |
| | st.markdown("#### الطبيعة الجغرافية والوصول") |
| | geo_col1, geo_col2 = st.columns(2) |
| | |
| | with geo_col1: |
| | geography = st.selectbox( |
| | "الطبيعة الجغرافية", |
| | options=["صحراوية", "جبلية", "ساحلية", "زراعية", "حضرية", "أخرى"], |
| | index=0 if not site_info else ["صحراوية", "جبلية", "ساحلية", "زراعية", "حضرية", "أخرى"].index(site_info.get('geography', "صحراوية")), |
| | key=f"site_geography_{idx}" |
| | ) |
| | |
| | accessibility = st.selectbox( |
| | "إمكانية الوصول", |
| | options=["سهلة", "متوسطة", "صعبة"], |
| | index=0 if not site_info else ["سهلة", "متوسطة", "صعبة"].index(site_info.get('accessibility', "سهلة")), |
| | key=f"site_accessibility_{idx}" |
| | ) |
| | |
| | with geo_col2: |
| | distance_to_city = st.text_input( |
| | "المسافة عن أقرب مدينة (كم)", |
| | value=site_info.get('distance_to_city', ""), |
| | key=f"site_distance_{idx}" |
| | ) |
| | |
| | nearest_city = st.text_input( |
| | "أقرب مدينة رئيسية", |
| | value=site_info.get('nearest_city', ""), |
| | key=f"site_nearest_city_{idx}" |
| | ) |
| | |
| | |
| | st.markdown("#### التضاريس والمناخ") |
| | terrain_col1, terrain_col2 = st.columns(2) |
| | |
| | with terrain_col1: |
| | soil_type = st.selectbox( |
| | "نوع التربة", |
| | options=["رملية", "صخرية", "طينية", "مختلطة", "أخرى"], |
| | index=0 if not site_info else ["رملية", "صخرية", "طينية", "مختلطة", "أخرى"].index(site_info.get('soil_type', "رملية")), |
| | key=f"site_soil_{idx}" |
| | ) |
| | |
| | avg_temperature = st.text_input( |
| | "متوسط درجة الحرارة", |
| | value=site_info.get('avg_temperature', ""), |
| | key=f"site_temp_{idx}" |
| | ) |
| | |
| | with terrain_col2: |
| | rainy_season = st.text_input( |
| | "موسم الأمطار", |
| | value=site_info.get('rainy_season', ""), |
| | key=f"site_rainy_{idx}" |
| | ) |
| | |
| | wind_info = st.text_input( |
| | "معلومات الرياح", |
| | value=site_info.get('wind_info', ""), |
| | key=f"site_wind_{idx}" |
| | ) |
| | |
| | |
| | st.markdown("#### الخدمات المتوفرة") |
| | services_col1, services_col2, services_col3 = st.columns(3) |
| | |
| | with services_col1: |
| | has_water = st.checkbox( |
| | "مياه", |
| | value=site_info.get('has_water', False), |
| | key=f"site_water_{idx}" |
| | ) |
| | |
| | with services_col2: |
| | has_electricity = st.checkbox( |
| | "كهرباء", |
| | value=site_info.get('has_electricity', False), |
| | key=f"site_electricity_{idx}" |
| | ) |
| | |
| | with services_col3: |
| | has_communications = st.checkbox( |
| | "اتصالات", |
| | value=site_info.get('has_communications', False), |
| | key=f"site_communications_{idx}" |
| | ) |
| | |
| | |
| | site_notes = st.text_area( |
| | "ملاحظات إضافية عن الموقع", |
| | value=site_info.get('notes', ""), |
| | key=f"site_notes_{idx}" |
| | ) |
| | |
| | |
| | if styled_button("حفظ معلومات الموقع", key=f"save_site_info_{idx}", type="primary", icon="💾"): |
| | |
| | updated_site_info = { |
| | "geography": geography, |
| | "accessibility": accessibility, |
| | "distance_to_city": distance_to_city, |
| | "nearest_city": nearest_city, |
| | "soil_type": soil_type, |
| | "avg_temperature": avg_temperature, |
| | "rainy_season": rainy_season, |
| | "wind_info": wind_info, |
| | "has_water": has_water, |
| | "has_electricity": has_electricity, |
| | "has_communications": has_communications, |
| | "notes": site_notes |
| | } |
| | |
| | |
| | project['site_info'] = updated_site_info |
| | st.success("تم حفظ معلومات الموقع بنجاح!") |
| | |
| | def _render_new_project_form(self): |
| | """عرض نموذج إضافة مشروع جديد""" |
| | st.markdown(""" |
| | <div class='custom-box info-box'> |
| | <h3>➕ إضافة مشروع جديد</h3> |
| | <p>قم بإدخال معلومات المشروع الجديد بالتفصيل.</p> |
| | </div> |
| | """, unsafe_allow_html=True) |
| | |
| | |
| | st.markdown("### معلومات المشروع الأساسية") |
| | |
| | |
| | col1, col2 = st.columns(2) |
| | |
| | with col1: |
| | project_name = st.text_input("اسم المشروع", key="new_project_name") |
| | project_number = st.text_input("رقم المشروع", key="new_project_number") |
| | project_client = st.text_input("العميل", key="new_project_client") |
| | |
| | with col2: |
| | project_location = st.text_input("موقع المشروع", key="new_project_location") |
| | project_type = st.selectbox( |
| | "نوع المشروع", |
| | options=["بنية تحتية", "مباني", "طرق", "جسور", "شبكات مياه", "شبكات كهرباء", "أخرى"], |
| | key="new_project_type" |
| | ) |
| | project_estimated_value = st.number_input( |
| | "القيمة التقديرية (ريال)", |
| | min_value=0.0, |
| | step=100000.0, |
| | format="%.2f", |
| | key="new_project_value" |
| | ) |
| | |
| | |
| | st.markdown("### الجدول الزمني") |
| | |
| | date_col1, date_col2 = st.columns(2) |
| | |
| | with date_col1: |
| | project_start_date = st.date_input( |
| | "تاريخ البدء", |
| | value=datetime.now(), |
| | key="new_project_start_date" |
| | ) |
| | |
| | project_submission_date = st.date_input( |
| | "تاريخ التقديم", |
| | value=datetime.now() + timedelta(days=30), |
| | key="new_project_submission_date" |
| | ) |
| | |
| | with date_col2: |
| | project_duration = st.number_input( |
| | "مدة المشروع (يوم)", |
| | min_value=1, |
| | value=180, |
| | step=1, |
| | key="new_project_duration" |
| | ) |
| | |
| | project_status = st.selectbox( |
| | "حالة المشروع", |
| | options=["جديد", "قيد الدراسة", "تم التقديم", "تم الترسية", "قيد التنفيذ", "مكتمل", "ملغي"], |
| | key="new_project_status" |
| | ) |
| | |
| | |
| | if styled_button("إضافة المشروع", key="add_new_project", type="success", icon="✅"): |
| | |
| | if not project_name or not project_number or not project_client or not project_location: |
| | st.error("يرجى تعبئة جميع الحقول الأساسية (اسم المشروع، رقم المشروع، العميل، الموقع).") |
| | else: |
| | |
| | new_project = { |
| | "name": project_name, |
| | "number": project_number, |
| | "client": project_client, |
| | "location": project_location, |
| | "type": project_type, |
| | "estimated_value": project_estimated_value, |
| | "start_date": project_start_date.strftime("%Y-%m-%d"), |
| | "submission_date": project_submission_date.strftime("%Y-%m-%d"), |
| | "duration": project_duration, |
| | "status": project_status, |
| | "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), |
| | "site_info": {}, |
| | "manager_insights": [], |
| | "site_media": [], |
| | "advantages": [], |
| | "risks": [], |
| | "inquiries": [] |
| | } |
| | |
| | |
| | st.session_state.projects.append(new_project) |
| | |
| | |
| | project_dir = os.path.join(self.projects_dir, project_number) |
| | os.makedirs(project_dir, exist_ok=True) |
| | |
| | |
| | os.makedirs(os.path.join(project_dir, "insights"), exist_ok=True) |
| | os.makedirs(os.path.join(project_dir, "images"), exist_ok=True) |
| | os.makedirs(os.path.join(project_dir, "videos"), exist_ok=True) |
| | os.makedirs(os.path.join(project_dir, "others"), exist_ok=True) |
| | |
| | st.success(f"تمت إضافة المشروع '{project_name}' بنجاح!") |
| | st.rerun() |
| | |
| | def _render_archived_projects(self): |
| | """عرض قائمة المشاريع المؤرشفة""" |
| | st.markdown(""" |
| | <div class='custom-box info-box'> |
| | <h3>📂 أرشيف المشاريع</h3> |
| | <p>قائمة المشاريع المكتملة أو المؤرشفة.</p> |
| | </div> |
| | """, unsafe_allow_html=True) |
| | |
| | |
| | archived_projects = [p for p in st.session_state.projects if p.get("status") == "archived" or p.get("status") == "مكتمل"] |
| | |
| | if not archived_projects: |
| | st.info("لا توجد مشاريع مؤرشفة حالياً.") |
| | return |
| | |
| | |
| | for idx, project in enumerate(archived_projects): |
| | with st.expander(f"{project.get('name')} - {project.get('client')}"): |
| | self._render_project_details(project, idx + 1000) |
| | |
| | def _render_projects_reports(self): |
| | """عرض تقارير المشاريع والإحصائيات""" |
| | st.markdown(""" |
| | <div class='custom-box info-box'> |
| | <h3>📊 تقارير المشاريع</h3> |
| | <p>عرض إحصائيات وتقارير عن المشاريع الحالية والسابقة.</p> |
| | </div> |
| | """, unsafe_allow_html=True) |
| | |
| | |
| | if not st.session_state.projects: |
| | st.info("لا توجد مشاريع لعرض التقارير.") |
| | return |
| | |
| | |
| | st.markdown("### إحصائيات عامة") |
| | |
| | |
| | total_projects = len(st.session_state.projects) |
| | active_projects = len([p for p in st.session_state.projects if p.get("status") not in ["archived", "مكتمل", "ملغي"]]) |
| | completed_projects = len([p for p in st.session_state.projects if p.get("status") in ["مكتمل"]]) |
| | canceled_projects = len([p for p in st.session_state.projects if p.get("status") in ["ملغي"]]) |
| | |
| | |
| | col1, col2, col3, col4 = st.columns(4) |
| | |
| | with col1: |
| | st.metric("إجمالي المشاريع", total_projects) |
| | |
| | with col2: |
| | st.metric("المشاريع النشطة", active_projects) |
| | |
| | with col3: |
| | st.metric("المشاريع المكتملة", completed_projects) |
| | |
| | with col4: |
| | st.metric("المشاريع الملغاة", canceled_projects) |
| | |
| | |
| | st.markdown("### توزيع المشاريع حسب النوع") |
| | |
| | |
| | project_types = {} |
| | for project in st.session_state.projects: |
| | project_type = project.get("type", "غير محدد") |
| | if project_type in project_types: |
| | project_types[project_type] += 1 |
| | else: |
| | project_types[project_type] = 1 |
| | |
| | |
| | types_df = pd.DataFrame({ |
| | "نوع المشروع": list(project_types.keys()), |
| | "عدد المشاريع": list(project_types.values()) |
| | }) |
| | |
| | |
| | fig = px.pie( |
| | types_df, |
| | values="عدد المشاريع", |
| | names="نوع المشروع", |
| | title="توزيع المشاريع حسب النوع" |
| | ) |
| | st.plotly_chart(fig, use_container_width=True) |
| | |
| | |
| | st.markdown("### توزيع المشاريع حسب الحالة") |
| | |
| | |
| | project_statuses = {} |
| | for project in st.session_state.projects: |
| | status = project.get("status", "غير محدد") |
| | if status in project_statuses: |
| | project_statuses[status] += 1 |
| | else: |
| | project_statuses[status] = 1 |
| | |
| | |
| | statuses_df = pd.DataFrame({ |
| | "حالة المشروع": list(project_statuses.keys()), |
| | "عدد المشاريع": list(project_statuses.values()) |
| | }) |
| | |
| | |
| | fig2 = px.bar( |
| | statuses_df, |
| | x="حالة المشروع", |
| | y="عدد المشاريع", |
| | title="توزيع المشاريع حسب الحالة", |
| | color="حالة المشروع" |
| | ) |
| | st.plotly_chart(fig2, use_container_width=True) |
| | |
| | |
| | st.markdown("### أهم المخاطر في المشاريع الحالية") |
| | |
| | |
| | all_risks = [] |
| | for project in st.session_state.projects: |
| | if project.get("status") not in ["archived", "مكتمل", "ملغي"]: |
| | for risk in project.get("risks", []): |
| | all_risks.append({ |
| | "مشروع": project.get("name"), |
| | "مخاطرة": risk.get("title"), |
| | "الحدة": risk.get("severity"), |
| | "الاحتمالية": risk.get("probability", 0) |
| | }) |
| | |
| | if all_risks: |
| | |
| | risks_df = pd.DataFrame(all_risks) |
| | |
| | |
| | risks_df = risks_df.sort_values(by=["الحدة", "الاحتمالية"], ascending=[False, False]) |
| | |
| | |
| | def color_severity(val): |
| | if val == "عالي": |
| | return 'background-color: #FFCCCC' |
| | elif val == "متوسط": |
| | return 'background-color: #FFFFCC' |
| | else: |
| | return 'background-color: #CCFFCC' |
| | |
| | |
| | st.dataframe(risks_df.style.applymap(color_severity, subset=["الحدة"]), use_container_width=True) |
| | else: |
| | st.info("لا توجد مخاطر مسجلة في المشاريع النشطة.") |
| | |
| | def _save_project_file(self, project_number, file_type, uploaded_file): |
| | """حفظ ملف مرفق للمشروع وإرجاع المسار""" |
| | try: |
| | |
| | project_dir = os.path.join(self.projects_dir, project_number) |
| | type_dir = os.path.join(project_dir, file_type) |
| | |
| | os.makedirs(type_dir, exist_ok=True) |
| | |
| | |
| | file_name = f"{int(time.time())}_{uploaded_file.name}" |
| | file_path = os.path.join(type_dir, file_name) |
| | |
| | |
| | with open(file_path, "wb") as f: |
| | f.write(uploaded_file.getbuffer()) |
| | |
| | return file_path |
| | except Exception as e: |
| | st.error(f"خطأ في حفظ الملف: {str(e)}") |
| | return None |
| |
|
| |
|
| | |
| | def main(): |
| | """تشغيل نموذج إدارة المشاريع بشكل مستقل""" |
| | |
| | st.set_page_config( |
| | page_title="إدارة المشاريع | WAHBi AI", |
| | page_icon="🏗️", |
| | layout="wide", |
| | initial_sidebar_state="expanded", |
| | menu_items={ |
| | 'Get Help': 'mailto:support@wahbi-ai.com', |
| | 'Report a bug': 'mailto:support@wahbi-ai.com', |
| | 'About': 'نظام إدارة المشاريع - جزء من نظام WAHBi AI لتحليل المناقصات' |
| | } |
| | ) |
| | |
| | |
| | projects_management = ProjectsManagement() |
| | |
| | |
| | projects_management.render() |
| |
|
| | |
| | if __name__ == "__main__": |
| | main() |