|
|
|
|
|
|
|
""" |
|
وحدة نظام الإشعارات الذكي لتحديثات المشروع والتنبيهات |
|
تتيح هذه الوحدة متابعة تحديثات المشاريع وإرسال تنبيهات ذكية مخصصة للمستخدمين بناءً على أدوارهم واهتماماتهم |
|
""" |
|
|
|
import os |
|
import sys |
|
import streamlit as st |
|
import pandas as pd |
|
import numpy as np |
|
import json |
|
import datetime |
|
import time |
|
import threading |
|
import logging |
|
from typing import List, Dict, Any, Tuple, Optional, Union |
|
|
|
|
|
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 SmartNotificationSystem: |
|
"""فئة نظام الإشعارات الذكي لتحديثات المشروع والتنبيهات""" |
|
|
|
def __init__(self): |
|
"""تهيئة نظام الإشعارات الذكي""" |
|
|
|
self.data_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../data/notifications")) |
|
os.makedirs(self.data_dir, exist_ok=True) |
|
|
|
|
|
if 'notifications' not in st.session_state: |
|
st.session_state.notifications = [] |
|
|
|
if 'unread_count' not in st.session_state: |
|
st.session_state.unread_count = 0 |
|
|
|
if 'notification_channels' not in st.session_state: |
|
st.session_state.notification_channels = { |
|
"browser": True, |
|
"email": False, |
|
"sms": False, |
|
"mobile_app": False |
|
} |
|
|
|
if 'notification_preferences' not in st.session_state: |
|
st.session_state.notification_preferences = { |
|
"project_updates": True, |
|
"document_analysis": True, |
|
"deadline_reminders": True, |
|
"risk_alerts": True, |
|
"price_changes": True, |
|
"team_mentions": True, |
|
"system_updates": True |
|
} |
|
|
|
|
|
self._load_notifications() |
|
|
|
|
|
logging.basicConfig( |
|
level=logging.INFO, |
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', |
|
handlers=[ |
|
logging.FileHandler(os.path.join(self.data_dir, "notifications.log")), |
|
logging.StreamHandler() |
|
] |
|
) |
|
self.logger = logging.getLogger("smart_notifications") |
|
|
|
def render(self): |
|
"""عرض واجهة نظام الإشعارات الذكي""" |
|
render_header("نظام الإشعارات الذكي") |
|
|
|
|
|
tabs = st.tabs([ |
|
"جميع الإشعارات", |
|
"إشعارات غير مقروءة", |
|
"إعدادات الإشعارات", |
|
"جدولة الإشعارات", |
|
"تقارير وإحصائيات" |
|
]) |
|
|
|
|
|
with tabs[0]: |
|
self._render_all_notifications() |
|
|
|
|
|
with tabs[1]: |
|
self._render_unread_notifications() |
|
|
|
|
|
with tabs[2]: |
|
self._render_notification_settings() |
|
|
|
|
|
with tabs[3]: |
|
self._render_notification_scheduling() |
|
|
|
|
|
with tabs[4]: |
|
self._render_notification_analytics() |
|
|
|
|
|
render_credits() |
|
|
|
def _render_all_notifications(self): |
|
"""عرض جميع الإشعارات""" |
|
st.markdown(""" |
|
<div class='custom-box info-box'> |
|
<h3>🔔 جميع الإشعارات</h3> |
|
<p>عرض كافة الإشعارات والتنبيهات الخاصة بالمشاريع والنظام.</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
col1, col2, col3 = st.columns([1, 1, 1]) |
|
|
|
with col1: |
|
if styled_button("تحديث الإشعارات", key="refresh_notifications", type="primary", icon="🔄"): |
|
self._load_notifications() |
|
st.success("تم تحديث الإشعارات بنجاح") |
|
|
|
with col2: |
|
if styled_button("تعليم الكل كمقروء", key="mark_all_read", type="secondary", icon="✓"): |
|
self._mark_all_as_read() |
|
st.success("تم تعليم جميع الإشعارات كمقروءة") |
|
|
|
with col3: |
|
if styled_button("حذف جميع الإشعارات", key="clear_notifications", type="danger", icon="🗑️"): |
|
confirmed = st.text_input("اكتب 'تأكيد' لحذف جميع الإشعارات", key="confirm_clear") |
|
if confirmed == "تأكيد": |
|
self._clear_all_notifications() |
|
st.success("تم حذف جميع الإشعارات بنجاح") |
|
|
|
|
|
filter_col1, filter_col2 = st.columns(2) |
|
|
|
with filter_col1: |
|
notification_type = st.multiselect( |
|
"تصفية حسب النوع", |
|
options=[ |
|
"تحديث مشروع", "وثيقة جديدة", "تذكير موعد نهائي", |
|
"تنبيه مخاطر", "تغيير سعر", "إشارة فريق العمل", "تحديث النظام" |
|
], |
|
key="filter_notification_type" |
|
) |
|
|
|
with filter_col2: |
|
date_range = st.date_input( |
|
"نطاق التاريخ", |
|
value=( |
|
datetime.datetime.now() - datetime.timedelta(days=30), |
|
datetime.datetime.now() |
|
), |
|
key="filter_date_range" |
|
) |
|
|
|
|
|
filtered_notifications = self._filter_notifications( |
|
notification_type=notification_type, |
|
date_range=date_range |
|
) |
|
|
|
|
|
if filtered_notifications: |
|
for notification in filtered_notifications: |
|
self._render_notification_card(notification) |
|
else: |
|
st.info("لا توجد إشعارات متاحة") |
|
|
|
def _render_unread_notifications(self): |
|
"""عرض الإشعارات غير المقروءة""" |
|
st.markdown(""" |
|
<div class='custom-box info-box'> |
|
<h3>🔔 الإشعارات غير المقروءة</h3> |
|
<p>عرض الإشعارات والتنبيهات التي لم تتم قراءتها بعد.</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
if styled_button("تحديث الإشعارات", key="refresh_unread", type="primary", icon="🔄"): |
|
self._load_notifications() |
|
st.success("تم تحديث الإشعارات بنجاح") |
|
|
|
with col2: |
|
if styled_button("تعليم الكل كمقروء", key="mark_unread_read", type="secondary", icon="✓"): |
|
self._mark_all_as_read() |
|
st.success("تم تعليم جميع الإشعارات كمقروءة") |
|
|
|
|
|
unread_notifications = [n for n in st.session_state.notifications if not n.get("read", False)] |
|
|
|
|
|
if unread_notifications: |
|
for notification in unread_notifications: |
|
self._render_notification_card(notification, show_mark_button=True) |
|
else: |
|
st.success("لا توجد إشعارات غير مقروءة") |
|
|
|
def _render_notification_settings(self): |
|
"""عرض إعدادات الإشعارات""" |
|
st.markdown(""" |
|
<div class='custom-box info-box'> |
|
<h3>⚙️ إعدادات الإشعارات</h3> |
|
<p>تخصيص إعدادات وتفضيلات الإشعارات الخاصة بك.</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("### قنوات الإشعارات") |
|
st.markdown("حدد الطرق التي ترغب في تلقي الإشعارات من خلالها.") |
|
|
|
channels_col1, channels_col2 = st.columns(2) |
|
|
|
with channels_col1: |
|
st.session_state.notification_channels["browser"] = st.checkbox( |
|
"إشعارات المتصفح", |
|
value=st.session_state.notification_channels.get("browser", True), |
|
key="channel_browser" |
|
) |
|
|
|
st.session_state.notification_channels["email"] = st.checkbox( |
|
"البريد الإلكتروني", |
|
value=st.session_state.notification_channels.get("email", False), |
|
key="channel_email" |
|
) |
|
|
|
if st.session_state.notification_channels["email"]: |
|
email = st.text_input( |
|
"البريد الإلكتروني للإشعارات", |
|
value=st.session_state.get("notification_email", ""), |
|
key="notification_email" |
|
) |
|
st.session_state.notification_email = email |
|
|
|
with channels_col2: |
|
st.session_state.notification_channels["sms"] = st.checkbox( |
|
"الرسائل النصية (SMS)", |
|
value=st.session_state.notification_channels.get("sms", False), |
|
key="channel_sms" |
|
) |
|
|
|
if st.session_state.notification_channels["sms"]: |
|
phone = st.text_input( |
|
"رقم الهاتف للإشعارات", |
|
value=st.session_state.get("notification_phone", ""), |
|
key="notification_phone" |
|
) |
|
st.session_state.notification_phone = phone |
|
|
|
st.session_state.notification_channels["mobile_app"] = st.checkbox( |
|
"تطبيق الهاتف المحمول", |
|
value=st.session_state.notification_channels.get("mobile_app", False), |
|
key="channel_mobile_app" |
|
) |
|
|
|
|
|
st.markdown("### أنواع الإشعارات") |
|
st.markdown("حدد أنواع الإشعارات التي ترغب في تلقيها.") |
|
|
|
prefs_col1, prefs_col2 = st.columns(2) |
|
|
|
with prefs_col1: |
|
st.session_state.notification_preferences["project_updates"] = st.checkbox( |
|
"تحديثات المشاريع", |
|
value=st.session_state.notification_preferences.get("project_updates", True), |
|
key="pref_project_updates" |
|
) |
|
|
|
st.session_state.notification_preferences["document_analysis"] = st.checkbox( |
|
"تحليل المستندات", |
|
value=st.session_state.notification_preferences.get("document_analysis", True), |
|
key="pref_document_analysis" |
|
) |
|
|
|
st.session_state.notification_preferences["deadline_reminders"] = st.checkbox( |
|
"تذكيرات المواعيد النهائية", |
|
value=st.session_state.notification_preferences.get("deadline_reminders", True), |
|
key="pref_deadline_reminders" |
|
) |
|
|
|
st.session_state.notification_preferences["risk_alerts"] = st.checkbox( |
|
"تنبيهات المخاطر", |
|
value=st.session_state.notification_preferences.get("risk_alerts", True), |
|
key="pref_risk_alerts" |
|
) |
|
|
|
with prefs_col2: |
|
st.session_state.notification_preferences["price_changes"] = st.checkbox( |
|
"تغييرات الأسعار", |
|
value=st.session_state.notification_preferences.get("price_changes", True), |
|
key="pref_price_changes" |
|
) |
|
|
|
st.session_state.notification_preferences["team_mentions"] = st.checkbox( |
|
"إشارات فريق العمل", |
|
value=st.session_state.notification_preferences.get("team_mentions", True), |
|
key="pref_team_mentions" |
|
) |
|
|
|
st.session_state.notification_preferences["system_updates"] = st.checkbox( |
|
"تحديثات النظام", |
|
value=st.session_state.notification_preferences.get("system_updates", True), |
|
key="pref_system_updates" |
|
) |
|
|
|
|
|
st.markdown("### إعدادات التكرار") |
|
|
|
frequency = st.radio( |
|
"تكرار الإشعارات المتشابهة", |
|
options=["فوري", "تجميع كل ساعة", "تجميع كل يوم", "مخصص"], |
|
index=0, |
|
key="notification_frequency" |
|
) |
|
|
|
if frequency == "مخصص": |
|
custom_hours = st.number_input( |
|
"التجميع كل (ساعات)", |
|
min_value=1, |
|
max_value=24, |
|
value=4, |
|
key="custom_frequency_hours" |
|
) |
|
st.session_state.custom_frequency_hours = custom_hours |
|
|
|
|
|
with st.expander("إعدادات متقدمة"): |
|
st.checkbox( |
|
"عرض الإشعارات عند بدء تشغيل النظام", |
|
value=True, |
|
key="show_on_startup" |
|
) |
|
|
|
st.checkbox( |
|
"الإشعارات الصوتية", |
|
value=False, |
|
key="audio_notifications" |
|
) |
|
|
|
st.checkbox( |
|
"حفظ سجل الإشعارات", |
|
value=True, |
|
key="log_notifications" |
|
) |
|
|
|
|
|
retention_days = st.slider( |
|
"الاحتفاظ بالإشعارات (أيام)", |
|
min_value=7, |
|
max_value=365, |
|
value=st.session_state.get("retention_days_value", 90), |
|
key="retention_days" |
|
) |
|
|
|
if "retention_days_value" not in st.session_state: |
|
st.session_state.retention_days_value = retention_days |
|
|
|
|
|
if styled_button("حفظ الإعدادات", key="save_notification_settings", type="primary", icon="💾"): |
|
self._save_notification_settings() |
|
st.success("تم حفظ إعدادات الإشعارات بنجاح") |
|
|
|
def _render_notification_scheduling(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: |
|
reminder_name = st.text_input("عنوان التذكير", key="new_reminder_name") |
|
reminder_desc = st.text_area("وصف التذكير", key="new_reminder_desc") |
|
reminder_date = st.date_input("تاريخ التذكير", key="new_reminder_date") |
|
reminder_time = st.time_input("وقت التذكير", key="new_reminder_time") |
|
|
|
with col2: |
|
reminder_type = st.selectbox( |
|
"نوع التذكير", |
|
options=[ |
|
"موعد نهائي للمناقصة", |
|
"اجتماع مشروع", |
|
"زيارة موقع", |
|
"تسليم مستندات", |
|
"دفعة مالية", |
|
"مراجعة أداء", |
|
"أخرى" |
|
], |
|
key="new_reminder_type" |
|
) |
|
|
|
reminder_priority = st.select_slider( |
|
"الأولوية", |
|
options=["منخفضة", "متوسطة", "عالية", "حرجة"], |
|
value="متوسطة", |
|
key="new_reminder_priority" |
|
) |
|
|
|
reminder_repeat = st.selectbox( |
|
"التكرار", |
|
options=[ |
|
"مرة واحدة", |
|
"يومياً", |
|
"أسبوعياً", |
|
"شهرياً", |
|
"سنوياً" |
|
], |
|
key="new_reminder_repeat" |
|
) |
|
|
|
if reminder_type == "أخرى": |
|
custom_type = st.text_input("حدد نوع التذكير", key="custom_reminder_type") |
|
|
|
|
|
if styled_button("إضافة التذكير", key="add_reminder", type="primary", icon="➕"): |
|
if not reminder_name or not reminder_desc: |
|
st.error("يرجى تعبئة حقول العنوان والوصف") |
|
else: |
|
self._add_scheduled_notification( |
|
title=reminder_name, |
|
message=reminder_desc, |
|
notification_date=datetime.datetime.combine(reminder_date, reminder_time), |
|
notification_type=reminder_type if reminder_type != "أخرى" else custom_type, |
|
priority=reminder_priority, |
|
repeat=reminder_repeat |
|
) |
|
st.success("تم إضافة التذكير بنجاح") |
|
|
|
|
|
st.markdown("### التذكيرات المجدولة") |
|
|
|
|
|
scheduled_notifications = self._get_scheduled_notifications() |
|
|
|
if scheduled_notifications: |
|
|
|
scheduled_df = pd.DataFrame(scheduled_notifications) |
|
|
|
|
|
display_df = scheduled_df.copy() |
|
display_df["التاريخ والوقت"] = display_df["notification_date"].apply(lambda x: x.strftime("%Y-%m-%d %H:%M")) |
|
display_df["العنوان"] = display_df["title"] |
|
display_df["النوع"] = display_df["notification_type"] |
|
display_df["الأولوية"] = display_df["priority"] |
|
display_df["التكرار"] = display_df["repeat"] |
|
|
|
|
|
st.dataframe( |
|
display_df[["العنوان", "النوع", "التاريخ والوقت", "الأولوية", "التكرار"]], |
|
use_container_width=True |
|
) |
|
|
|
|
|
for notification in scheduled_notifications: |
|
with st.expander(f"{notification['title']} - {notification['notification_date'].strftime('%Y-%m-%d %H:%M')}"): |
|
notification_col1, notification_col2 = st.columns([3, 1]) |
|
|
|
with notification_col1: |
|
st.markdown(f"**الوصف:** {notification['message']}") |
|
st.markdown(f"**النوع:** {notification['notification_type']}") |
|
st.markdown(f"**الأولوية:** {notification['priority']}") |
|
st.markdown(f"**التكرار:** {notification['repeat']}") |
|
|
|
with notification_col2: |
|
if styled_button("تعديل", key=f"edit_{notification['id']}", type="secondary", icon="✏️"): |
|
|
|
st.info("ميزة التعديل قيد التطوير") |
|
|
|
if styled_button("حذف", key=f"delete_{notification['id']}", type="danger", icon="🗑️"): |
|
self._delete_scheduled_notification(notification['id']) |
|
st.rerun() |
|
else: |
|
st.info("لا توجد تذكيرات مجدولة") |
|
|
|
def _render_notification_analytics(self): |
|
"""عرض تقارير وإحصائيات الإشعارات""" |
|
st.markdown(""" |
|
<div class='custom-box info-box'> |
|
<h3>📊 تقارير وإحصائيات الإشعارات</h3> |
|
<p>تحليل وعرض إحصائيات الإشعارات والتنبيهات.</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("### إحصائيات عامة") |
|
|
|
|
|
if st.session_state.notifications: |
|
|
|
total_count = len(st.session_state.notifications) |
|
read_count = len([n for n in st.session_state.notifications if n.get("read", False)]) |
|
unread_count = total_count - read_count |
|
|
|
|
|
notification_types = {} |
|
for notification in st.session_state.notifications: |
|
notification_type = notification.get("notification_type", "أخرى") |
|
notification_types[notification_type] = notification_types.get(notification_type, 0) + 1 |
|
|
|
|
|
metric_col1, metric_col2, metric_col3 = st.columns(3) |
|
|
|
with metric_col1: |
|
st.metric("إجمالي الإشعارات", total_count) |
|
|
|
with metric_col2: |
|
st.metric("الإشعارات المقروءة", read_count, delta=f"{read_count/total_count*100:.1f}%" if total_count > 0 else "0%") |
|
|
|
with metric_col3: |
|
st.metric("الإشعارات غير المقروءة", unread_count, delta=f"{unread_count/total_count*100:.1f}%" if total_count > 0 else "0%") |
|
|
|
|
|
st.markdown("### توزيع الإشعارات حسب النوع") |
|
|
|
|
|
types_df = pd.DataFrame({ |
|
"النوع": list(notification_types.keys()), |
|
"العدد": list(notification_types.values()) |
|
}) |
|
|
|
|
|
import plotly.express as px |
|
|
|
fig = px.pie( |
|
types_df, |
|
values="العدد", |
|
names="النوع", |
|
title="توزيع الإشعارات حسب النوع", |
|
color_discrete_sequence=px.colors.sequential.RdBu |
|
) |
|
|
|
fig.update_layout( |
|
title_font_size=20, |
|
font_family="Arial", |
|
font_size=14, |
|
height=400 |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("### توزيع الإشعارات حسب الوقت") |
|
|
|
|
|
dates = [ |
|
n.get("timestamp", datetime.datetime.now()).replace(hour=0, minute=0, second=0, microsecond=0) |
|
for n in st.session_state.notifications |
|
if "timestamp" in n |
|
] |
|
|
|
if dates: |
|
date_counts = pd.Series(dates).value_counts().sort_index() |
|
|
|
|
|
date_df = pd.DataFrame({ |
|
"التاريخ": date_counts.index, |
|
"العدد": date_counts.values |
|
}) |
|
|
|
|
|
fig2 = px.line( |
|
date_df, |
|
x="التاريخ", |
|
y="العدد", |
|
title="توزيع الإشعارات حسب التاريخ", |
|
markers=True |
|
) |
|
|
|
fig2.update_layout( |
|
title_font_size=20, |
|
font_family="Arial", |
|
font_size=14, |
|
height=400 |
|
) |
|
|
|
st.plotly_chart(fig2, use_container_width=True) |
|
|
|
|
|
st.markdown("### تصدير بيانات الإشعارات") |
|
|
|
export_col1, export_col2 = st.columns(2) |
|
|
|
with export_col1: |
|
if styled_button("تصدير CSV", key="export_csv", type="primary", icon="📄"): |
|
|
|
export_df = pd.DataFrame(st.session_state.notifications) |
|
|
|
|
|
if "timestamp" in export_df.columns: |
|
export_df["timestamp"] = export_df["timestamp"].apply( |
|
lambda x: x.strftime("%Y-%m-%d %H:%M:%S") if isinstance(x, datetime.datetime) else str(x) |
|
) |
|
|
|
|
|
csv_data = export_df.to_csv(index=False) |
|
|
|
|
|
st.download_button( |
|
label="تنزيل ملف CSV", |
|
data=csv_data, |
|
file_name=f"notifications_export_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", |
|
mime="text/csv" |
|
) |
|
|
|
with export_col2: |
|
if styled_button("تصدير JSON", key="export_json", type="primary", icon="📄"): |
|
|
|
export_data = [] |
|
for notification in st.session_state.notifications: |
|
export_item = notification.copy() |
|
if "timestamp" in export_item and isinstance(export_item["timestamp"], datetime.datetime): |
|
export_item["timestamp"] = export_item["timestamp"].strftime("%Y-%m-%d %H:%M:%S") |
|
export_data.append(export_item) |
|
|
|
|
|
json_data = json.dumps(export_data, ensure_ascii=False, indent=2) |
|
|
|
|
|
st.download_button( |
|
label="تنزيل ملف JSON", |
|
data=json_data, |
|
file_name=f"notifications_export_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.json", |
|
mime="application/json" |
|
) |
|
else: |
|
st.info("لا توجد بيانات كافية لعرض الرسم البياني") |
|
else: |
|
st.info("لا توجد إشعارات لعرض الإحصائيات") |
|
|
|
def _render_notification_card(self, notification, show_mark_button=False): |
|
"""عرض بطاقة إشعار""" |
|
|
|
card_style = "notification-card" |
|
if not notification.get("read", False): |
|
card_style += " unread-notification" |
|
|
|
priority = notification.get("priority", "متوسطة") |
|
if priority == "عالية" or priority == "حرجة": |
|
card_style += " high-priority-notification" |
|
|
|
|
|
icon_map = { |
|
"تحديث مشروع": "🔄", |
|
"وثيقة جديدة": "📄", |
|
"تذكير موعد نهائي": "⏰", |
|
"تنبيه مخاطر": "⚠️", |
|
"تغيير سعر": "💰", |
|
"إشارة فريق العمل": "👥", |
|
"تحديث النظام": "🖥️" |
|
} |
|
|
|
notification_type = notification.get("notification_type", "تحديث مشروع") |
|
icon = icon_map.get(notification_type, "🔔") |
|
|
|
|
|
timestamp = notification.get("timestamp", datetime.datetime.now()) |
|
if isinstance(timestamp, datetime.datetime): |
|
time_str = timestamp.strftime("%Y-%m-%d %H:%M") |
|
else: |
|
time_str = str(timestamp) |
|
|
|
|
|
card_html = f""" |
|
<div class="{card_style}"> |
|
<div class="notification-header"> |
|
<span class="notification-icon">{icon}</span> |
|
<span class="notification-title">{notification.get('title', 'إشعار جديد')}</span> |
|
<span class="notification-time">{time_str}</span> |
|
</div> |
|
<div class="notification-body"> |
|
<p>{notification.get('message', '')}</p> |
|
</div> |
|
<div class="notification-footer"> |
|
<span class="notification-type">{notification_type}</span> |
|
<span class="notification-priority">{priority}</span> |
|
</div> |
|
</div> |
|
""" |
|
|
|
|
|
st.markdown(card_html, unsafe_allow_html=True) |
|
|
|
|
|
if show_mark_button: |
|
col1, col2 = st.columns([1, 4]) |
|
|
|
with col1: |
|
if styled_button("تعليم كمقروء", key=f"mark_read_{notification.get('id', '')}", type="secondary", icon="✓"): |
|
self._mark_notification_as_read(notification.get('id', '')) |
|
st.rerun() |
|
|
|
with col2: |
|
if notification.get("link"): |
|
if styled_button("عرض التفاصيل", key=f"view_details_{notification.get('id', '')}", type="primary", icon="🔍"): |
|
|
|
|
|
st.markdown(f"[عرض التفاصيل]({notification.get('link')})") |
|
|
|
def add_notification(self, title, message, notification_type="تحديث مشروع", priority="متوسطة", link=None): |
|
""" |
|
إضافة إشعار جديد |
|
|
|
المعلمات: |
|
title: عنوان الإشعار |
|
message: نص الإشعار |
|
notification_type: نوع الإشعار |
|
priority: أولوية الإشعار |
|
link: رابط مرتبط بالإشعار (اختياري) |
|
|
|
الإرجاع: |
|
معرف الإشعار الجديد |
|
""" |
|
|
|
notification_id = f"notif_{int(time.time())}_{len(st.session_state.notifications)}" |
|
|
|
|
|
notification = { |
|
"id": notification_id, |
|
"title": title, |
|
"message": message, |
|
"notification_type": notification_type, |
|
"priority": priority, |
|
"read": False, |
|
"timestamp": datetime.datetime.now(), |
|
"link": link |
|
} |
|
|
|
|
|
st.session_state.notifications.append(notification) |
|
|
|
|
|
st.session_state.unread_count += 1 |
|
|
|
|
|
self._save_notifications() |
|
|
|
|
|
self.logger.info( |
|
f"تمت إضافة إشعار جديد: {title} ({notification_type})" |
|
) |
|
|
|
return notification_id |
|
|
|
def _mark_notification_as_read(self, notification_id): |
|
""" |
|
تعليم إشعار كمقروء |
|
|
|
المعلمات: |
|
notification_id: معرف الإشعار |
|
|
|
الإرجاع: |
|
قيمة بوليانية تشير إلى نجاح العملية |
|
""" |
|
|
|
for i, notification in enumerate(st.session_state.notifications): |
|
if notification.get("id") == notification_id and not notification.get("read", False): |
|
|
|
st.session_state.notifications[i]["read"] = True |
|
|
|
|
|
st.session_state.unread_count = max(0, st.session_state.unread_count - 1) |
|
|
|
|
|
self._save_notifications() |
|
|
|
return True |
|
|
|
return False |
|
|
|
def _mark_all_as_read(self): |
|
""" |
|
تعليم جميع الإشعارات كمقروءة |
|
|
|
الإرجاع: |
|
عدد الإشعارات التي تم تعليمها |
|
""" |
|
count = 0 |
|
|
|
|
|
for i, notification in enumerate(st.session_state.notifications): |
|
if not notification.get("read", False): |
|
st.session_state.notifications[i]["read"] = True |
|
count += 1 |
|
|
|
|
|
st.session_state.unread_count = 0 |
|
|
|
|
|
self._save_notifications() |
|
|
|
return count |
|
|
|
def _clear_all_notifications(self): |
|
""" |
|
حذف جميع الإشعارات |
|
|
|
الإرجاع: |
|
عدد الإشعارات التي تم حذفها |
|
""" |
|
count = len(st.session_state.notifications) |
|
|
|
|
|
st.session_state.notifications = [] |
|
|
|
|
|
st.session_state.unread_count = 0 |
|
|
|
|
|
self._save_notifications() |
|
|
|
return count |
|
|
|
def _filter_notifications(self, notification_type=None, date_range=None): |
|
""" |
|
تصفية الإشعارات حسب النوع والتاريخ |
|
|
|
المعلمات: |
|
notification_type: قائمة أنواع الإشعارات |
|
date_range: نطاق تاريخ الإشعارات |
|
|
|
الإرجاع: |
|
قائمة الإشعارات المصفاة |
|
""" |
|
filtered_notifications = st.session_state.notifications.copy() |
|
|
|
|
|
if notification_type and len(notification_type) > 0: |
|
filtered_notifications = [ |
|
n for n in filtered_notifications |
|
if n.get("notification_type") in notification_type |
|
] |
|
|
|
|
|
if date_range and len(date_range) == 2: |
|
start_date, end_date = date_range |
|
|
|
|
|
start_date = datetime.datetime.combine(start_date, datetime.time.min) |
|
end_date = datetime.datetime.combine(end_date, datetime.time.max) |
|
|
|
filtered_notifications = [ |
|
n for n in filtered_notifications |
|
if isinstance(n.get("timestamp"), datetime.datetime) and |
|
start_date <= n.get("timestamp") <= end_date |
|
] |
|
|
|
return filtered_notifications |
|
|
|
def _add_scheduled_notification(self, title, message, notification_date, notification_type="تذكير", priority="متوسطة", repeat="مرة واحدة"): |
|
""" |
|
إضافة إشعار مجدول |
|
|
|
المعلمات: |
|
title: عنوان الإشعار |
|
message: نص الإشعار |
|
notification_date: تاريخ ووقت الإشعار |
|
notification_type: نوع الإشعار |
|
priority: أولوية الإشعار |
|
repeat: نمط تكرار الإشعار |
|
|
|
الإرجاع: |
|
معرف الإشعار المجدول |
|
""" |
|
|
|
scheduled_id = f"sched_{int(time.time())}_{len(self._get_scheduled_notifications())}" |
|
|
|
|
|
scheduled_notification = { |
|
"id": scheduled_id, |
|
"title": title, |
|
"message": message, |
|
"notification_date": notification_date, |
|
"notification_type": notification_type, |
|
"priority": priority, |
|
"repeat": repeat, |
|
"created_at": datetime.datetime.now(), |
|
"last_triggered": None |
|
} |
|
|
|
|
|
scheduled_notifications = self._get_scheduled_notifications() |
|
scheduled_notifications.append(scheduled_notification) |
|
|
|
|
|
self._save_scheduled_notifications(scheduled_notifications) |
|
|
|
|
|
self.logger.info( |
|
f"تمت إضافة إشعار مجدول: {title} ({notification_date.strftime('%Y-%m-%d %H:%M')})" |
|
) |
|
|
|
return scheduled_id |
|
|
|
def _delete_scheduled_notification(self, notification_id): |
|
""" |
|
حذف إشعار مجدول |
|
|
|
المعلمات: |
|
notification_id: معرف الإشعار المجدول |
|
|
|
الإرجاع: |
|
قيمة بوليانية تشير إلى نجاح العملية |
|
""" |
|
scheduled_notifications = self._get_scheduled_notifications() |
|
|
|
|
|
for i, notification in enumerate(scheduled_notifications): |
|
if notification.get("id") == notification_id: |
|
|
|
del scheduled_notifications[i] |
|
|
|
|
|
self._save_scheduled_notifications(scheduled_notifications) |
|
|
|
|
|
self.logger.info( |
|
f"تم حذف الإشعار المجدول: {notification_id}" |
|
) |
|
|
|
return True |
|
|
|
return False |
|
|
|
def _get_scheduled_notifications(self): |
|
""" |
|
الحصول على قائمة الإشعارات المجدولة |
|
|
|
الإرجاع: |
|
قائمة الإشعارات المجدولة |
|
""" |
|
try: |
|
|
|
scheduled_file = os.path.join(self.data_dir, "scheduled_notifications.json") |
|
|
|
if os.path.exists(scheduled_file): |
|
with open(scheduled_file, 'r', encoding='utf-8') as f: |
|
scheduled_data = json.load(f) |
|
|
|
|
|
for notification in scheduled_data: |
|
if "notification_date" in notification: |
|
notification["notification_date"] = datetime.datetime.fromisoformat(notification["notification_date"]) |
|
|
|
if "created_at" in notification: |
|
notification["created_at"] = datetime.datetime.fromisoformat(notification["created_at"]) |
|
|
|
if "last_triggered" in notification and notification["last_triggered"]: |
|
notification["last_triggered"] = datetime.datetime.fromisoformat(notification["last_triggered"]) |
|
|
|
return scheduled_data |
|
|
|
return [] |
|
|
|
except Exception as e: |
|
self.logger.error(f"حدث خطأ أثناء قراءة الإشعارات المجدولة: {str(e)}") |
|
return [] |
|
|
|
def _save_scheduled_notifications(self, scheduled_notifications): |
|
""" |
|
حفظ قائمة الإشعارات المجدولة |
|
|
|
المعلمات: |
|
scheduled_notifications: قائمة الإشعارات المجدولة |
|
""" |
|
try: |
|
|
|
os.makedirs(self.data_dir, exist_ok=True) |
|
|
|
|
|
scheduled_data = [] |
|
|
|
for notification in scheduled_notifications: |
|
notification_copy = notification.copy() |
|
|
|
if "notification_date" in notification_copy and isinstance(notification_copy["notification_date"], datetime.datetime): |
|
notification_copy["notification_date"] = notification_copy["notification_date"].isoformat() |
|
|
|
if "created_at" in notification_copy and isinstance(notification_copy["created_at"], datetime.datetime): |
|
notification_copy["created_at"] = notification_copy["created_at"].isoformat() |
|
|
|
if "last_triggered" in notification_copy and isinstance(notification_copy["last_triggered"], datetime.datetime): |
|
notification_copy["last_triggered"] = notification_copy["last_triggered"].isoformat() |
|
|
|
scheduled_data.append(notification_copy) |
|
|
|
|
|
scheduled_file = os.path.join(self.data_dir, "scheduled_notifications.json") |
|
|
|
with open(scheduled_file, 'w', encoding='utf-8') as f: |
|
json.dump(scheduled_data, f, ensure_ascii=False, indent=2) |
|
|
|
except Exception as e: |
|
self.logger.error(f"حدث خطأ أثناء حفظ الإشعارات المجدولة: {str(e)}") |
|
|
|
def _save_notification_settings(self): |
|
"""حفظ إعدادات الإشعارات""" |
|
try: |
|
|
|
os.makedirs(self.data_dir, exist_ok=True) |
|
|
|
|
|
settings_data = { |
|
"notification_channels": st.session_state.notification_channels, |
|
"notification_preferences": st.session_state.notification_preferences, |
|
"notification_email": st.session_state.get("notification_email", ""), |
|
"notification_phone": st.session_state.get("notification_phone", ""), |
|
"notification_frequency": st.session_state.get("notification_frequency", "فوري"), |
|
"custom_frequency_hours": st.session_state.get("custom_frequency_hours", 4), |
|
"show_on_startup": st.session_state.get("show_on_startup", True), |
|
"audio_notifications": st.session_state.get("audio_notifications", False), |
|
"log_notifications": st.session_state.get("log_notifications", True), |
|
"retention_days": st.session_state.get("retention_days", 90) |
|
} |
|
|
|
|
|
settings_file = os.path.join(self.data_dir, "notification_settings.json") |
|
|
|
with open(settings_file, 'w', encoding='utf-8') as f: |
|
json.dump(settings_data, f, ensure_ascii=False, indent=2) |
|
|
|
|
|
self.logger.info("تم حفظ إعدادات الإشعارات بنجاح") |
|
|
|
except Exception as e: |
|
self.logger.error(f"حدث خطأ أثناء حفظ إعدادات الإشعارات: {str(e)}") |
|
|
|
def _load_notification_settings(self): |
|
"""تحميل إعدادات الإشعارات""" |
|
try: |
|
|
|
settings_file = os.path.join(self.data_dir, "notification_settings.json") |
|
|
|
if os.path.exists(settings_file): |
|
with open(settings_file, 'r', encoding='utf-8') as f: |
|
settings_data = json.load(f) |
|
|
|
|
|
st.session_state.notification_channels = settings_data.get("notification_channels", {}) |
|
st.session_state.notification_preferences = settings_data.get("notification_preferences", {}) |
|
st.session_state.notification_email = settings_data.get("notification_email", "") |
|
st.session_state.notification_phone = settings_data.get("notification_phone", "") |
|
st.session_state.notification_frequency = settings_data.get("notification_frequency", "فوري") |
|
st.session_state.custom_frequency_hours = settings_data.get("custom_frequency_hours", 4) |
|
st.session_state.show_on_startup = settings_data.get("show_on_startup", True) |
|
st.session_state.audio_notifications = settings_data.get("audio_notifications", False) |
|
st.session_state.log_notifications = settings_data.get("log_notifications", True) |
|
st.session_state.retention_days = settings_data.get("retention_days", 90) |
|
|
|
|
|
self.logger.info("تم تحميل إعدادات الإشعارات بنجاح") |
|
|
|
except Exception as e: |
|
self.logger.error(f"حدث خطأ أثناء تحميل إعدادات الإشعارات: {str(e)}") |
|
|
|
def _save_notifications(self): |
|
"""حفظ الإشعارات""" |
|
try: |
|
|
|
os.makedirs(self.data_dir, exist_ok=True) |
|
|
|
|
|
notifications_data = [] |
|
|
|
for notification in st.session_state.notifications: |
|
notification_copy = notification.copy() |
|
|
|
if "timestamp" in notification_copy and isinstance(notification_copy["timestamp"], datetime.datetime): |
|
notification_copy["timestamp"] = notification_copy["timestamp"].isoformat() |
|
|
|
notifications_data.append(notification_copy) |
|
|
|
|
|
notifications_file = os.path.join(self.data_dir, "notifications.json") |
|
|
|
with open(notifications_file, 'w', encoding='utf-8') as f: |
|
json.dump(notifications_data, f, ensure_ascii=False, indent=2) |
|
|
|
except Exception as e: |
|
self.logger.error(f"حدث خطأ أثناء حفظ الإشعارات: {str(e)}") |
|
|
|
def _load_notifications(self): |
|
"""تحميل الإشعارات""" |
|
try: |
|
|
|
notifications_file = os.path.join(self.data_dir, "notifications.json") |
|
|
|
if os.path.exists(notifications_file): |
|
with open(notifications_file, 'r', encoding='utf-8') as f: |
|
notifications_data = json.load(f) |
|
|
|
|
|
for notification in notifications_data: |
|
if "timestamp" in notification: |
|
notification["timestamp"] = datetime.datetime.fromisoformat(notification["timestamp"]) |
|
|
|
|
|
st.session_state.notifications = notifications_data |
|
|
|
|
|
st.session_state.unread_count = len([ |
|
n for n in st.session_state.notifications |
|
if not n.get("read", False) |
|
]) |
|
|
|
|
|
self._load_notification_settings() |
|
|
|
|
|
self.logger.info(f"تم تحميل {len(notifications_data)} إشعار بنجاح") |
|
|
|
except Exception as e: |
|
self.logger.error(f"حدث خطأ أثناء تحميل الإشعارات: {str(e)}") |
|
|
|
def check_scheduled_notifications(self): |
|
""" |
|
التحقق من الإشعارات المجدولة وإطلاقها إذا حان وقتها |
|
|
|
الإرجاع: |
|
عدد الإشعارات التي تم إطلاقها |
|
""" |
|
count = 0 |
|
|
|
|
|
scheduled_notifications = self._get_scheduled_notifications() |
|
|
|
|
|
now = datetime.datetime.now() |
|
|
|
|
|
for notification in scheduled_notifications: |
|
notification_date = notification.get("notification_date") |
|
|
|
if notification_date and notification_date <= now: |
|
|
|
self.add_notification( |
|
title=notification.get("title"), |
|
message=notification.get("message"), |
|
notification_type=notification.get("notification_type"), |
|
priority=notification.get("priority") |
|
) |
|
|
|
|
|
notification["last_triggered"] = now |
|
|
|
|
|
repeat = notification.get("repeat", "مرة واحدة") |
|
|
|
if repeat == "مرة واحدة": |
|
|
|
self._delete_scheduled_notification(notification.get("id")) |
|
else: |
|
|
|
if repeat == "يومياً": |
|
new_date = notification_date + datetime.timedelta(days=1) |
|
elif repeat == "أسبوعياً": |
|
new_date = notification_date + datetime.timedelta(weeks=1) |
|
elif repeat == "شهرياً": |
|
|
|
new_month = notification_date.month + 1 |
|
new_year = notification_date.year |
|
|
|
if new_month > 12: |
|
new_month = 1 |
|
new_year += 1 |
|
|
|
new_date = notification_date.replace(year=new_year, month=new_month) |
|
elif repeat == "سنوياً": |
|
new_date = notification_date.replace(year=notification_date.year + 1) |
|
else: |
|
|
|
new_date = notification_date + datetime.timedelta(days=1) |
|
|
|
|
|
notification["notification_date"] = new_date |
|
|
|
count += 1 |
|
|
|
|
|
if count > 0: |
|
self._save_scheduled_notifications(scheduled_notifications) |
|
|
|
return count |
|
|
|
|
|
|
|
class NotificationsApp: |
|
"""وحدة تطبيق نظام الإشعارات الذكي""" |
|
|
|
def __init__(self): |
|
"""تهيئة وحدة تطبيق نظام الإشعارات الذكي""" |
|
self.smart_notification_system = SmartNotificationSystem() |
|
|
|
def render(self): |
|
"""عرض واجهة وحدة تطبيق نظام الإشعارات الذكي""" |
|
st.markdown("<h2 class='module-title'>نظام الإشعارات الذكي لتحديثات المشروع والتنبيهات</h2>", unsafe_allow_html=True) |
|
|
|
st.markdown(""" |
|
<div class="module-description"> |
|
يتيح لك نظام الإشعارات الذكي متابعة تحديثات المشاريع وتلقي تنبيهات مخصصة حسب أدوارك واهتماماتك. |
|
يمكنك تخصيص إعدادات الإشعارات وجدولة التذكيرات للمواعيد النهائية والمهام. |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
self.smart_notification_system.render() |
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
st.set_page_config( |
|
page_title="نظام الإشعارات الذكي | WAHBi AI", |
|
page_icon="🔔", |
|
layout="wide", |
|
initial_sidebar_state="expanded" |
|
) |
|
|
|
app = NotificationsApp() |
|
app.render() |