"""
وحدة الإشعارات الذكية - نظام تحليل المناقصات
"""
import streamlit as st
import pandas as pd
import datetime
import json
import os
import sys
from pathlib import Path
# إضافة مسار المشروع للنظام
sys.path.append(str(Path(__file__).parent.parent))
# استيراد محسن واجهة المستخدم
from styling.enhanced_ui import UIEnhancer
class NotificationsApp:
"""تطبيق الإشعارات الذكية"""
def __init__(self):
"""تهيئة تطبيق الإشعارات الذكية"""
self.ui = UIEnhancer(page_title="الإشعارات الذكية - نظام تحليل المناقصات", page_icon="🔔")
# تهيئة متغير السمة في حالة الجلسة إذا لم يكن موجوداً
if 'theme' not in st.session_state:
st.session_state.theme = 'light'
self.ui.apply_theme_colors()
# بيانات الإشعارات (نموذجية)
self.notifications_data = [
{
"id": "N001",
"title": "موعد تسليم مناقصة",
"message": "موعد تسليم مناقصة T-2025-001 (إنشاء مبنى إداري) بعد 5 أيام",
"type": "deadline",
"priority": "high",
"related_entity": "T-2025-001",
"created_at": "2025-03-25T10:30:00",
"is_read": False
},
{
"id": "N002",
"title": "ترسية مناقصة",
"message": "تم ترسية مناقصة T-2025-003 (توريد معدات) بنجاح",
"type": "award",
"priority": "medium",
"related_entity": "T-2025-003",
"created_at": "2025-03-28T14:15:00",
"is_read": True
},
{
"id": "N003",
"title": "تحديث مستندات",
"message": "تم تحديث مستندات مناقصة T-2025-002 (صيانة طرق)",
"type": "document",
"priority": "medium",
"related_entity": "T-2025-002",
"created_at": "2025-03-29T09:45:00",
"is_read": False
},
{
"id": "N004",
"title": "تغيير في المواصفات",
"message": "تم تغيير المواصفات الفنية لمناقصة T-2025-001 (إنشاء مبنى إداري)",
"type": "change",
"priority": "high",
"related_entity": "T-2025-001",
"created_at": "2025-03-27T11:20:00",
"is_read": False
},
{
"id": "N005",
"title": "تأخير في المشروع",
"message": "تأخير في تنفيذ مشروع P002 (تطوير طريق الملك فهد - جدة)",
"type": "delay",
"priority": "high",
"related_entity": "P002",
"created_at": "2025-03-26T16:10:00",
"is_read": True
},
{
"id": "N006",
"title": "اكتمال مرحلة",
"message": "اكتمال مرحلة الأساسات في مشروع P001 (إنشاء مبنى إداري - الرياض)",
"type": "milestone",
"priority": "low",
"related_entity": "P001",
"created_at": "2025-03-24T13:30:00",
"is_read": True
},
{
"id": "N007",
"title": "طلب معلومات إضافية",
"message": "طلب معلومات إضافية لمناقصة T-2025-004 (تجهيز مختبرات)",
"type": "request",
"priority": "medium",
"related_entity": "T-2025-004",
"created_at": "2025-03-30T08:15:00",
"is_read": False
},
{
"id": "N008",
"title": "تحديث أسعار المواد",
"message": "تم تحديث أسعار مواد البناء في قاعدة البيانات",
"type": "update",
"priority": "low",
"related_entity": "DB-MATERIALS",
"created_at": "2025-03-29T15:40:00",
"is_read": False
},
{
"id": "N009",
"title": "اجتماع فريق العمل",
"message": "اجتماع فريق العمل لمناقشة مناقصة T-2025-001 غداً الساعة 10:00 صباحاً",
"type": "meeting",
"priority": "medium",
"related_entity": "T-2025-001",
"created_at": "2025-03-28T16:20:00",
"is_read": True
},
{
"id": "N010",
"title": "تغيير في الميزانية",
"message": "تم تغيير الميزانية المخصصة لمشروع P004 (بناء مدرسة - أبها)",
"type": "budget",
"priority": "high",
"related_entity": "P004",
"created_at": "2025-03-25T14:50:00",
"is_read": False
}
]
# إعدادات الإشعارات (نموذجية)
self.notification_settings = {
"deadline": True,
"award": True,
"document": True,
"change": True,
"delay": True,
"milestone": True,
"request": True,
"update": True,
"meeting": True,
"budget": True,
"email_notifications": True,
"sms_notifications": False,
"push_notifications": True,
"notification_frequency": "realtime"
}
def run(self):
"""تشغيل تطبيق الإشعارات الذكية"""
# إضافة زر تبديل السمة في أعلى الصفحة
col1, col2, col3 = st.columns([1, 8, 1])
with col3:
if st.button("🌓 تبديل السمة"):
# تبديل السمة
if st.session_state.theme == "light":
st.session_state.theme = "dark"
else:
st.session_state.theme = "light"
# تطبيق السمة الجديدة
self.ui.theme_mode = st.session_state.theme
self.ui.apply_theme_colors()
st.rerun()
# إنشاء ترويسة الصفحة
self.ui.create_header("الإشعارات الذكية", "إدارة ومتابعة الإشعارات والتنبيهات")
# إنشاء علامات تبويب للوظائف المختلفة
tabs = st.tabs(["الإشعارات الحالية", "إعدادات الإشعارات", "إنشاء إشعار", "سجل الإشعارات"])
# علامة تبويب الإشعارات الحالية
with tabs[0]:
self.show_current_notifications()
# علامة تبويب إعدادات الإشعارات
with tabs[1]:
self.show_notification_settings()
# علامة تبويب إنشاء إشعار
with tabs[2]:
self.create_notification()
# علامة تبويب سجل الإشعارات
with tabs[3]:
self.show_notification_history()
def show_current_notifications(self):
"""عرض الإشعارات الحالية"""
st.markdown("### الإشعارات الحالية")
# إنشاء فلاتر للإشعارات
col1, col2, col3 = st.columns(3)
with col1:
type_filter = st.multiselect(
"نوع الإشعار",
options=["الكل", "موعد نهائي", "ترسية", "مستند", "تغيير", "تأخير", "مرحلة", "طلب", "تحديث", "اجتماع", "ميزانية"],
default=["الكل"]
)
with col2:
priority_filter = st.multiselect(
"الأولوية",
options=["الكل", "عالية", "متوسطة", "منخفضة"],
default=["الكل"]
)
with col3:
read_filter = st.radio(
"الحالة",
options=["الكل", "غير مقروءة", "مقروءة"],
horizontal=True
)
# تطبيق الفلاتر
filtered_notifications = self.notifications_data
# تحويل أنواع الإشعارات من الإنجليزية إلى العربية للفلترة
type_mapping = {
"موعد نهائي": "deadline",
"ترسية": "award",
"مستند": "document",
"تغيير": "change",
"تأخير": "delay",
"مرحلة": "milestone",
"طلب": "request",
"تحديث": "update",
"اجتماع": "meeting",
"ميزانية": "budget"
}
# تحويل الأولويات من العربية إلى الإنجليزية للفلترة
priority_mapping = {
"عالية": "high",
"متوسطة": "medium",
"منخفضة": "low"
}
if "الكل" not in type_filter and type_filter:
filtered_types = [type_mapping[t] for t in type_filter if t in type_mapping]
filtered_notifications = [n for n in filtered_notifications if n["type"] in filtered_types]
if "الكل" not in priority_filter and priority_filter:
filtered_priorities = [priority_mapping[p] for p in priority_filter if p in priority_mapping]
filtered_notifications = [n for n in filtered_notifications if n["priority"] in filtered_priorities]
if read_filter == "غير مقروءة":
filtered_notifications = [n for n in filtered_notifications if not n["is_read"]]
elif read_filter == "مقروءة":
filtered_notifications = [n for n in filtered_notifications if n["is_read"]]
# عرض عدد الإشعارات غير المقروءة
unread_count = len([n for n in filtered_notifications if not n["is_read"]])
st.markdown(f"**عدد الإشعارات غير المقروءة:** {unread_count}")
# زر تحديث وتعليم الكل كمقروء
col1, col2 = st.columns([1, 1])
with col1:
if st.button("تحديث الإشعارات", use_container_width=True):
st.success("تم تحديث الإشعارات بنجاح")
with col2:
if st.button("تعليم الكل كمقروء", use_container_width=True):
st.success("تم تعليم جميع الإشعارات كمقروءة")
# عرض الإشعارات
if not filtered_notifications:
st.info("لا توجد إشعارات تطابق الفلاتر المحددة")
else:
for notification in filtered_notifications:
self.display_notification(notification)
def display_notification(self, notification):
"""عرض إشعار واحد"""
# تحديد لون الإشعار بناءً على الأولوية
if notification["priority"] == "high":
color = self.ui.COLORS['danger']
priority_text = "عالية"
elif notification["priority"] == "medium":
color = self.ui.COLORS['warning']
priority_text = "متوسطة"
else:
color = self.ui.COLORS['secondary']
priority_text = "منخفضة"
# تحويل نوع الإشعار إلى العربية
type_mapping = {
"deadline": "موعد نهائي",
"award": "ترسية",
"document": "مستند",
"change": "تغيير",
"delay": "تأخير",
"milestone": "مرحلة",
"request": "طلب",
"update": "تحديث",
"meeting": "اجتماع",
"budget": "ميزانية"
}
notification_type = type_mapping.get(notification["type"], notification["type"])
# تحويل التاريخ إلى تنسيق مناسب
created_at = datetime.datetime.fromisoformat(notification["created_at"])
formatted_date = created_at.strftime("%Y-%m-%d %H:%M")
# تحديد أيقونة الإشعار
icon_mapping = {
"deadline": "⏰",
"award": "🏆",
"document": "📄",
"change": "🔄",
"delay": "⚠️",
"milestone": "🏁",
"request": "❓",
"update": "🔄",
"meeting": "👥",
"budget": "💰"
}
icon = icon_mapping.get(notification["type"], "📌")
# إنشاء بطاقة الإشعار
st.markdown(
f"""
{icon} {notification['title']}
{notification['message']}
النوع: {notification_type}
الأولوية: {priority_text}
التاريخ: {formatted_date}
""",
unsafe_allow_html=True
)
def show_notification_settings(self):
"""عرض إعدادات الإشعارات"""
st.markdown("### إعدادات الإشعارات")
# إنشاء نموذج الإعدادات
with st.form("notification_settings_form"):
st.markdown("#### أنواع الإشعارات")
col1, col2 = st.columns(2)
with col1:
deadline = st.checkbox("المواعيد النهائية", value=self.notification_settings["deadline"])
award = st.checkbox("ترسية المناقصات", value=self.notification_settings["award"])
document = st.checkbox("تحديثات المستندات", value=self.notification_settings["document"])
change = st.checkbox("التغييرات في المواصفات", value=self.notification_settings["change"])
delay = st.checkbox("التأخيرات في المشاريع", value=self.notification_settings["delay"])
with col2:
milestone = st.checkbox("اكتمال المراحل", value=self.notification_settings["milestone"])
request = st.checkbox("طلبات المعلومات", value=self.notification_settings["request"])
update = st.checkbox("تحديثات النظام", value=self.notification_settings["update"])
meeting = st.checkbox("الاجتماعات", value=self.notification_settings["meeting"])
budget = st.checkbox("تغييرات الميزانية", value=self.notification_settings["budget"])
st.markdown("#### طرق الإشعار")
col1, col2, col3 = st.columns(3)
with col1:
email_notifications = st.checkbox("البريد الإلكتروني", value=self.notification_settings["email_notifications"])
with col2:
sms_notifications = st.checkbox("الرسائل النصية", value=self.notification_settings["sms_notifications"])
with col3:
push_notifications = st.checkbox("إشعارات الويب", value=self.notification_settings["push_notifications"])
st.markdown("#### تكرار الإشعارات")
notification_frequency = st.radio(
"تكرار الإشعارات",
options=["في الوقت الحقيقي", "مرة واحدة يومياً", "مرة واحدة أسبوعياً"],
index=0 if self.notification_settings["notification_frequency"] == "realtime" else 1 if self.notification_settings["notification_frequency"] == "daily" else 2,
horizontal=True
)
# زر حفظ الإعدادات
submit_button = st.form_submit_button("حفظ الإعدادات")
if submit_button:
# تحديث الإعدادات (في تطبيق حقيقي، سيتم حفظ الإعدادات في قاعدة البيانات)
self.notification_settings.update({
"deadline": deadline,
"award": award,
"document": document,
"change": change,
"delay": delay,
"milestone": milestone,
"request": request,
"update": update,
"meeting": meeting,
"budget": budget,
"email_notifications": email_notifications,
"sms_notifications": sms_notifications,
"push_notifications": push_notifications,
"notification_frequency": "realtime" if notification_frequency == "في الوقت الحقيقي" else "daily" if notification_frequency == "مرة واحدة يومياً" else "weekly"
})
st.success("تم حفظ الإعدادات بنجاح")
# إعدادات متقدمة
st.markdown("### إعدادات متقدمة")
with st.expander("إعدادات متقدمة"):
st.markdown("#### جدولة الإشعارات")
col1, col2 = st.columns(2)
with col1:
st.time_input("وقت الإشعارات اليومية", datetime.time(9, 0))
with col2:
st.selectbox(
"يوم الإشعارات الأسبوعية",
options=["الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت"],
index=0
)
st.markdown("#### فلترة الإشعارات")
min_priority = st.select_slider(
"الحد الأدنى للأولوية",
options=["منخفضة", "متوسطة", "عالية"],
value="منخفضة"
)
st.markdown("#### حفظ الإشعارات")
retention_period = st.slider(
"فترة الاحتفاظ بالإشعارات (بالأيام)",
min_value=7,
max_value=365,
value=90,
step=1
)
if st.button("حفظ الإعدادات المتقدمة"):
st.success("تم حفظ الإعدادات المتقدمة بنجاح")
def create_notification(self):
"""إنشاء إشعار جديد"""
st.markdown("### إنشاء إشعار جديد")
# إنشاء نموذج إشعار جديد
with st.form("new_notification_form"):
title = st.text_input("عنوان الإشعار")
message = st.text_area("نص الإشعار")
col1, col2 = st.columns(2)
with col1:
notification_type = st.selectbox(
"نوع الإشعار",
options=["موعد نهائي", "ترسية", "مستند", "تغيير", "تأخير", "مرحلة", "طلب", "تحديث", "اجتماع", "ميزانية"]
)
# تحويل نوع الإشعار إلى الإنجليزية
type_mapping = {
"موعد نهائي": "deadline",
"ترسية": "award",
"مستند": "document",
"تغيير": "change",
"تأخير": "delay",
"مرحلة": "milestone",
"طلب": "request",
"تحديث": "update",
"اجتماع": "meeting",
"ميزانية": "budget"
}
notification_type_en = type_mapping.get(notification_type, "deadline")
priority = st.selectbox(
"الأولوية",
options=["عالية", "متوسطة", "منخفضة"]
)
# تحويل الأولوية إلى الإنجليزية
priority_mapping = {
"عالية": "high",
"متوسطة": "medium",
"منخفضة": "low"
}
priority_en = priority_mapping.get(priority, "medium")
with col2:
related_entity = st.text_input("الكيان المرتبط (مثل: رقم المناقصة، رقم المشروع)")
notification_date = st.date_input(
"تاريخ الإشعار",
value=datetime.datetime.now().date()
)
notification_time = st.time_input(
"وقت الإشعار",
value=datetime.datetime.now().time()
)
# زر إنشاء الإشعار
submit_button = st.form_submit_button("إنشاء الإشعار")
if submit_button and title and message:
# إنشاء إشعار جديد (في تطبيق حقيقي، سيتم حفظ الإشعار في قاعدة البيانات)
new_id = f"N{len(self.notifications_data) + 1:03d}"
# تحويل التاريخ والوقت إلى تنسيق ISO
notification_datetime = datetime.datetime.combine(notification_date, notification_time)
notification_datetime_iso = notification_datetime.isoformat()
# إضافة الإشعار الجديد إلى قائمة الإشعارات
self.notifications_data.append({
"id": new_id,
"title": title,
"message": message,
"type": notification_type_en,
"priority": priority_en,
"related_entity": related_entity,
"created_at": notification_datetime_iso,
"is_read": False
})
st.success("تم إنشاء الإشعار بنجاح")
# عرض الإشعار الجديد
st.markdown("### الإشعار الجديد")
self.display_notification(self.notifications_data[-1])
# إنشاء إشعارات متعددة
st.markdown("### إنشاء إشعارات متعددة")
with st.expander("إنشاء إشعارات متعددة"):
st.markdown("#### تحميل ملف إشعارات")
uploaded_file = st.file_uploader("قم بتحميل ملف إشعارات (CSV, JSON)", type=["csv", "json"])
if uploaded_file is not None:
if st.button("استيراد الإشعارات"):
st.success("تم استيراد الإشعارات بنجاح")
st.markdown("#### إنشاء إشعارات من قالب")
template_type = st.selectbox(
"نوع القالب",
options=["إشعارات المواعيد النهائية", "إشعارات الاجتماعات", "إشعارات التحديثات"]
)
if st.button("إنشاء إشعارات من القالب"):
st.success("تم إنشاء الإشعارات من القالب بنجاح")
def show_notification_history(self):
"""عرض سجل الإشعارات"""
st.markdown("### سجل الإشعارات")
# إنشاء فلاتر للسجل
col1, col2 = st.columns(2)
with col1:
date_range = st.date_input(
"نطاق التاريخ",
value=(
datetime.datetime.now().date() - datetime.timedelta(days=30),
datetime.datetime.now().date()
)
)
with col2:
entity_filter = st.text_input("الكيان المرتبط")
# تحويل البيانات إلى DataFrame
notifications_df = pd.DataFrame(self.notifications_data)
# تحويل عمود التاريخ إلى نوع datetime
notifications_df["created_at"] = pd.to_datetime(notifications_df["created_at"])
# تطبيق فلتر التاريخ
if len(date_range) == 2:
start_date, end_date = date_range
start_date = pd.to_datetime(start_date)
end_date = pd.to_datetime(end_date) + pd.Timedelta(days=1) - pd.Timedelta(seconds=1)
notifications_df = notifications_df[
(notifications_df["created_at"] >= start_date) &
(notifications_df["created_at"] <= end_date)
]
# تطبيق فلتر الكيان المرتبط
if entity_filter:
notifications_df = notifications_df[
notifications_df["related_entity"].str.contains(entity_filter, case=False)
]
# تحويل أنواع الإشعارات من الإنجليزية إلى العربية
type_mapping = {
"deadline": "موعد نهائي",
"award": "ترسية",
"document": "مستند",
"change": "تغيير",
"delay": "تأخير",
"milestone": "مرحلة",
"request": "طلب",
"update": "تحديث",
"meeting": "اجتماع",
"budget": "ميزانية"
}
notifications_df["type_ar"] = notifications_df["type"].map(type_mapping)
# تحويل الأولويات من الإنجليزية إلى العربية
priority_mapping = {
"high": "عالية",
"medium": "متوسطة",
"low": "منخفضة"
}
notifications_df["priority_ar"] = notifications_df["priority"].map(priority_mapping)
# تحويل حالة القراءة إلى نص
notifications_df["is_read_text"] = notifications_df["is_read"].map({True: "مقروءة", False: "غير مقروءة"})
# تنسيق عمود التاريخ
notifications_df["created_at_formatted"] = notifications_df["created_at"].dt.strftime("%Y-%m-%d %H:%M")
# إنشاء DataFrame للعرض
display_df = notifications_df[[
"id", "title", "type_ar", "priority_ar", "related_entity",
"created_at_formatted", "is_read_text"
]].rename(columns={
"id": "الرقم",
"title": "العنوان",
"type_ar": "النوع",
"priority_ar": "الأولوية",
"related_entity": "الكيان المرتبط",
"created_at_formatted": "التاريخ",
"is_read_text": "الحالة"
})
# عرض الجدول
st.dataframe(display_df, use_container_width=True, hide_index=True)
# إحصائيات الإشعارات
st.markdown("### إحصائيات الإشعارات")
col1, col2, col3 = st.columns(3)
with col1:
total_count = len(notifications_df)
st.metric("إجمالي الإشعارات", total_count)
with col2:
read_count = len(notifications_df[notifications_df["is_read"] == True])
st.metric("الإشعارات المقروءة", read_count)
with col3:
unread_count = len(notifications_df[notifications_df["is_read"] == False])
st.metric("الإشعارات غير المقروءة", unread_count)
# رسم بياني لتوزيع الإشعارات حسب النوع
st.markdown("#### توزيع الإشعارات حسب النوع")
type_counts = notifications_df["type_ar"].value_counts().reset_index()
type_counts.columns = ["النوع", "العدد"]
st.bar_chart(type_counts, x="النوع", y="العدد")
# رسم بياني لتوزيع الإشعارات حسب الأولوية
st.markdown("#### توزيع الإشعارات حسب الأولوية")
priority_counts = notifications_df["priority_ar"].value_counts().reset_index()
priority_counts.columns = ["الأولوية", "العدد"]
st.bar_chart(priority_counts, x="الأولوية", y="العدد")
# خيارات التصدير
st.markdown("### تصدير البيانات")
col1, col2 = st.columns(2)
with col1:
if st.button("تصدير إلى CSV"):
st.success("تم تصدير البيانات إلى CSV بنجاح")
with col2:
if st.button("تصدير إلى Excel"):
st.success("تم تصدير البيانات إلى Excel بنجاح")