|
""" |
|
وحدة الإشعارات الذكية - نظام تحليل المناقصات |
|
""" |
|
|
|
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="🔔") |
|
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): |
|
"""تشغيل تطبيق الإشعارات الذكية""" |
|
|
|
menu_items = [ |
|
{"name": "لوحة المعلومات", "icon": "house"}, |
|
{"name": "المناقصات والعقود", "icon": "file-text"}, |
|
{"name": "تحليل المستندات", "icon": "file-earmark-text"}, |
|
{"name": "نظام التسعير", "icon": "calculator"}, |
|
{"name": "حاسبة تكاليف البناء", "icon": "building"}, |
|
{"name": "الموارد والتكاليف", "icon": "people"}, |
|
{"name": "تحليل المخاطر", "icon": "exclamation-triangle"}, |
|
{"name": "إدارة المشاريع", "icon": "kanban"}, |
|
{"name": "الخرائط والمواقع", "icon": "geo-alt"}, |
|
{"name": "الجدول الزمني", "icon": "calendar3"}, |
|
{"name": "الإشعارات", "icon": "bell"}, |
|
{"name": "مقارنة المستندات", "icon": "files"}, |
|
{"name": "المساعد الذكي", "icon": "robot"}, |
|
{"name": "التقارير", "icon": "bar-chart"}, |
|
{"name": "الإعدادات", "icon": "gear"} |
|
] |
|
|
|
|
|
selected = self.ui.create_sidebar(menu_items) |
|
|
|
|
|
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""" |
|
<div style="border-left: 5px solid {color}; padding: 10px; margin-bottom: 10px; background-color: {'#f8f9fa' if st.session_state.theme == 'light' else '#2b2b2b'}; border-radius: 5px; {'opacity: 0.7;' if notification['is_read'] else ''}"> |
|
<div style="display: flex; justify-content: space-between; align-items: center;"> |
|
<div> |
|
<h4 style="margin: 0;">{icon} {notification['title']}</h4> |
|
<p style="margin: 5px 0;">{notification['message']}</p> |
|
<div style="display: flex; gap: 10px; font-size: 0.8em; color: {'#6c757d' if st.session_state.theme == 'light' else '#adb5bd'};"> |
|
<span>النوع: {notification_type}</span> |
|
<span>الأولوية: {priority_text}</span> |
|
<span>التاريخ: {formatted_date}</span> |
|
</div> |
|
</div> |
|
<div> |
|
<button style="background: none; border: none; cursor: pointer; color: {'#6c757d' if st.session_state.theme == 'light' else '#adb5bd'};">✓</button> |
|
<button style="background: none; border: none; cursor: pointer; color: {'#6c757d' if st.session_state.theme == 'light' else '#adb5bd'};">🗑️</button> |
|
</div> |
|
</div> |
|
</div> |
|
""", |
|
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, "update") |
|
|
|
with col2: |
|
priority = st.selectbox( |
|
"الأولوية", |
|
options=["عالية", "متوسطة", "منخفضة"] |
|
) |
|
|
|
|
|
priority_mapping = { |
|
"عالية": "high", |
|
"متوسطة": "medium", |
|
"منخفضة": "low" |
|
} |
|
|
|
priority_en = priority_mapping.get(priority, "medium") |
|
|
|
related_entity = st.text_input("الكيان المرتبط (مثل: رقم المناقصة أو المشروع)") |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
send_email = st.checkbox("إرسال بريد إلكتروني") |
|
|
|
with col2: |
|
send_push = st.checkbox("إرسال إشعار ويب") |
|
|
|
|
|
submit_button = st.form_submit_button("إنشاء الإشعار") |
|
|
|
if submit_button: |
|
if not title or not message: |
|
st.error("يرجى ملء جميع الحقول المطلوبة") |
|
else: |
|
|
|
new_notification = { |
|
"id": f"N{len(self.notifications_data) + 1:03d}", |
|
"title": title, |
|
"message": message, |
|
"type": notification_type_en, |
|
"priority": priority_en, |
|
"related_entity": related_entity, |
|
"created_at": datetime.datetime.now().isoformat(), |
|
"is_read": False |
|
} |
|
|
|
|
|
self.notifications_data.append(new_notification) |
|
|
|
st.success("تم إنشاء الإشعار بنجاح") |
|
|
|
|
|
if send_email: |
|
st.info("تم إرسال الإشعار عبر البريد الإلكتروني") |
|
|
|
if send_push: |
|
st.info("تم إرسال إشعار الويب") |
|
|
|
def show_notification_history(self): |
|
"""عرض سجل الإشعارات""" |
|
st.markdown("### سجل الإشعارات") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
date_range = st.date_input( |
|
"نطاق التاريخ", |
|
value=( |
|
datetime.datetime.now() - datetime.timedelta(days=30), |
|
datetime.datetime.now() |
|
) |
|
) |
|
|
|
with col2: |
|
entity_filter = st.text_input("البحث حسب الكيان المرتبط") |
|
|
|
|
|
notifications_df = pd.DataFrame(self.notifications_data) |
|
|
|
|
|
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) + datetime.timedelta(days=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") |
|
|
|
|
|
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 |
|
) |
|
|
|
|
|
col1, col2 = st.columns([1, 5]) |
|
with col1: |
|
if st.button("تصدير البيانات", use_container_width=True): |
|
st.success("تم تصدير البيانات بنجاح") |
|
|
|
|
|
st.markdown("### إحصائيات الإشعارات") |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
|
|
type_counts = notifications_df["type_ar"].value_counts() |
|
st.markdown("#### الإشعارات حسب النوع") |
|
st.bar_chart(type_counts) |
|
|
|
with col2: |
|
|
|
priority_counts = notifications_df["priority_ar"].value_counts() |
|
st.markdown("#### الإشعارات حسب الأولوية") |
|
st.bar_chart(priority_counts) |
|
|
|
with col3: |
|
|
|
read_counts = notifications_df["is_read_text"].value_counts() |
|
st.markdown("#### الإشعارات حسب الحالة") |
|
st.bar_chart(read_counts) |
|
|
|
|
|
if __name__ == "__main__": |
|
notifications_app = NotificationsApp() |
|
notifications_app.run() |
|
|