|
|
|
|
|
|
|
""" |
|
وحدة المساعدات العامة |
|
توفر هذه الوحدة مجموعة من الدوال المساعدة المستخدمة في مختلف أجزاء النظام |
|
""" |
|
|
|
import os |
|
import datetime |
|
import json |
|
import re |
|
import streamlit as st |
|
|
|
def create_directory_if_not_exists(directory_path): |
|
"""إنشاء مجلد إذا لم يكن موجوداً بالفعل""" |
|
if not os.path.exists(directory_path): |
|
os.makedirs(directory_path) |
|
return True |
|
return False |
|
|
|
def get_data_folder(): |
|
"""الحصول على مسار مجلد البيانات""" |
|
data_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'data')) |
|
create_directory_if_not_exists(data_folder) |
|
return data_folder |
|
|
|
def format_time(timestamp=None): |
|
"""تنسيق الوقت بصيغة قابلة للقراءة""" |
|
if timestamp is None: |
|
timestamp = datetime.datetime.now() |
|
return timestamp.strftime("%Y-%m-%d %H:%M:%S") |
|
|
|
def get_user_info(): |
|
"""الحصول على معلومات المستخدم الحالي""" |
|
|
|
|
|
return { |
|
"id": 1, |
|
"username": "admin", |
|
"name": "مدير النظام", |
|
"role": "admin" |
|
} |
|
|
|
def load_css(): |
|
"""تحميل أنماط CSS المخصصة""" |
|
st.markdown(""" |
|
<style> |
|
.module-title { |
|
color: #1E88E5; |
|
text-align: center; |
|
margin-bottom: 20px; |
|
font-weight: bold; |
|
} |
|
|
|
.section-title { |
|
color: #1565C0; |
|
margin-top: 20px; |
|
margin-bottom: 10px; |
|
font-weight: bold; |
|
} |
|
|
|
.info-box { |
|
background-color: #E3F2FD; |
|
padding: 15px; |
|
border-radius: 5px; |
|
margin-bottom: 15px; |
|
} |
|
|
|
.warning-box { |
|
background-color: #FFF8E1; |
|
padding: 15px; |
|
border-radius: 5px; |
|
margin-bottom: 15px; |
|
} |
|
|
|
.error-box { |
|
background-color: #FFEBEE; |
|
padding: 15px; |
|
border-radius: 5px; |
|
margin-bottom: 15px; |
|
} |
|
|
|
.success-box { |
|
background-color: #E8F5E9; |
|
padding: 15px; |
|
border-radius: 5px; |
|
margin-bottom: 15px; |
|
} |
|
|
|
.centered { |
|
text-align: center; |
|
} |
|
|
|
.footer { |
|
text-align: center; |
|
margin-top: 30px; |
|
color: #78909C; |
|
font-size: 0.8em; |
|
} |
|
|
|
/* تحسين تصميم الجداول */ |
|
table { |
|
width: 100%; |
|
} |
|
|
|
thead th { |
|
background-color: #1976D2 !important; |
|
color: white !important; |
|
} |
|
|
|
tbody tr:nth-child(even) { |
|
background-color: #f2f2f2; |
|
} |
|
|
|
tbody tr:hover { |
|
background-color: #E3F2FD; |
|
} |
|
|
|
/* تحسين ظهور الفورم */ |
|
input, select, textarea { |
|
border-radius: 5px !important; |
|
border: 1px solid #ddd !important; |
|
padding: 10px !important; |
|
} |
|
|
|
.stButton>button { |
|
border-radius: 5px !important; |
|
} |
|
|
|
/* زيادة حجم الخط للتوافق مع اللغة العربية */ |
|
html, body, [class*="css"] { |
|
font-family: 'Tajawal', sans-serif; |
|
} |
|
</style> |
|
|
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;700&display=swap"> |
|
""", unsafe_allow_html=True) |
|
|
|
def render_credits(): |
|
"""عرض المعلومات عن حقوق الملكية وإصدار النظام""" |
|
st.markdown(""" |
|
<div class="footer"> |
|
<p>هذا النظام يعمل لصالح شركة شبه الجزيرة للمقاولات، جميع الحقوق محفوظة 2025</p> |
|
<p>نظام WAHBi AI - الإصدار 2.0</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
def load_icons(): |
|
"""تحميل الأيقونات المستخدمة في النظام""" |
|
icons = { |
|
"project": "🏗️", |
|
"document": "📄", |
|
"analysis": "🔍", |
|
"warning": "⚠️", |
|
"success": "✅", |
|
"error": "❌", |
|
"info": "ℹ️", |
|
"settings": "⚙️", |
|
"user": "👤", |
|
"money": "💰", |
|
"time": "⏱️", |
|
"location": "📍", |
|
"notification": "🔔", |
|
"edit": "✏️", |
|
"delete": "🗑️", |
|
"upload": "📤", |
|
"download": "📥", |
|
"save": "💾", |
|
"cancel": "❌", |
|
"add": "➕", |
|
"calendar": "📅", |
|
"chat": "💬", |
|
"search": "🔎", |
|
"star": "⭐", |
|
"trophy": "🏆", |
|
"medal": "🥇", |
|
"chart": "📊", |
|
"map": "🗺️", |
|
"building": "🏢", |
|
"road": "🛣️", |
|
"bridge": "🌉", |
|
} |
|
return icons |
|
|
|
def format_number(number, decimal_places=2): |
|
"""تنسيق الأرقام بطريقة أنيقة""" |
|
if isinstance(number, (int, float)): |
|
if decimal_places == 0: |
|
return "{:,.0f}".format(number) |
|
else: |
|
return "{:,.{dp}f}".format(number, dp=decimal_places) |
|
return str(number) |
|
|
|
def format_currency(amount, currency="ريال", decimal_places=2): |
|
"""تنسيق المبالغ المالية""" |
|
if amount is None: |
|
return "غير محدد" |
|
formatted = format_number(amount, decimal_places) |
|
return f"{formatted} {currency}" |
|
|
|
def styled_button(label, key=None, type="primary", on_click=None, args=None, full_width=False, icon=None, is_link=False, help=None): |
|
""" |
|
إنشاء زر بتنسيق معين |
|
:param label: نص الزر |
|
:param key: مفتاح الزر الفريد |
|
:param type: نوع التنسيق ('primary', 'secondary', 'success', 'warning', 'danger', 'info', 'glass', 'flat') |
|
:param on_click: الدالة التي سيتم تنفيذها عند النقر |
|
:param args: معاملات الدالة |
|
:param full_width: هل يأخذ الزر العرض كاملاً |
|
:param icon: أيقونة لعرضها قبل النص (emoji أو HTML) |
|
:param is_link: إذا كان الزر رابطاً بدلاً من زر عادي |
|
:param help: نص المساعدة للزر |
|
:return: زر مُنسّق |
|
""" |
|
if is_link: |
|
btn_class = f"{type}-btn" |
|
if icon: |
|
btn_class += " action-btn" |
|
label_with_icon = f"{icon} {label}" |
|
else: |
|
label_with_icon = label |
|
|
|
button_html = f""" |
|
<div class="{btn_class}"> |
|
{label_with_icon} |
|
</div> |
|
""" |
|
return st.markdown(button_html, unsafe_allow_html=True) |
|
else: |
|
with st.container(): |
|
btn_class = f"{type}-btn" |
|
if icon: |
|
btn_class += " action-btn" |
|
label_with_icon = f"{icon} {label}" |
|
else: |
|
label_with_icon = label |
|
|
|
st.markdown(f'<div class="{btn_class}">', unsafe_allow_html=True) |
|
clicked = st.button(label_with_icon, key=key, on_click=on_click, args=args, use_container_width=full_width, help=help) |
|
st.markdown('</div>', unsafe_allow_html=True) |
|
return clicked |
|
|
|
def filter_dataframe(df, column, value): |
|
"""ترشيح إطار البيانات""" |
|
if value == "الكل": |
|
return df |
|
return df[df[column] == value] |
|
|
|
def get_file_extension(filename): |
|
"""استخراج امتداد الملف""" |
|
if not filename: |
|
return "" |
|
return os.path.splitext(filename)[-1].lower() |
|
|
|
def extract_numbers_from_text(text): |
|
"""استخراج الأرقام من النص |
|
|
|
Args: |
|
text (str): النص المراد استخراج الأرقام منه |
|
|
|
Returns: |
|
list: قائمة بالأرقام المستخرجة |
|
""" |
|
if not text: |
|
return [] |
|
|
|
|
|
pattern = r'[-+]?\d*\.\d+|\d+' |
|
|
|
|
|
numbers = re.findall(pattern, text) |
|
|
|
|
|
converted_numbers = [] |
|
for num in numbers: |
|
if '.' in num: |
|
converted_numbers.append(float(num)) |
|
else: |
|
converted_numbers.append(int(num)) |
|
|
|
return converted_numbers |