|
|
|
|
|
|
|
""" |
|
نموذج إدارة المشاريع لنظام 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:[email protected]', |
|
'Report a bug': 'mailto:[email protected]', |
|
'About': 'نظام إدارة المشاريع - جزء من نظام WAHBi AI لتحليل المناقصات' |
|
} |
|
) |
|
|
|
|
|
projects_management = ProjectsManagement() |
|
|
|
|
|
projects_management.render() |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |